/*
 * Copyright (C) 2006  Anthony Liguori <anthony@codemonkey.ws>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  GTK VNC Widget
 */

#include "vncshmimage.h"

#include <string.h>

#include <machine/param.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>

#include <gdk/gdkx.h>

#define VNC_SHM_IMAGE_GET_PRIVATE(obj) \
      (G_TYPE_INSTANCE_GET_PRIVATE((obj), VNC_TYPE_SHM_IMAGE, VncShmImagePrivate))

struct _VncShmImagePrivate
{
	XShmSegmentInfo *x_shm_info;
	XImage *ximage;
	GdkImage *image;
	Display *xdisplay;
};

static VncShmImage *vnc_shm_image_initialize(VncShmImage *obj, GdkVisual *visual, gint width, gint height, gboolean use_shm)
{
	VncShmImagePrivate *priv = obj->priv;
	VncShmImageClass *klass = VNC_SHM_IMAGE_GET_CLASS(obj);

	priv->x_shm_info = g_new(XShmSegmentInfo, 1);
	priv->x_shm_info->shmid = -1;
	priv->x_shm_info->shmaddr = (char *)-1;

	priv->xdisplay = gdk_x11_display_get_xdisplay(gdk_display_get_default());

	priv->ximage = NULL;
	priv->x_shm_info->shmid = -1;
	priv->x_shm_info->shmaddr = (char *)-1;

	if (!klass->has_xshm || !use_shm)
		goto error;

	priv->ximage = XShmCreateImage(priv->xdisplay,
				       GDK_VISUAL_XVISUAL(visual),
				       visual->depth,
				       ZPixmap,
				       NULL,
				       priv->x_shm_info,
				       width, height);
	if (priv->ximage == NULL)
		goto error;

	priv->x_shm_info->shmid = shmget(IPC_PRIVATE,
					 priv->ximage->bytes_per_line * priv->ximage->height,
					 IPC_CREAT | 0600);
	if (priv->x_shm_info->shmid == -1)
		goto error;

	priv->x_shm_info->shmaddr = shmat(priv->x_shm_info->shmid, NULL, 0);
	priv->ximage->data = priv->x_shm_info->shmaddr;

	if (priv->x_shm_info->shmaddr == (char *)-1)
		goto error;

	gdk_error_trap_push();

	XShmAttach(priv->xdisplay, priv->x_shm_info);
	XSync(priv->xdisplay, False);

	if (gdk_error_trap_pop())
		goto error;

	obj->shmid = priv->x_shm_info->shmid;
	obj->depth = priv->ximage->depth;
	obj->bpp = priv->ximage->bits_per_pixel / 8;
	obj->pixels = (gpointer)priv->x_shm_info->shmaddr;
	obj->bytes_per_line = priv->ximage->bytes_per_line;
	obj->width = width;
	obj->height = height;

	return obj;

error:
	if (priv->x_shm_info->shmaddr != (char *)-1)
		shmdt(priv->x_shm_info->shmaddr);

	if (priv->x_shm_info->shmid != -1)
		shmctl(priv->x_shm_info->shmid, IPC_RMID, NULL);

	if (priv->ximage)
		XDestroyImage(priv->ximage);

	g_free(priv->x_shm_info);
	priv->x_shm_info = 0;

	priv->image = gdk_image_new(GDK_IMAGE_FASTEST, visual, width, height);

	obj->shmid = -1;
	obj->depth = priv->image->depth;
	obj->bpp = priv->image->bpp;
	obj->pixels = priv->image->mem;
	obj->bytes_per_line = priv->image->bpl;
	obj->width = width;
	obj->height =  height;

	return obj;
}

static void vnc_shm_image_finalize(GObject *obj)
{
	VncShmImagePrivate *priv = VNC_SHM_IMAGE(obj)->priv;

	if (!priv->image) {
		XShmDetach(priv->xdisplay, priv->x_shm_info);
		XDestroyImage(priv->ximage);
		shmdt(priv->x_shm_info->shmaddr);
		g_free(priv->x_shm_info);
	} else
		gdk_image_unref(priv->image);
}

VncShmImage *vnc_shm_image_new(GdkVisual *visual, gint width, gint height, gboolean use_shm)
{
	VncShmImage *obj;

	obj = VNC_SHM_IMAGE(g_object_new(VNC_TYPE_SHM_IMAGE, NULL));
	if (!obj)
		return NULL;

	return vnc_shm_image_initialize(obj, visual, width, height, use_shm);
}

static void vnc_shm_image_class_init(VncShmImageClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
	Bool pixmaps;
	int major, minor;
	Display *xdisplay = gdk_x11_display_get_xdisplay(gdk_display_get_default());

	if (XShmQueryExtension(xdisplay) &&
	    XShmQueryVersion(xdisplay, &major, &minor, &pixmaps))
		klass->has_xshm = TRUE;
	else
		klass->has_xshm = FALSE;

	gobject_class->finalize = vnc_shm_image_finalize;

	g_type_class_add_private(klass, sizeof(VncShmImagePrivate));
}

static void vnc_shm_image_init(GTypeInstance *instance, gpointer klass G_GNUC_UNUSED)
{
	VncShmImage *obj = VNC_SHM_IMAGE(instance);

	obj->priv = VNC_SHM_IMAGE_GET_PRIVATE(obj);
	memset(obj->priv, 0, sizeof(VncShmImagePrivate));
}

void vnc_shm_image_draw(VncShmImage *obj,
			GdkDrawable *drawable,
			GdkGC *gc,
			gint xsrc, gint ysrc,
			gint xdest, gint ydest,
			gint width, gint height)
{
	VncShmImagePrivate *priv = obj->priv;

	if (priv->image)
		gdk_draw_image(drawable,
			       gc,
			       priv->image,
			       xsrc, ysrc,
			       xdest, ydest,
			       width, height);
	else 
		XShmPutImage(GDK_DRAWABLE_XDISPLAY(drawable), 
			     GDK_DRAWABLE_XID(drawable),
			     GDK_GC_XGC(gc),
			     priv->ximage, 
			     xsrc, ysrc, xdest, ydest,
			     width, height, False);
}

GdkPixbuf *vnc_shm_image_get_pixbuf(VncShmImage *obj)
{
	VncShmImagePrivate *priv = obj->priv;
	if (priv->image) {
		GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
						   FALSE,
						   8,
						   obj->width,
						   obj->height);

		if (!gdk_pixbuf_get_from_image(pixbuf,
					       priv->image,
					       gdk_colormap_get_system(),
					       0,0,0,0,
					       obj->width,
					       obj->height)) {
			gdk_pixbuf_unref(pixbuf);
			return NULL;
		}
		return pixbuf;
	} else {
		return gdk_pixbuf_new_from_data((guchar *)priv->ximage,
						GDK_COLORSPACE_RGB,
						FALSE,
						obj->bpp * 8,
						obj->width,
						obj->height,
						obj->bytes_per_line,
						NULL,
						NULL);
	}
}


GType vnc_shm_image_get_type(void)
{
	static GType type;

	if (type == 0) {
		static const GTypeInfo info = {
			sizeof(VncShmImageClass),
			NULL,
			NULL,
			(GClassInitFunc)vnc_shm_image_class_init,
			NULL,
			NULL,
			sizeof(VncShmImage),
			0,
			vnc_shm_image_init,
			NULL
		};

		type = g_type_register_static(G_TYPE_OBJECT,
					      "VncShmImage",
					      &info,
					      0);
	}

	return type;
}

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 *  tab-width: 8
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1