/* Clutter. * An OpenGL based 'interactive canvas' library. * Authored By Matthew Allum * Copyright (C) 2006-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, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-backend-x11.h" #include "clutter-stage-x11.h" #include "clutter-x11.h" #include "../clutter-stage-window.h" #include "../clutter-main.h" #include "../clutter-feature.h" #include "../clutter-color.h" #include "../clutter-util.h" #include "../clutter-event.h" #include "../clutter-enum-types.h" #include "../clutter-private.h" #include "../clutter-debug.h" #include "../clutter-units.h" #include "cogl/cogl.h" #ifdef HAVE_XFIXES #include #endif static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface); G_DEFINE_TYPE_WITH_CODE (ClutterStageX11, clutter_stage_x11, CLUTTER_TYPE_GROUP, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW, clutter_stage_window_iface_init)); #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ #define _NET_WM_STATE_ADD 1 /* add/set property */ #define _NET_WM_STATE_TOGGLE 2 /* toggle property */ static void send_wmspec_change_state (ClutterBackendX11 *backend_x11, Window window, Atom state, gboolean add) { XClientMessageEvent xclient; memset (&xclient, 0, sizeof (xclient)); xclient.type = ClientMessage; xclient.window = window; xclient.message_type = backend_x11->atom_NET_WM_STATE; xclient.format = 32; xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE; xclient.data.l[1] = state; xclient.data.l[2] = 0; xclient.data.l[3] = 0; xclient.data.l[4] = 0; XSendEvent (backend_x11->xdpy, DefaultRootWindow(backend_x11->xdpy), False, SubstructureRedirectMask|SubstructureNotifyMask, (XEvent *)&xclient); } void clutter_stage_x11_fix_window_size (ClutterStageX11 *stage_x11) { gboolean resize; resize = clutter_stage_get_user_resizable (stage_x11->wrapper); if (stage_x11->xwin != None && stage_x11->is_foreign_xwin == FALSE) { XSizeHints *size_hints; ClutterUnit min_width, min_height; size_hints = XAllocSizeHints(); clutter_actor_get_preferred_width (CLUTTER_ACTOR (stage_x11), -1, &min_width, NULL); clutter_actor_get_preferred_height (CLUTTER_ACTOR (stage_x11), min_width, &min_height, NULL); size_hints->min_width = CLUTTER_UNITS_TO_DEVICE (min_width); size_hints->min_height = CLUTTER_UNITS_TO_DEVICE (min_height); size_hints->flags = PMinSize; if (!resize) { size_hints->max_width = size_hints->min_width; size_hints->max_height = size_hints->min_height; size_hints->flags |= PMaxSize; } XSetWMNormalHints (stage_x11->xdpy, stage_x11->xwin, size_hints); XFree(size_hints); } } static void clutter_stage_x11_show (ClutterActor *actor) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (actor); CLUTTER_ACTOR_CLASS (clutter_stage_x11_parent_class)->show (actor); if (stage_x11->xwin) { /* Fire off a redraw to avoid flicker on first map. * Appears not to work perfectly on intel drivers at least. */ clutter_redraw (stage_x11->wrapper); XSync (stage_x11->xdpy, FALSE); XMapWindow (stage_x11->xdpy, stage_x11->xwin); } } static void clutter_stage_x11_hide (ClutterActor *actor) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (actor); if (stage_x11->xwin) XUnmapWindow (stage_x11->xdpy, stage_x11->xwin); } void clutter_stage_x11_set_wm_protocols (ClutterStageX11 *stage_x11) { ClutterBackendX11 *backend_x11 = stage_x11->backend; Atom protocols[2]; int n = 0; protocols[n++] = backend_x11->atom_WM_DELETE_WINDOW; protocols[n++] = backend_x11->atom_NET_WM_PING; XSetWMProtocols (stage_x11->xdpy, stage_x11->xwin, protocols, n); } static void clutter_stage_x11_get_preferred_width (ClutterActor *self, ClutterUnit for_height, ClutterUnit *min_width_p, ClutterUnit *natural_width_p) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (self); gboolean resize; if (stage_x11->fullscreen_on_map) { int width; width = DisplayWidth (stage_x11->xdpy, stage_x11->xscreen); if (min_width_p) *min_width_p = CLUTTER_UNITS_FROM_DEVICE (width); if (natural_width_p) *natural_width_p = CLUTTER_UNITS_FROM_DEVICE (width); return; } resize = clutter_stage_get_user_resizable (stage_x11->wrapper); if (min_width_p) { /* FIXME need API to set this */ if (resize) *min_width_p = CLUTTER_UNITS_FROM_DEVICE (1); else *min_width_p = CLUTTER_UNITS_FROM_DEVICE (stage_x11->xwin_width); } if (natural_width_p) *natural_width_p = CLUTTER_UNITS_FROM_DEVICE (stage_x11->xwin_width); } static void clutter_stage_x11_get_preferred_height (ClutterActor *self, ClutterUnit for_width, ClutterUnit *min_height_p, ClutterUnit *natural_height_p) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (self); gboolean resize; if (stage_x11->fullscreen_on_map) { int height; height = DisplayHeight (stage_x11->xdpy, stage_x11->xscreen); if (min_height_p) *min_height_p = CLUTTER_UNITS_FROM_DEVICE (height); if (natural_height_p) *natural_height_p = CLUTTER_UNITS_FROM_DEVICE (height); return; } resize = clutter_stage_get_user_resizable (stage_x11->wrapper); if (min_height_p) { if (resize) *min_height_p = CLUTTER_UNITS_FROM_DEVICE (1); /* FIXME need API * to set this */ else *min_height_p = CLUTTER_UNITS_FROM_DEVICE (stage_x11->xwin_height); } if (natural_height_p) *natural_height_p = CLUTTER_UNITS_FROM_DEVICE (stage_x11->xwin_height); } static void clutter_stage_x11_allocate (ClutterActor *self, const ClutterActorBox *box, gboolean origin_changed) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (self); ClutterActorClass *parent_class; gint new_width, new_height; new_width = ABS (CLUTTER_UNITS_TO_INT (box->x2 - box->x1)); new_height = ABS (CLUTTER_UNITS_TO_INT (box->y2 - box->y1)); if (new_width == 0 || new_height == 0) { /* Should not happen, if this turns up we need to debug it and * determine the cleanest way to fix. */ g_warning ("X11 stage not allowed to have 0 width or height"); new_width = 1; new_height = 1; } if (new_width != stage_x11->xwin_width || new_height != stage_x11->xwin_height) { stage_x11->xwin_width = new_width; stage_x11->xwin_height = new_height; if (stage_x11->xwin != None && !stage_x11->is_foreign_xwin) { CLUTTER_NOTE (BACKEND, "%s: XResizeWindow[%x] (%d, %d)", G_STRLOC, (unsigned int) stage_x11->xwin, stage_x11->xwin_width, stage_x11->xwin_height); XResizeWindow (stage_x11->xdpy, stage_x11->xwin, stage_x11->xwin_width, stage_x11->xwin_height); } clutter_stage_x11_fix_window_size (stage_x11); if (stage_x11->xpixmap != None) { /* Need to recreate to resize */ clutter_actor_unrealize (self); clutter_actor_realize (self); } } /* chain up to fill in actor->priv->allocation */ parent_class = CLUTTER_ACTOR_CLASS (clutter_stage_x11_parent_class); parent_class->allocate (self, box, origin_changed); } static inline void set_wm_title (ClutterStageX11 *stage_x11) { ClutterBackendX11 *backend_x11 = stage_x11->backend; if (stage_x11->xwin == None) return; if (stage_x11->title == NULL) { XDeleteProperty (stage_x11->xdpy, stage_x11->xwin, backend_x11->atom_NET_WM_NAME); } else { XChangeProperty (stage_x11->xdpy, stage_x11->xwin, backend_x11->atom_NET_WM_NAME, backend_x11->atom_UTF8_STRING, 8, PropModeReplace, (unsigned char *) stage_x11->title, (int) strlen (stage_x11->title)); } } static inline void set_cursor_visible (ClutterStageX11 *stage_x11) { if (stage_x11->xwin == None) return; CLUTTER_NOTE (BACKEND, "setting cursor state ('%s') over stage window (%u)", stage_x11->is_cursor_visible ? "visible" : "invisible", (unsigned int) stage_x11->xwin); if (stage_x11->is_cursor_visible) { #if 0 /* HAVE_XFIXES - seems buggy/unreliable */ XFixesShowCursor (stage_x11->xdpy, stage_x11->xwin); #else XUndefineCursor (stage_x11->xdpy, stage_x11->xwin); #endif /* HAVE_XFIXES */ } else { #if 0 /* HAVE_XFIXES - seems buggy/unreliable, check cursor in firefox * loading page after hiding. */ XFixesHideCursor (stage_x11->xdpy, stage_x11->xwin); #else XColor col; Pixmap pix; Cursor curs; pix = XCreatePixmap (stage_x11->xdpy, stage_x11->xwin, 1, 1, 1); memset (&col, 0, sizeof (col)); curs = XCreatePixmapCursor (stage_x11->xdpy, pix, pix, &col, &col, 1, 1); XFreePixmap (stage_x11->xdpy, pix); XDefineCursor (stage_x11->xdpy, stage_x11->xwin, curs); #endif /* HAVE_XFIXES */ } } static void clutter_stage_x11_realize (ClutterActor *actor) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (actor); set_wm_title (stage_x11); set_cursor_visible (stage_x11); } static void clutter_stage_x11_set_fullscreen (ClutterStageWindow *stage_window, gboolean is_fullscreen) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); ClutterBackendX11 *backend_x11 = stage_x11->backend; ClutterStage *stage = stage_x11->wrapper; static gboolean was_resizeable = FALSE; if (!stage) return; CLUTTER_SET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_SYNC_MATRICES); if (is_fullscreen) { int width, height; width = DisplayWidth (stage_x11->xdpy, stage_x11->xscreen); height = DisplayHeight (stage_x11->xdpy, stage_x11->xscreen); /* we force the stage to the screen size here, in order to * get the fullscreen stage size right after the call to * clutter_stage_fullscreen(). XXX this might break in case * the stage is not fullscreened, but if that does not happen * we are massively screwed anyway */ stage_x11->xwin_width = width; stage_x11->xwin_height = height; clutter_actor_set_size (CLUTTER_ACTOR (stage), width, height); if (stage_x11->xwin != None) { stage_x11->fullscreen_on_map = TRUE; /* if the actor is not mapped we resize the stage window to match * the size of the screen; this is useful for e.g. EGLX to avoid * a resize when calling clutter_stage_fullscreen() before showing * the stage */ if (!CLUTTER_ACTOR_IS_MAPPED (stage_x11)) { /* FIXME: This wont work if we support more states */ XChangeProperty (stage_x11->xdpy, stage_x11->xwin, backend_x11->atom_NET_WM_STATE, XA_ATOM, 32, PropModeReplace, (unsigned char *) &backend_x11->atom_NET_WM_STATE_FULLSCREEN, 1); } else { /* We need to set window user resize-able for metacity at * at least to allow the window to fullscreen *sigh* */ if (clutter_stage_get_user_resizable (stage) == TRUE) was_resizeable = TRUE; else clutter_stage_set_user_resizable (stage, TRUE); send_wmspec_change_state (backend_x11, stage_x11->xwin, backend_x11->atom_NET_WM_STATE_FULLSCREEN, TRUE); } } } else { if (stage_x11->xwin != None) { if (!CLUTTER_ACTOR_IS_MAPPED (stage_x11)) { /* FIXME: This wont work if we support more states */ XDeleteProperty (stage_x11->xdpy, stage_x11->xwin, backend_x11->atom_NET_WM_STATE); } else { clutter_stage_set_user_resizable (stage, TRUE); send_wmspec_change_state (backend_x11, stage_x11->xwin, backend_x11->atom_NET_WM_STATE_FULLSCREEN, FALSE); /* reset the windows state - this isn't fun - see above */ if (!was_resizeable) clutter_stage_set_user_resizable (stage, FALSE); was_resizeable = FALSE; } stage_x11->fullscreen_on_map = FALSE; } } } static void clutter_stage_x11_set_cursor_visible (ClutterStageWindow *stage_window, gboolean cursor_visible) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); stage_x11->is_cursor_visible = (cursor_visible == TRUE); set_cursor_visible (stage_x11); } static void clutter_stage_x11_set_title (ClutterStageWindow *stage_window, const gchar *title) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); g_free (stage_x11->title); stage_x11->title = g_strdup (title); set_wm_title (stage_x11); } static void clutter_stage_x11_set_user_resizable (ClutterStageWindow *stage_window, gboolean is_resizable) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window); clutter_stage_x11_fix_window_size (stage_x11); } static ClutterActor * clutter_stage_x11_get_wrapper (ClutterStageWindow *stage_window) { return CLUTTER_ACTOR (CLUTTER_STAGE_X11 (stage_window)->wrapper); } static void clutter_stage_x11_finalize (GObject *gobject) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject); g_free (stage_x11->title); G_OBJECT_CLASS (clutter_stage_x11_parent_class)->finalize (gobject); } static void clutter_stage_x11_dispose (GObject *gobject) { ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject); if (stage_x11->xwin) clutter_actor_unrealize (CLUTTER_ACTOR (stage_x11)); G_OBJECT_CLASS (clutter_stage_x11_parent_class)->dispose (gobject); } static void clutter_stage_x11_class_init (ClutterStageX11Class *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); gobject_class->finalize = clutter_stage_x11_finalize; gobject_class->dispose = clutter_stage_x11_dispose; actor_class->realize = clutter_stage_x11_realize; actor_class->show = clutter_stage_x11_show; actor_class->hide = clutter_stage_x11_hide; actor_class->get_preferred_width = clutter_stage_x11_get_preferred_width; actor_class->get_preferred_height = clutter_stage_x11_get_preferred_height; actor_class->allocate = clutter_stage_x11_allocate; } static void clutter_stage_x11_init (ClutterStageX11 *stage) { stage->xdpy = NULL; stage->xwin_root = None; stage->xscreen = 0; stage->xwin = None; stage->xwin_width = 640; stage->xwin_height = 480; stage->xvisinfo = None; stage->is_foreign_xwin = FALSE; stage->fullscreen_on_map = FALSE; stage->is_cursor_visible = TRUE; stage->title = NULL; stage->wrapper = NULL; CLUTTER_SET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_IS_TOPLEVEL); } static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface) { iface->get_wrapper = clutter_stage_x11_get_wrapper; iface->set_title = clutter_stage_x11_set_title; iface->set_fullscreen = clutter_stage_x11_set_fullscreen; iface->set_cursor_visible = clutter_stage_x11_set_cursor_visible; iface->set_user_resizable = clutter_stage_x11_set_user_resizable; } /** * clutter_x11_get_stage_window: * @stage: a #ClutterStage * * Gets the stages X Window. * * Return value: An XID for the stage window. * * Since: 0.4 */ Window clutter_x11_get_stage_window (ClutterStage *stage) { ClutterStageWindow *impl; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), None); impl = _clutter_stage_get_window (stage); g_assert (CLUTTER_IS_STAGE_X11 (impl)); return CLUTTER_STAGE_X11 (impl)->xwin; } /** * clutter_x11_get_stage_from_window: * @win: an X Window ID * * Gets the stage for a particular X window. * * Return value: The stage or NULL if a stage does not exist for the window. * * Since: 0.8 */ ClutterStage * clutter_x11_get_stage_from_window (Window win) { ClutterMainContext *context; ClutterStageManager *stage_manager; GSList *l; context = clutter_context_get_default (); stage_manager = context->stage_manager; /* FIXME: use a hash here for performance resaon */ for (l = stage_manager->stages; l; l = l->next) { ClutterStage *stage = l->data; ClutterStageWindow *impl; impl = _clutter_stage_get_window (stage); g_assert (CLUTTER_IS_STAGE_X11 (impl)); if (CLUTTER_STAGE_X11 (impl)->xwin == win) return stage; } return NULL; } /** * clutter_x11_get_stage_visual: * @stage: a #ClutterStage * * Returns the stage XVisualInfo * * Return value: The XVisualInfo for the stage. * * Since: 0.4 */ XVisualInfo * clutter_x11_get_stage_visual (ClutterStage *stage) { ClutterStageWindow *impl; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL); impl = _clutter_stage_get_window (stage); g_assert (CLUTTER_IS_STAGE_X11 (impl)); return CLUTTER_STAGE_X11 (impl)->xvisinfo; } /** * clutter_x11_set_stage_foreign: * @stage: a #ClutterStage * @xwindow: an existing X Window id * * Target the #ClutterStage to use an existing external X Window * * Return value: %TRUE if foreign window is valid * * Since: 0.4 */ gboolean clutter_x11_set_stage_foreign (ClutterStage *stage, Window xwindow) { ClutterStageX11 *stage_x11; ClutterStageWindow *impl; ClutterActor *actor; gint x, y; guint width, height, border, depth; Window root_return; Status status; ClutterGeometry geom; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); g_return_val_if_fail (xwindow != None, FALSE); actor = CLUTTER_ACTOR (stage); impl = _clutter_stage_get_window (stage); stage_x11 = CLUTTER_STAGE_X11 (impl); clutter_x11_trap_x_errors (); status = XGetGeometry (stage_x11->xdpy, xwindow, &root_return, &x, &y, &width, &height, &border, &depth); if (clutter_x11_untrap_x_errors () || !status || width == 0 || height == 0 || depth != stage_x11->xvisinfo->depth) { g_warning ("Unable to retrieve the new window geometry"); return FALSE; } clutter_actor_unrealize (actor); CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)", (int) xwindow); stage_x11->xwin = xwindow; stage_x11->is_foreign_xwin = TRUE; geom.x = x; geom.y = y; geom.width = stage_x11->xwin_width = width; geom.height = stage_x11->xwin_height = height; clutter_actor_set_geometry (actor, &geom); clutter_actor_realize (actor); CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_ACTOR_SYNC_MATRICES); return TRUE; } void clutter_stage_x11_map (ClutterStageX11 *stage_x11) { /* set the mapped flag on the implementation */ CLUTTER_ACTOR_SET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED); /* and on the wrapper itself */ CLUTTER_ACTOR_SET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED); if (stage_x11->fullscreen_on_map) clutter_stage_fullscreen (CLUTTER_STAGE (stage_x11->wrapper)); else clutter_stage_unfullscreen (CLUTTER_STAGE (stage_x11->wrapper)); clutter_actor_queue_relayout (CLUTTER_ACTOR (stage_x11->wrapper)); } void clutter_stage_x11_unmap (ClutterStageX11 *stage_x11) { /* like above, unset the MAPPED stage on both the implementation and * the wrapper */ CLUTTER_ACTOR_UNSET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED); CLUTTER_ACTOR_UNSET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED); }