/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Authored By Johan Bilien * * Copyright (C) 2007 OpenedHand * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * */ /** * SECTION:clutter-x11-texture-pixmap * @short_description: A texture which displays the content of an X Pixmap. * * #ClutterX11TexturePixmap is a class for displaying the content of an * X Pixmap as a ClutterActor. Used together with the X Composite extension, * it allows to display the content of X Windows inside Clutter. * * The class uses the GLX_EXT_texture_from_pixmap OpenGL extension * (http://people.freedesktop.org/~davidr/GLX_EXT_texture_from_pixmap.txt) * if available */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "../clutter-marshal.h" #include "clutter-x11-texture-pixmap.h" #include "clutter-x11.h" #include "clutter-backend-x11.h" #include "clutter-private.h" #include "cogl/cogl.h" #include #include #include #include #include #include enum { PROP_PIXMAP = 1, PROP_PIXMAP_WIDTH, PROP_PIXMAP_HEIGHT, PROP_DEPTH, PROP_AUTO, PROP_WINDOW, PROP_WINDOW_REDIRECT_AUTOMATIC, PROP_WINDOW_MAPPED, PROP_DESTROYED, PROP_WINDOW_X, PROP_WINDOW_Y, PROP_WINDOW_OVERRIDE_REDIRECT }; enum { UPDATE_AREA, QUEUE_DAMAGE_REDRAW, /* FIXME: Pixmap lost signal? */ LAST_SIGNAL }; static ClutterX11FilterReturn on_x_event_filter (XEvent *xev, ClutterEvent *cev, gpointer data); static void clutter_x11_texture_pixmap_update_area_real (ClutterX11TexturePixmap *texture, gint x, gint y, gint width, gint height); static void clutter_x11_texture_pixmap_set_mapped (ClutterX11TexturePixmap *texture, gboolean mapped); static void clutter_x11_texture_pixmap_destroyed (ClutterX11TexturePixmap *texture); static guint signals[LAST_SIGNAL] = { 0, }; struct _ClutterX11TexturePixmapPrivate { Window window; Pixmap pixmap; guint pixmap_width, pixmap_height; guint depth; XImage *image; XShmSegmentInfo shminfo; gboolean automatic_updates; Damage damage; Drawable damage_drawable; /* FIXME: lots of gbooleans. coalesce into bitfields */ gboolean have_shm; gboolean window_redirect_automatic; gboolean window_mapped; gboolean destroyed; gboolean owns_pixmap; gboolean override_redirect; gint window_x, window_y; }; static int _damage_event_base = 0; /* FIXME: Ultimatly with current cogl we should subclass clutter actor */ G_DEFINE_TYPE (ClutterX11TexturePixmap, \ clutter_x11_texture_pixmap, \ CLUTTER_TYPE_TEXTURE); static gboolean check_extensions (ClutterX11TexturePixmap *texture) { int damage_error; ClutterX11TexturePixmapPrivate *priv; Display *dpy; priv = texture->priv; if (_damage_event_base) return TRUE; dpy = clutter_x11_get_default_display(); if (!XDamageQueryExtension (dpy, &_damage_event_base, &damage_error)) { g_warning ("No Damage extension"); return FALSE; } return TRUE; } static void free_shm_resources (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; priv = texture->priv; if (priv->shminfo.shmid != -1) { XShmDetach(clutter_x11_get_default_display(), &priv->shminfo); shmdt(priv->shminfo.shmaddr); shmctl(priv->shminfo.shmid, IPC_RMID, 0); priv->shminfo.shmid = -1; } } /* Tries to allocate enough shared mem to handle a full size * update size of the X Pixmap. */ static gboolean try_alloc_shm (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; XImage *dummy_image; Display *dpy; priv = texture->priv; dpy = clutter_x11_get_default_display(); g_return_val_if_fail (priv->pixmap, FALSE); if (!XShmQueryExtension(dpy) || g_getenv("CLUTTER_X11_NO_SHM")) { priv->have_shm = FALSE; return FALSE; } clutter_x11_trap_x_errors (); /* We are creating a dummy_image so we can have Xlib calculate * image->bytes_per_line - including any magic padding it may * want - for the largest possible ximage we might need to use * when handling updates to the texture. * * Note: we pass a NULL shminfo here, but that has no bearing * on the setup of the XImage, except that ximage->obdata will * == NULL. */ dummy_image = XShmCreateImage(dpy, DefaultVisual(dpy, clutter_x11_get_default_screen()), priv->depth, ZPixmap, NULL, NULL, /* shminfo, */ priv->pixmap_width, priv->pixmap_height); if (!dummy_image) goto failed_image_create; priv->shminfo.shmid = shmget (IPC_PRIVATE, dummy_image->bytes_per_line * dummy_image->height, IPC_CREAT | 0777); if (priv->shminfo.shmid == -1) goto failed_shmget; priv->shminfo.shmaddr = shmat (priv->shminfo.shmid, 0, 0); if (priv->shminfo.shmaddr == (void *) -1) goto failed_shmat; priv->shminfo.readOnly = False; if (XShmAttach (dpy, &priv->shminfo) == 0) goto failed_xshmattach; if (clutter_x11_untrap_x_errors ()) g_warning ("X Error: Failed to setup XShm"); XDestroyImage (dummy_image); priv->have_shm = TRUE; return TRUE; failed_xshmattach: g_warning ("XShmAttach failed"); shmdt (priv->shminfo.shmaddr); failed_shmat: g_warning ("shmat failed"); shmctl (priv->shminfo.shmid, IPC_RMID, 0); failed_shmget: g_warning ("shmget failed"); XDestroyImage (dummy_image); failed_image_create: if (clutter_x11_untrap_x_errors ()) g_warning ("X Error: Failed to setup XShm"); priv->have_shm = FALSE; return FALSE; } static void check_for_pixmap_damage (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv = texture->priv; Display *dpy; XserverRegion parts; int i, r_count; XRectangle *r_damage; XRectangle r_bounds; clutter_x11_trap_x_errors (); /* * Retrieve the damaged region and break it down into individual * rectangles so we do not have to update the whole shebang. */ dpy = clutter_x11_get_default_display(); parts = XFixesCreateRegion (dpy, 0, 0); XDamageSubtract (dpy, priv->damage, None, parts); r_damage = XFixesFetchRegionAndBounds (dpy, parts, &r_count, &r_bounds); clutter_x11_untrap_x_errors (); if (r_damage) { for (i = 0; i < r_count; ++i) clutter_x11_texture_pixmap_update_area (texture, r_damage[i].x, r_damage[i].y, r_damage[i].width, r_damage[i].height); XFree (r_damage); } XFixesDestroyRegion (dpy, parts); } static ClutterX11FilterReturn on_x_event_filter (XEvent *xev, ClutterEvent *cev, gpointer data) { ClutterX11TexturePixmap *texture; ClutterX11TexturePixmapPrivate *priv; texture = CLUTTER_X11_TEXTURE_PIXMAP (data); g_return_val_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture), \ CLUTTER_X11_FILTER_CONTINUE); priv = texture->priv; if (xev->type == _damage_event_base + XDamageNotify) { XDamageNotifyEvent *dev = (XDamageNotifyEvent*)xev; if (dev->drawable != priv->damage_drawable) return CLUTTER_X11_FILTER_CONTINUE; check_for_pixmap_damage (texture); } return CLUTTER_X11_FILTER_CONTINUE; } static ClutterX11FilterReturn on_x_event_filter_too (XEvent *xev, ClutterEvent *cev, gpointer data) { ClutterX11TexturePixmap *texture; ClutterX11TexturePixmapPrivate *priv; texture = CLUTTER_X11_TEXTURE_PIXMAP (data); g_return_val_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture), \ CLUTTER_X11_FILTER_CONTINUE); priv = texture->priv; if (xev->xany.window != priv->window) return CLUTTER_X11_FILTER_CONTINUE; switch (xev->type) { case MapNotify: case ConfigureNotify: clutter_x11_texture_pixmap_sync_window (texture); break; case UnmapNotify: clutter_x11_texture_pixmap_set_mapped (texture, FALSE); break; case DestroyNotify: clutter_x11_texture_pixmap_destroyed (texture); break; default: break; } return CLUTTER_X11_FILTER_CONTINUE; } static void create_damage_resources (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; Display *dpy; priv = texture->priv; dpy = clutter_x11_get_default_display(); if (!priv->window && !priv->pixmap) return; clutter_x11_trap_x_errors (); if (priv->window) priv->damage_drawable = priv->window; else priv->damage_drawable = priv->pixmap; priv->damage = XDamageCreate (dpy, priv->damage_drawable, XDamageReportNonEmpty); /* Errors here might occur if the window is already destroyed, we * simply skip processing damage and assume that the texture pixmap * will be cleaned up by the app when it gets a DestroyNotify. */ XSync (dpy, FALSE); clutter_x11_untrap_x_errors (); if (priv->damage) clutter_x11_add_filter (on_x_event_filter, (gpointer)texture); } static void free_damage_resources (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; Display *dpy; priv = texture->priv; dpy = clutter_x11_get_default_display(); if (priv->damage) { clutter_x11_trap_x_errors (); XDamageDestroy (dpy, priv->damage); XSync (dpy, FALSE); clutter_x11_untrap_x_errors (); priv->damage = None; priv->damage_drawable = None; clutter_x11_remove_filter (on_x_event_filter, (gpointer)texture); } } static void clutter_x11_texture_pixmap_real_queue_damage_redraw ( ClutterX11TexturePixmap *texture, gint x, gint y, gint width, gint height) { ClutterActor *self = CLUTTER_ACTOR (texture); ClutterActorBox allocation; guint pixmap_width = 0; guint pixmap_height = 0; float scale_x; float scale_y; ClutterActorBox clip; /* NB: clutter_actor_queue_clipped_redraw expects a box in the actor's * coordinate space so we need to convert from pixmap coordinates to * actor coordinates... */ /* XXX: we don't care if we get an out of date allocation here because * clutter_actor_queue_clipped_redraw knows to ignore the clip if the * actor's allocation is invalid. * * This is noted because clutter_actor_get_allocation_box does some * unnecessary work to support buggy code with a comment suggesting that * it could be changed later which would be good for this use case! */ clutter_actor_get_allocation_box (self, &allocation); g_object_get (self, "pixmap-width", &pixmap_width, "pixmap-height", &pixmap_height, NULL); scale_x = (allocation.x2 - allocation.x1) / pixmap_width; scale_y = (allocation.y2 - allocation.y1) / pixmap_height; clip.x1 = x * scale_x; clip.y1 = y * scale_y; clip.x2 = clip.x1 + width * scale_x; clip.y2 = clip.y1 + height * scale_y; _clutter_actor_queue_redraw_with_clip (self, CLUTTER_REDRAW_CLIPPED_TO_BOX, &clip); } static void clutter_x11_texture_pixmap_init (ClutterX11TexturePixmap *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_X11_TYPE_TEXTURE_PIXMAP, ClutterX11TexturePixmapPrivate); g_signal_override_class_handler ( "queue-damage-redraw", CLUTTER_X11_TYPE_TEXTURE_PIXMAP, G_CALLBACK (clutter_x11_texture_pixmap_real_queue_damage_redraw)); if (!check_extensions (self)) { /* FIMXE: means display lacks needed extensions for at least auto. * - a _can_autoupdate() method ? */ } self->priv->image = NULL; self->priv->automatic_updates = FALSE; self->priv->damage = None; self->priv->damage_drawable = None; self->priv->window = None; self->priv->pixmap = None; self->priv->pixmap_height = 0; self->priv->pixmap_width = 0; self->priv->shminfo.shmid = -1; self->priv->window_redirect_automatic = TRUE; self->priv->window_mapped = FALSE; self->priv->destroyed = FALSE; self->priv->override_redirect = FALSE; self->priv->window_x = 0; self->priv->window_y = 0; } static void clutter_x11_texture_pixmap_dispose (GObject *object) { ClutterX11TexturePixmap *texture = CLUTTER_X11_TEXTURE_PIXMAP (object); ClutterX11TexturePixmapPrivate *priv = texture->priv; free_damage_resources (texture); clutter_x11_remove_filter (on_x_event_filter_too, (gpointer)texture); if (priv->owns_pixmap && priv->pixmap) { XFreePixmap (clutter_x11_get_default_display (), priv->pixmap); priv->pixmap = None; } if (priv->image) { XDestroyImage (priv->image); priv->image = NULL; } free_shm_resources (texture); G_OBJECT_CLASS (clutter_x11_texture_pixmap_parent_class)->dispose (object); } static void clutter_x11_texture_pixmap_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterX11TexturePixmap *texture = CLUTTER_X11_TEXTURE_PIXMAP (object); ClutterX11TexturePixmapPrivate *priv = texture->priv; switch (prop_id) { case PROP_PIXMAP: clutter_x11_texture_pixmap_set_pixmap (texture, g_value_get_ulong (value)); break; case PROP_AUTO: clutter_x11_texture_pixmap_set_automatic (texture, g_value_get_boolean (value)); break; case PROP_WINDOW: clutter_x11_texture_pixmap_set_window (texture, g_value_get_ulong (value), priv->window_redirect_automatic); break; case PROP_WINDOW_REDIRECT_AUTOMATIC: { gboolean new; new = g_value_get_boolean (value); /* Change the update mode.. */ if (new != priv->window_redirect_automatic && priv->window) clutter_x11_texture_pixmap_set_window (texture, priv->window, new); priv->window_redirect_automatic = new; } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_x11_texture_pixmap_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterX11TexturePixmap *texture = CLUTTER_X11_TEXTURE_PIXMAP (object); ClutterX11TexturePixmapPrivate *priv = texture->priv; switch (prop_id) { case PROP_PIXMAP: g_value_set_ulong (value, priv->pixmap); break; case PROP_PIXMAP_WIDTH: g_value_set_uint (value, priv->pixmap_width); break; case PROP_PIXMAP_HEIGHT: g_value_set_uint (value, priv->pixmap_height); break; case PROP_DEPTH: g_value_set_uint (value, priv->depth); break; case PROP_AUTO: g_value_set_boolean (value, priv->automatic_updates); break; case PROP_WINDOW: g_value_set_ulong (value, priv->window); break; case PROP_WINDOW_REDIRECT_AUTOMATIC: g_value_set_boolean (value, priv->window_redirect_automatic); break; case PROP_WINDOW_MAPPED: g_value_set_boolean (value, priv->window_mapped); break; case PROP_DESTROYED: g_value_set_boolean (value, priv->destroyed); break; case PROP_WINDOW_X: g_value_set_int (value, priv->window_x); break; case PROP_WINDOW_Y: g_value_set_int (value, priv->window_y); break; case PROP_WINDOW_OVERRIDE_REDIRECT: g_value_set_boolean (value, priv->override_redirect); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clutter_x11_texture_pixmap_realize (ClutterActor *actor) { ClutterX11TexturePixmap *texture = CLUTTER_X11_TEXTURE_PIXMAP (actor); ClutterX11TexturePixmapPrivate *priv = texture->priv; CLUTTER_ACTOR_CLASS (clutter_x11_texture_pixmap_parent_class)-> realize (actor); clutter_x11_texture_pixmap_update_area_real (texture, 0, 0, priv->pixmap_width, priv->pixmap_height); } static void clutter_x11_texture_pixmap_class_init (ClutterX11TexturePixmapClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); GParamSpec *pspec; ClutterBackend *default_backend; g_type_class_add_private (klass, sizeof (ClutterX11TexturePixmapPrivate)); object_class->dispose = clutter_x11_texture_pixmap_dispose; object_class->set_property = clutter_x11_texture_pixmap_set_property; object_class->get_property = clutter_x11_texture_pixmap_get_property; actor_class->realize = clutter_x11_texture_pixmap_realize; klass->update_area = clutter_x11_texture_pixmap_update_area_real; pspec = g_param_spec_ulong ("pixmap", "Pixmap", "The X11 Pixmap to be bound", 0, G_MAXULONG, None, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PIXMAP, pspec); pspec = g_param_spec_uint ("pixmap-width", "Pixmap width", "The width of the " "pixmap bound to this texture", 0, G_MAXUINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_PIXMAP_WIDTH, pspec); pspec = g_param_spec_uint ("pixmap-height", "Pixmap height", "The height of the " "pixmap bound to this texture", 0, G_MAXUINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_PIXMAP_HEIGHT, pspec); pspec = g_param_spec_uint ("pixmap-depth", "Pixmap Depth", "The depth (in number of bits) of the " "pixmap bound to this texture", 0, G_MAXUINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_DEPTH, pspec); pspec = g_param_spec_boolean ("automatic-updates", "Automatic Updates", "If the texture should be kept in " "sync with any pixmap changes.", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_AUTO, pspec); pspec = g_param_spec_ulong ("window", "Window", "The X11 Window to be bound", 0, G_MAXULONG, None, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_WINDOW, pspec); pspec = g_param_spec_boolean ("window-redirect-automatic", "Window Redirect Automatic", "If composite window redirects are set to " "Automatic (or Manual if false)", TRUE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_WINDOW_REDIRECT_AUTOMATIC, pspec); pspec = g_param_spec_boolean ("window-mapped", "Window Mapped", "If window is mapped", FALSE, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_WINDOW_MAPPED, pspec); pspec = g_param_spec_boolean ("destroyed", "Destroyed", "If window has been destroyed", FALSE, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_DESTROYED, pspec); pspec = g_param_spec_int ("window-x", "Window X", "X position of window on screen according to X11", G_MININT, G_MAXINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_WINDOW_X, pspec); pspec = g_param_spec_int ("window-y", "Window Y", "Y position of window on screen according to X11", G_MININT, G_MAXINT, 0, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_WINDOW_Y, pspec); pspec = g_param_spec_boolean ("window-override-redirect", "Window Override Redirect", "If this is an override-redirect window", FALSE, G_PARAM_READABLE); g_object_class_install_property (object_class, PROP_WINDOW_OVERRIDE_REDIRECT, pspec); /** * ClutterX11TexturePixmap::update-area: * @texture: the object which received the signal * * The ::hide signal is emitted to ask the texture to update its * content from its source pixmap. * * Since: 0.8 */ signals[UPDATE_AREA] = g_signal_new ("update-area", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ClutterX11TexturePixmapClass, \ update_area), NULL, NULL, clutter_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); /** * ClutterX11TexturePixmap::queue-damage-redraw * @texture: the object which received the signal * @x: The top left x position of the damage region * @y: The top left y position of the damage region * @width: The width of the damage region * @height: The height of the damage region * * ::queue-damage-redraw is emitted to notify that some sub-region of the * underlying pixmap has changed and you need to queue a * corresponding redraw for the actor. * * The default handler will queue a clipped redraw in response to * the damage, using the assumption that the pixmap is being painted * to a rectangle covering the transformed allocation of the actor. * If you sub-class and change the paint method so this isn't true * then you must also provide your own damage signal handler to * queue a redraw that blocks this default behaviour. * * Since: 1.2 */ signals[QUEUE_DAMAGE_REDRAW] = g_signal_new ("queue-damage-redraw", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, clutter_marshal_VOID__INT_INT_INT_INT, G_TYPE_NONE, 4, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); default_backend = clutter_get_default_backend (); if (!CLUTTER_IS_BACKEND_X11 (default_backend)) { g_critical ("ClutterX11TexturePixmap instanciated with a " "non-X11 backend"); return; } } static void clutter_x11_texture_pixmap_update_area_real (ClutterX11TexturePixmap *texture, gint x, gint y, gint width, gint height) { ClutterX11TexturePixmapPrivate *priv; Display *dpy; XImage *image; char *first_pixel; GError *error = NULL; guint bytes_per_line; char *data; gboolean data_allocated = FALSE; int err_code; if (!CLUTTER_ACTOR_IS_REALIZED (texture)) return; priv = texture->priv; dpy = clutter_x11_get_default_display(); if (!priv->pixmap) return; if (priv->shminfo.shmid == -1) try_alloc_shm (texture); clutter_x11_trap_x_errors (); if (priv->have_shm) { image = XShmCreateImage(dpy, DefaultVisual(dpy, clutter_x11_get_default_screen()), priv->depth, ZPixmap, NULL, &priv->shminfo, width, height); image->data = priv->shminfo.shmaddr; XShmGetImage (dpy, priv->pixmap, image, x, y, AllPlanes); first_pixel = image->data; } else { if (!priv->image) { priv->image = XGetImage (dpy, priv->pixmap, 0, 0, priv->pixmap_width, priv->pixmap_height, AllPlanes, ZPixmap); first_pixel = priv->image->data + priv->image->bytes_per_line * y + x * priv->image->bits_per_pixel/8; } else { XGetSubImage (dpy, priv->pixmap, x, y, width, height, AllPlanes, ZPixmap, priv->image, x, y); first_pixel = priv->image->data + priv->image->bytes_per_line * y + x * priv->image->bits_per_pixel/8; } image = priv->image; } XSync (dpy, FALSE); if ((err_code = clutter_x11_untrap_x_errors ())) { g_warning ("Failed to get XImage of pixmap: %lx, removing", priv->pixmap); /* safe to assume pixmap has gone away? - therefor reset */ clutter_x11_texture_pixmap_set_pixmap (texture, None); return; } if (priv->depth == 24) { guint xpos, ypos; for (ypos=0; yposbytes_per_line*ypos + xpos * 4; p[3] = 0xFF; } data = first_pixel; bytes_per_line = image->bytes_per_line; } else if (priv->depth == 16) { guint xpos, ypos; data = g_malloc (height * width * 4); data_allocated = TRUE; bytes_per_line = width * 4; for (ypos=0; yposbytes_per_line * ypos + xpos * 2; guint16 *src_pixel = (guint16 *)src_p; char *dst_p = data + bytes_per_line * ypos + xpos * 4; guint32 *dst_pixel = (guint32 *)dst_p; *dst_pixel = ((((*src_pixel << 3) & 0xf8) | ((*src_pixel >> 2) & 0x7)) | \ (((*src_pixel << 5) & 0xfc00) | ((*src_pixel >> 1) & 0x300)) | \ (((*src_pixel << 8) & 0xf80000) | ((*src_pixel << 3) & 0x70000))) | 0xff000000; } } else if (priv->depth == 32) { bytes_per_line = image->bytes_per_line; data = first_pixel; } else return; /* For debugging purposes, un comment to simply generate dummy * pixmap data. (A Green background and Blue cross) */ #if 0 { guint xpos, ypos; if (data_allocated) g_free (data); data_allocated = TRUE; data = g_malloc (width*height*4); bytes_per_line = width *4; for (ypos=0; ypos width/2 && xpos <= (width/2) + width/4) || (ypos > height/2 && ypos <= (height/2) + height/4)) *pixel=0xff0000ff; else *pixel=0xff00ff00; } } #endif if (x != 0 || y != 0 || width != priv->pixmap_width || height != priv->pixmap_height) clutter_texture_set_area_from_rgb_data (CLUTTER_TEXTURE (texture), (guint8 *)data, TRUE, x, y, width, height, bytes_per_line, 4, CLUTTER_TEXTURE_RGB_FLAG_BGR | CLUTTER_TEXTURE_RGB_FLAG_PREMULT, &error); else clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (texture), (guint8 *)data, TRUE, width, height, bytes_per_line, 4, CLUTTER_TEXTURE_RGB_FLAG_BGR | CLUTTER_TEXTURE_RGB_FLAG_PREMULT, &error); if (error) { g_warning ("Error when uploading from pixbuf: %s", error->message); g_error_free (error); } if (data_allocated) g_free (data); if (priv->have_shm) XFree (image); } /** * clutter_x11_texture_pixmap_new: * * Creates a new #ClutterX11TexturePixmap which can be used to display the * contents of an X11 Pixmap inside a Clutter scene graph * * Return value: A new #ClutterX11TexturePixmap * * Since: 0.8 */ ClutterActor * clutter_x11_texture_pixmap_new (void) { ClutterActor *actor; actor = g_object_new (CLUTTER_X11_TYPE_TEXTURE_PIXMAP, NULL); return actor; } /** * clutter_x11_texture_pixmap_new_with_pixmap: * @pixmap: the X Pixmap to which this texture should be bound * * Creates a new #ClutterX11TexturePixmap for @pixmap * * Return value: A new #ClutterX11TexturePixmap bound to the given X Pixmap * * Since: 0.8 */ ClutterActor * clutter_x11_texture_pixmap_new_with_pixmap (Pixmap pixmap) { ClutterActor *actor; actor = g_object_new (CLUTTER_X11_TYPE_TEXTURE_PIXMAP, "pixmap", pixmap, NULL); return actor; } /** * clutter_x11_texture_pixmap_new_with_window: * @window: the X window to which this texture should be bound * * Creates a new #ClutterX11TexturePixmap for @window * * Return value: A new #ClutterX11TexturePixmap bound to the given X window. * * Since: 0.8 **/ ClutterActor * clutter_x11_texture_pixmap_new_with_window (Window window) { ClutterActor *actor; actor = g_object_new (CLUTTER_X11_TYPE_TEXTURE_PIXMAP, "window", window, NULL); return actor; } /** * clutter_x11_texture_pixmap_set_pixmap: * @texture: the texture to bind * @pixmap: the X Pixmap to which the texture should be bound * * Sets the X Pixmap to which the texture should be bound. * * Since: 0.8 */ void clutter_x11_texture_pixmap_set_pixmap (ClutterX11TexturePixmap *texture, Pixmap pixmap) { Window root; int x, y; unsigned int width, height, border_width, depth; Status status = 0; gboolean new_pixmap = FALSE, new_pixmap_width = FALSE; gboolean new_pixmap_height = FALSE, new_pixmap_depth = FALSE; ClutterX11TexturePixmapPrivate *priv; g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture)); priv = texture->priv; clutter_x11_trap_x_errors (); status = XGetGeometry (clutter_x11_get_default_display(), (Drawable)pixmap, &root, &x, &y, &width, &height, &border_width, &depth); if (clutter_x11_untrap_x_errors () || status == 0) { if (pixmap != None) g_warning ("Unable to query pixmap: %lx", pixmap); pixmap = None; width = height = depth = 0; } if (priv->image) { XDestroyImage (priv->image); priv->image = NULL; } if (priv->pixmap != pixmap) { if (priv->pixmap && priv->owns_pixmap) XFreePixmap (clutter_x11_get_default_display (), priv->pixmap); priv->pixmap = pixmap; new_pixmap = TRUE; /* The damage object is created on the window if there is any * but if there is no window, then we create it directly on * the pixmap, so it needs to be recreated with a change in * pixmap. */ if (priv->automatic_updates && new_pixmap && !priv->window) { free_damage_resources (texture); create_damage_resources (texture); } } if (priv->pixmap_width != width) { priv->pixmap_width = width; new_pixmap_width = TRUE; } if (priv->pixmap_height != height) { priv->pixmap_height = height; new_pixmap_height = TRUE; } if (priv->depth != depth) { priv->depth = depth; new_pixmap_depth = TRUE; } /* NB: We defer sending the signals until updating all the * above members so the values are all available to the * signal handlers. */ g_object_ref (texture); if (new_pixmap) g_object_notify (G_OBJECT (texture), "pixmap"); if (new_pixmap_width) g_object_notify (G_OBJECT (texture), "pixmap-width"); if (new_pixmap_height) g_object_notify (G_OBJECT (texture), "pixmap-height"); if (new_pixmap_depth) g_object_notify (G_OBJECT (texture), "pixmap-depth"); free_shm_resources (texture); if (priv->depth != 0 && priv->pixmap != None && priv->pixmap_width != 0 && priv->pixmap_height != 0) { if (CLUTTER_ACTOR_IS_REALIZED (texture)) clutter_x11_texture_pixmap_update_area (texture, 0, 0, priv->pixmap_width, priv->pixmap_height); } /* * Keep ref until here in case a notify causes removal from the scene; can't * lower the notifies because glx's notify handler needs to run before * update_area */ g_object_unref (texture); } /** * clutter_x11_texture_pixmap_set_window: * @texture: the texture to bind * @window: the X window to which the texture should be bound * @automatic: %TRUE for automatic window updates, %FALSE for manual. * * Sets up a suitable pixmap for the window, using the composite and damage * extensions if possible, and then calls * clutter_x11_texture_pixmap_set_pixmap(). * * If you want to display a window in a #ClutterTexture, you probably want * this function, or its older sister, clutter_glx_texture_pixmap_set_window() * * Since: 0.8 */ void clutter_x11_texture_pixmap_set_window (ClutterX11TexturePixmap *texture, Window window, gboolean automatic) { ClutterX11TexturePixmapPrivate *priv; XWindowAttributes attr; Display *dpy = clutter_x11_get_default_display (); g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture)); priv = texture->priv; if (!clutter_x11_has_composite_extension()) return; if (priv->window == window && automatic == priv->window_redirect_automatic) return; if (priv->window) { clutter_x11_remove_filter (on_x_event_filter_too, (gpointer)texture); clutter_x11_trap_x_errors (); XCompositeUnredirectWindow(clutter_x11_get_default_display (), priv->window, priv->window_redirect_automatic ? CompositeRedirectAutomatic : CompositeRedirectManual); XSync (clutter_x11_get_default_display (), False); clutter_x11_untrap_x_errors (); } priv->window = window; priv->window_redirect_automatic = automatic; priv->window_mapped = FALSE; priv->destroyed = FALSE; if (window == None) return; clutter_x11_trap_x_errors (); { if (!XGetWindowAttributes (dpy, window, &attr)) { XSync (dpy, False); clutter_x11_untrap_x_errors (); g_warning ("bad window 0x%x", (guint32)window); priv->window = None; return; } XCompositeRedirectWindow (dpy, window, automatic ? CompositeRedirectAutomatic : CompositeRedirectManual); XSync (dpy, False); } clutter_x11_untrap_x_errors (); if (priv->window) { XSelectInput (dpy, priv->window, attr.your_event_mask | StructureNotifyMask); clutter_x11_add_filter (on_x_event_filter_too, (gpointer)texture); } if (priv->automatic_updates) { free_damage_resources (texture); create_damage_resources (texture); } g_object_ref (texture); g_object_notify (G_OBJECT (texture), "window"); clutter_x11_texture_pixmap_set_mapped (texture, attr.map_state == IsViewable); clutter_x11_texture_pixmap_sync_window (texture); g_object_unref (texture); } /** * clutter_x11_texture_pixmap_sync_window: * @texture: the texture to bind * * Resets the texture's pixmap from its window, perhaps in response to the * pixmap's invalidation as the window changed size. * * Since: 0.8 */ void clutter_x11_texture_pixmap_sync_window (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; Pixmap pixmap; g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture)); priv = texture->priv; if (priv->destroyed) return; if (!clutter_x11_has_composite_extension()) { clutter_x11_texture_pixmap_set_pixmap (texture, priv->window); return; } if (priv->window) { XWindowAttributes attr; Display *dpy = clutter_x11_get_default_display (); gboolean mapped = FALSE; gboolean notify_x = FALSE; gboolean notify_y = FALSE; gboolean notify_override_redirect = FALSE; Status status; /* NB: It's only valid to name a pixmap if the window is viewable. * * We don't explicitly check this though since there would be a race * between checking and naming unless we use a server grab which is * undesireable. * * Instead we gracefully handle any error with naming the pixmap. */ clutter_x11_trap_x_errors (); pixmap = XCompositeNameWindowPixmap (dpy, priv->window); status = XGetWindowAttributes (dpy, priv->window, &attr); if (status != 0) { notify_x = attr.x != priv->window_x; notify_y = attr.y != priv->window_y; notify_override_redirect = attr.override_redirect != priv->override_redirect; priv->window_x = attr.x; priv->window_y = attr.y; priv->override_redirect = attr.override_redirect; } /* We rely on XGetWindowAttributes() implicitly syncing with the server * so if there was an error naming the pixmap that will have been * caught by now. */ if (clutter_x11_untrap_x_errors ()) pixmap = None; g_object_ref (texture); /* guard against unparent */ if (pixmap) { clutter_x11_texture_pixmap_set_pixmap (texture, pixmap); priv->owns_pixmap = TRUE; } clutter_x11_texture_pixmap_set_mapped (texture, mapped); /* could do more clever things with a signal, i guess.. */ if (notify_override_redirect) g_object_notify (G_OBJECT (texture), "window-override-redirect"); if (notify_x) g_object_notify (G_OBJECT (texture), "window-x"); if (notify_y) g_object_notify (G_OBJECT (texture), "window-y"); g_object_unref (texture); } } static void clutter_x11_texture_pixmap_set_mapped (ClutterX11TexturePixmap *texture, gboolean mapped) { ClutterX11TexturePixmapPrivate *priv; priv = texture->priv; if (mapped != priv->window_mapped) { priv->window_mapped = mapped; g_object_notify (G_OBJECT (texture), "window-mapped"); } } static void clutter_x11_texture_pixmap_destroyed (ClutterX11TexturePixmap *texture) { ClutterX11TexturePixmapPrivate *priv; priv = texture->priv; if (!priv->destroyed) { priv->destroyed = TRUE; g_object_notify (G_OBJECT (texture), "destroyed"); } /* * Don't set window to None, that would destroy the pixmap, which might still * be useful e.g. for destroy animations -- app's responsibility. */ } /** * clutter_x11_texture_pixmap_update_area: * @texture: The texture whose content shall be updated. * @x: the X coordinate of the area to update * @y: the Y coordinate of the area to update * @width: the width of the area to update * @height: the height of the area to update * * Performs the actual binding of texture to the current content of * the pixmap. Can be called to update the texture if the pixmap * content has changed. * * Since: 0.8 **/ void clutter_x11_texture_pixmap_update_area (ClutterX11TexturePixmap *texture, gint x, gint y, gint width, gint height) { g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture)); g_signal_emit (texture, signals[UPDATE_AREA], 0, x, y, width, height); /* The default handler for the "queue-damage-redraw" signal is * clutter_x11_texture_pixmap_real_queue_damage_redraw which will queue a * clipped redraw. */ g_signal_emit (texture, signals[QUEUE_DAMAGE_REDRAW], 0, x, y, width, height); } /** * clutter_x11_texture_pixmap_set_automatic: * @texture: a #ClutterX11TexturePixmap * @setting: %TRUE to enable automatic updates * * Enables or disables the automatic updates ot @texture in case the backing * pixmap or window is damaged * * Since: 0.8 */ void clutter_x11_texture_pixmap_set_automatic (ClutterX11TexturePixmap *texture, gboolean setting) { ClutterX11TexturePixmapPrivate *priv; Display *dpy; g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture)); priv = texture->priv; if (setting == priv->automatic_updates) return; dpy = clutter_x11_get_default_display(); if (setting) create_damage_resources (texture); else free_damage_resources (texture); priv->automatic_updates = setting; }