2 * An OpenGL based 'interactive canvas' library.
3 * Authored By Matthew Allum <mallum@openedhand.com>
4 * Copyright (C) 2006-2007 OpenedHand
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
30 #include <cogl/cogl.h>
32 #include "clutter-backend-x11.h"
33 #include "clutter-stage-x11.h"
34 #include "clutter-x11.h"
36 #include "clutter-actor-private.h"
37 #include "clutter-debug.h"
38 #include "clutter-device-manager-private.h"
39 #include "clutter-enum-types.h"
40 #include "clutter-event-translator.h"
41 #include "clutter-event-private.h"
42 #include "clutter-feature.h"
43 #include "clutter-main.h"
44 #include "clutter-paint-volume-private.h"
45 #include "clutter-private.h"
46 #include "clutter-stage-private.h"
49 #include <X11/extensions/Xfixes.h>
52 #define STAGE_X11_IS_MAPPED(s) ((((ClutterStageX11 *) (s))->wm_state & STAGE_X11_WITHDRAWN) == 0)
54 static ClutterStageWindowIface *clutter_stage_window_parent_iface = NULL;
56 static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
57 static void clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface);
59 static ClutterStageCogl *clutter_x11_get_stage_window_from_window (Window win);
61 static GHashTable *clutter_stages_by_xid = NULL;
63 #define clutter_stage_x11_get_type _clutter_stage_x11_get_type
65 G_DEFINE_TYPE_WITH_CODE (ClutterStageX11,
67 CLUTTER_TYPE_STAGE_COGL,
68 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
69 clutter_stage_window_iface_init)
70 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_EVENT_TRANSLATOR,
71 clutter_event_translator_iface_init));
73 #define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
74 #define _NET_WM_STATE_ADD 1 /* add/set property */
75 #define _NET_WM_STATE_TOGGLE 2 /* toggle property */
78 send_wmspec_change_state (ClutterBackendX11 *backend_x11,
83 XClientMessageEvent xclient;
85 CLUTTER_NOTE (BACKEND, "%s NET_WM state", add ? "adding" : "removing");
87 memset (&xclient, 0, sizeof (xclient));
89 xclient.type = ClientMessage;
90 xclient.window = window;
91 xclient.message_type = backend_x11->atom_NET_WM_STATE;
94 xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
95 xclient.data.l[1] = state;
96 xclient.data.l[2] = 0;
97 xclient.data.l[3] = 0;
98 xclient.data.l[4] = 0;
100 XSendEvent (backend_x11->xdpy,
101 DefaultRootWindow (backend_x11->xdpy),
103 SubstructureRedirectMask | SubstructureNotifyMask,
108 update_state (ClutterStageX11 *stage_x11,
109 ClutterBackendX11 *backend_x11,
115 /* FIXME: This wont work if we support more states */
116 XChangeProperty (backend_x11->xdpy,
118 backend_x11->atom_NET_WM_STATE, XA_ATOM, 32,
120 (unsigned char *) state, 1);
124 /* FIXME: This wont work if we support more states */
125 XDeleteProperty (backend_x11->xdpy,
127 backend_x11->atom_NET_WM_STATE);
132 clutter_stage_x11_fix_window_size (ClutterStageX11 *stage_x11,
136 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
137 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
139 if (stage_x11->xwin != None && !stage_x11->is_foreign_xwin)
141 guint min_width, min_height;
142 XSizeHints *size_hints;
145 resize = clutter_stage_get_user_resizable (stage_cogl->wrapper);
147 size_hints = XAllocSizeHints();
149 clutter_stage_get_minimum_size (stage_cogl->wrapper,
154 new_width = min_width;
157 new_height = min_height;
159 size_hints->flags = 0;
161 /* If we are going fullscreen then we don't want any
162 restrictions on the window size */
163 if (!stage_x11->fullscreening)
167 size_hints->min_width = min_width;
168 size_hints->min_height = min_height;
169 size_hints->flags = PMinSize;
173 size_hints->min_width = new_width;
174 size_hints->min_height = new_height;
175 size_hints->max_width = new_width;
176 size_hints->max_height = new_height;
177 size_hints->flags = PMinSize | PMaxSize;
181 XSetWMNormalHints (backend_x11->xdpy, stage_x11->xwin, size_hints);
188 clutter_stage_x11_set_wm_protocols (ClutterStageX11 *stage_x11)
190 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
191 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
195 protocols[n++] = backend_x11->atom_WM_DELETE_WINDOW;
196 protocols[n++] = backend_x11->atom_NET_WM_PING;
198 XSetWMProtocols (backend_x11->xdpy, stage_x11->xwin, protocols, n);
202 clutter_stage_x11_get_geometry (ClutterStageWindow *stage_window,
203 cairo_rectangle_int_t *geometry)
205 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
206 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
207 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
209 geometry->x = geometry->y = 0;
211 /* If we're fullscreen, return the size of the display. */
212 if (_clutter_stage_is_fullscreen (stage_cogl->wrapper) &&
213 stage_x11->fullscreening)
215 geometry->width = DisplayWidth (backend_x11->xdpy, backend_x11->xscreen_num);
216 geometry->height = DisplayHeight (backend_x11->xdpy, backend_x11->xscreen_num);
221 geometry->width = stage_x11->xwin_width;
222 geometry->height = stage_x11->xwin_height;
226 clutter_stage_x11_resize (ClutterStageWindow *stage_window,
230 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
231 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
232 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
234 if (stage_x11->is_foreign_xwin)
236 /* If this is a foreign window we won't get a ConfigureNotify,
237 * so we need to manually set the size and queue a relayout on the
238 * stage here (as is normally done in response to ConfigureNotify).
240 stage_x11->xwin_width = width;
241 stage_x11->xwin_height = height;
242 clutter_actor_queue_relayout (CLUTTER_ACTOR (stage_cogl->wrapper));
246 /* If we're going fullscreen, don't mess with the size */
247 if (stage_x11->fullscreening)
250 if (width == 0 || height == 0)
252 /* Should not happen, if this turns up we need to debug it and
253 * determine the cleanest way to fix.
255 g_warning ("X11 stage not allowed to have 0 width or height");
260 CLUTTER_NOTE (BACKEND, "New size received: (%d, %d)", width, height);
262 if (stage_x11->xwin != None)
264 clutter_stage_x11_fix_window_size (stage_x11, width, height);
266 if (width != stage_x11->xwin_width ||
267 height != stage_x11->xwin_height)
269 CLUTTER_NOTE (BACKEND, "%s: XResizeWindow[%x] (%d, %d)",
271 (unsigned int) stage_x11->xwin,
275 CLUTTER_SET_PRIVATE_FLAGS (stage_cogl->wrapper,
278 /* XXX: in this case we can rely on a subsequent
279 * ConfigureNotify that will result in the stage
280 * being reallocated so we don't actively do anything
281 * to affect the stage allocation here. */
282 XResizeWindow (backend_x11->xdpy,
291 set_wm_pid (ClutterStageX11 *stage_x11)
293 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
294 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
297 if (stage_x11->xwin == None || stage_x11->is_foreign_xwin)
300 /* this will take care of WM_CLIENT_MACHINE and WM_LOCALE_NAME */
301 XSetWMProperties (backend_x11->xdpy, stage_x11->xwin,
308 XChangeProperty (backend_x11->xdpy,
310 backend_x11->atom_NET_WM_PID, XA_CARDINAL, 32,
316 set_wm_title (ClutterStageX11 *stage_x11)
318 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
319 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
321 if (stage_x11->xwin == None || stage_x11->is_foreign_xwin)
324 if (stage_x11->title == NULL)
326 XDeleteProperty (backend_x11->xdpy,
328 backend_x11->atom_NET_WM_NAME);
332 XChangeProperty (backend_x11->xdpy,
334 backend_x11->atom_NET_WM_NAME,
335 backend_x11->atom_UTF8_STRING,
338 (unsigned char *) stage_x11->title,
339 (int) strlen (stage_x11->title));
344 set_cursor_visible (ClutterStageX11 *stage_x11)
346 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
347 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
349 if (stage_x11->xwin == None)
352 CLUTTER_NOTE (BACKEND, "setting cursor state ('%s') over stage window (%u)",
353 stage_x11->is_cursor_visible ? "visible" : "invisible",
354 (unsigned int) stage_x11->xwin);
356 if (stage_x11->is_cursor_visible)
358 #if 0 /* HAVE_XFIXES - seems buggy/unreliable */
359 XFixesShowCursor (backend_x11->xdpy, stage_x11->xwin);
361 XUndefineCursor (backend_x11->xdpy, stage_x11->xwin);
362 #endif /* HAVE_XFIXES */
366 #if 0 /* HAVE_XFIXES - seems buggy/unreliable, check cursor in firefox
367 * loading page after hiding.
369 XFixesHideCursor (backend_x11->xdpy, stage_x11->xwin);
375 pix = XCreatePixmap (backend_x11->xdpy, stage_x11->xwin, 1, 1, 1);
376 memset (&col, 0, sizeof (col));
377 curs = XCreatePixmapCursor (backend_x11->xdpy,
381 XFreePixmap (backend_x11->xdpy, pix);
382 XDefineCursor (backend_x11->xdpy, stage_x11->xwin, curs);
383 #endif /* HAVE_XFIXES */
388 clutter_stage_x11_unrealize (ClutterStageWindow *stage_window)
390 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
392 if (clutter_stages_by_xid != NULL)
394 CLUTTER_NOTE (BACKEND, "Removing X11 stage 0x%x [%p]",
395 (unsigned int) stage_x11->xwin,
398 g_hash_table_remove (clutter_stages_by_xid,
399 GINT_TO_POINTER (stage_x11->xwin));
402 clutter_stage_window_parent_iface->unrealize (stage_window);
406 _clutter_stage_x11_update_foreign_event_mask (CoglOnscreen *onscreen,
410 ClutterStageX11 *stage_x11 = user_data;
411 ClutterStageCogl *stage_cogl = user_data;
412 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
413 XSetWindowAttributes attrs;
415 attrs.event_mask = event_mask | CLUTTER_STAGE_X11_EVENT_MASK;
417 XChangeWindowAttributes (backend_x11->xdpy,
424 clutter_stage_x11_set_fullscreen (ClutterStageWindow *stage_window,
425 gboolean is_fullscreen)
427 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
428 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
429 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
430 ClutterStage *stage = stage_cogl->wrapper;
431 gboolean was_fullscreen;
433 if (stage == NULL || CLUTTER_ACTOR_IN_DESTRUCTION (stage))
436 was_fullscreen = _clutter_stage_is_fullscreen (stage);
437 is_fullscreen = !!is_fullscreen;
439 if (was_fullscreen == is_fullscreen)
442 CLUTTER_NOTE (BACKEND, "%ssetting fullscreen", is_fullscreen ? "" : "un");
449 /* FIXME: this will do the wrong thing for dual-headed
450 displays. This will return the size of the combined display
451 but Metacity (at least) will fullscreen to only one of the
452 displays. This will cause the actor to report the wrong size
453 until the ConfigureNotify for the correct size is received */
454 width = DisplayWidth (backend_x11->xdpy, backend_x11->xscreen_num);
455 height = DisplayHeight (backend_x11->xdpy, backend_x11->xscreen_num);
458 /* Set the fullscreen hint so we can retain the old size of the window. */
459 stage_x11->fullscreening = TRUE;
461 if (stage_x11->xwin != None)
463 /* if the actor is not mapped we resize the stage window to match
464 * the size of the screen; this is useful for e.g. EGLX to avoid
465 * a resize when calling clutter_stage_fullscreen() before showing
468 if (!STAGE_X11_IS_MAPPED (stage_x11))
470 CLUTTER_NOTE (BACKEND, "Fullscreening unmapped stage");
472 update_state (stage_x11, backend_x11,
473 &backend_x11->atom_NET_WM_STATE_FULLSCREEN,
478 CLUTTER_NOTE (BACKEND, "Fullscreening mapped stage");
480 /* We need to fix the window size so that it will remove
481 the maximum and minimum window hints. Otherwise
482 metacity will honour the restrictions and not
483 fullscreen correctly. */
484 clutter_stage_x11_fix_window_size (stage_x11, -1, -1);
486 send_wmspec_change_state (backend_x11, stage_x11->xwin,
487 backend_x11->atom_NET_WM_STATE_FULLSCREEN,
492 stage_x11->fullscreen_on_realize = TRUE;
496 stage_x11->fullscreening = FALSE;
498 if (stage_x11->xwin != None)
500 if (!STAGE_X11_IS_MAPPED (stage_x11))
502 CLUTTER_NOTE (BACKEND, "Un-fullscreening unmapped stage");
504 update_state (stage_x11, backend_x11,
505 &backend_x11->atom_NET_WM_STATE_FULLSCREEN,
510 CLUTTER_NOTE (BACKEND, "Un-fullscreening mapped stage");
512 send_wmspec_change_state (backend_x11,
514 backend_x11->atom_NET_WM_STATE_FULLSCREEN,
517 /* Fix the window size to restore the minimum/maximum
519 clutter_stage_x11_fix_window_size (stage_x11,
520 stage_x11->xwin_width,
521 stage_x11->xwin_height);
525 stage_x11->fullscreen_on_realize = FALSE;
528 /* XXX: Note we rely on the ConfigureNotify mechanism as the common
529 * mechanism to handle notifications of new X window sizes from the
530 * X server so we don't actively change the stage viewport here or
531 * queue a relayout etc. */
535 _clutter_stage_x11_events_device_changed (ClutterStageX11 *stage_x11,
536 ClutterInputDevice *device,
537 ClutterDeviceManager *device_manager)
539 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
542 if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_FLOATING)
543 event_flags = CLUTTER_STAGE_X11_EVENT_MASK;
545 _clutter_device_manager_select_stage_events (device_manager,
551 stage_events_device_added (ClutterDeviceManager *device_manager,
552 ClutterInputDevice *device,
555 ClutterStageWindow *stage_window = user_data;
556 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
557 int event_flags = CLUTTER_STAGE_X11_EVENT_MASK;
559 if (clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_FLOATING)
560 _clutter_device_manager_select_stage_events (device_manager,
566 clutter_stage_x11_realize (ClutterStageWindow *stage_window)
568 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
569 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
570 ClutterBackend *backend = CLUTTER_BACKEND (stage_cogl->backend);
571 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
572 ClutterDeviceManager *device_manager;
574 gfloat width, height;
576 clutter_actor_get_size (CLUTTER_ACTOR (stage_cogl->wrapper),
579 stage_cogl->onscreen = cogl_onscreen_new (backend->cogl_context,
582 /* We just created a window of the size of the actor. No need to fix
583 the size of the stage, just update it. */
584 stage_x11->xwin_width = width;
585 stage_x11->xwin_height = height;
587 if (stage_x11->xwin != None)
589 cogl_x11_onscreen_set_foreign_window_xid (stage_cogl->onscreen,
591 _clutter_stage_x11_update_foreign_event_mask,
596 /* Chain to the parent class now. ClutterStageCogl will call cogl_framebuffer_allocate,
597 which will create the X Window we need */
599 if (!(clutter_stage_window_parent_iface->realize (stage_window)))
602 if (stage_x11->xwin == None)
603 stage_x11->xwin = cogl_x11_onscreen_get_window_xid (stage_cogl->onscreen);
605 if (clutter_stages_by_xid == NULL)
606 clutter_stages_by_xid = g_hash_table_new (NULL, NULL);
608 g_hash_table_insert (clutter_stages_by_xid,
609 GINT_TO_POINTER (stage_x11->xwin),
612 set_wm_pid (stage_x11);
613 set_wm_title (stage_x11);
614 set_cursor_visible (stage_x11);
617 /* the masks for the events we want to select on a stage window;
618 * KeyPressMask and KeyReleaseMask are necessary even with XI1
619 * because key events are broken with that extension, and will
622 event_flags = CLUTTER_STAGE_X11_EVENT_MASK;
624 /* we unconditionally select input events even with event retrieval
625 * disabled because we need to guarantee that the Clutter internal
626 * state is maintained when calling clutter_x11_handle_event() without
627 * requiring applications or embedding toolkits to select events
628 * themselves. if we did that, we'd have to document the events to be
629 * selected, and also update applications and embedding toolkits each
630 * time we added a new mask, or a new class of events.
632 * see: http://bugzilla.clutter-project.org/show_bug.cgi?id=998
633 * for the rationale of why we did conditional selection. it is now
634 * clear that a compositor should clear out the input region, since
635 * it cannot assume a perfectly clean slate coming from us.
637 * see: http://bugzilla.clutter-project.org/show_bug.cgi?id=2228
638 * for an example of things that break if we do conditional event
641 XSelectInput (backend_x11->xdpy, stage_x11->xwin, event_flags);
643 /* input events also depent on the actual device, so we need to
644 * use the device manager to let every device select them, using
645 * the event mask we passed to XSelectInput as the template
647 device_manager = clutter_device_manager_get_default ();
648 _clutter_device_manager_select_stage_events (device_manager,
652 g_signal_connect (device_manager, "device-added",
653 G_CALLBACK (stage_events_device_added), stage_window);
655 clutter_stage_x11_fix_window_size (stage_x11,
656 stage_x11->xwin_width,
657 stage_x11->xwin_height);
658 clutter_stage_x11_set_wm_protocols (stage_x11);
660 if (stage_x11->fullscreen_on_realize)
662 stage_x11->fullscreen_on_realize = FALSE;
664 clutter_stage_x11_set_fullscreen (stage_window, TRUE);
667 CLUTTER_NOTE (BACKEND, "Successfully realized stage");
673 clutter_stage_x11_set_cursor_visible (ClutterStageWindow *stage_window,
674 gboolean cursor_visible)
676 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
678 stage_x11->is_cursor_visible = !!cursor_visible;
679 set_cursor_visible (stage_x11);
683 clutter_stage_x11_set_title (ClutterStageWindow *stage_window,
686 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
688 g_free (stage_x11->title);
689 stage_x11->title = g_strdup (title);
690 set_wm_title (stage_x11);
694 clutter_stage_x11_set_user_resizable (ClutterStageWindow *stage_window,
695 gboolean is_resizable)
697 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
699 clutter_stage_x11_fix_window_size (stage_x11,
700 stage_x11->xwin_width,
701 stage_x11->xwin_height);
705 update_wm_hints (ClutterStageX11 *stage_x11)
707 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
708 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
711 if (stage_x11->wm_state & STAGE_X11_WITHDRAWN)
714 if (stage_x11->is_foreign_xwin)
717 wm_hints.flags = StateHint | InputHint;
718 wm_hints.initial_state = NormalState;
719 wm_hints.input = stage_x11->accept_focus ? True : False;
721 XSetWMHints (backend_x11->xdpy, stage_x11->xwin, &wm_hints);
725 clutter_stage_x11_set_accept_focus (ClutterStageWindow *stage_window,
726 gboolean accept_focus)
728 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
730 stage_x11->accept_focus = !!accept_focus;
731 update_wm_hints (stage_x11);
735 set_stage_x11_state (ClutterStageX11 *stage_x11,
736 ClutterStageX11State unset_flags,
737 ClutterStageX11State set_flags)
739 ClutterStageX11State new_stage_state, old_stage_state;
741 old_stage_state = stage_x11->wm_state;
743 new_stage_state = old_stage_state;
744 new_stage_state |= set_flags;
745 new_stage_state &= ~unset_flags;
747 if (new_stage_state == old_stage_state)
750 stage_x11->wm_state = new_stage_state;
754 clutter_stage_x11_show (ClutterStageWindow *stage_window,
757 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
758 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
759 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
761 if (stage_x11->xwin != None)
763 if (do_raise && !stage_x11->is_foreign_xwin)
765 CLUTTER_NOTE (BACKEND, "Raising stage[%lu]",
766 (unsigned long) stage_x11->xwin);
767 XRaiseWindow (backend_x11->xdpy, stage_x11->xwin);
770 if (!STAGE_X11_IS_MAPPED (stage_x11))
772 CLUTTER_NOTE (BACKEND, "Mapping stage[%lu]",
773 (unsigned long) stage_x11->xwin);
775 set_stage_x11_state (stage_x11, STAGE_X11_WITHDRAWN, 0);
777 update_wm_hints (stage_x11);
779 if (stage_x11->fullscreening)
780 clutter_stage_x11_set_fullscreen (stage_window, TRUE);
782 clutter_stage_x11_set_fullscreen (stage_window, FALSE);
785 g_assert (STAGE_X11_IS_MAPPED (stage_x11));
787 clutter_actor_map (CLUTTER_ACTOR (stage_cogl->wrapper));
789 if (!stage_x11->is_foreign_xwin)
790 XMapWindow (backend_x11->xdpy, stage_x11->xwin);
795 clutter_stage_x11_hide (ClutterStageWindow *stage_window)
797 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
798 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
799 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
801 if (stage_x11->xwin != None)
803 if (STAGE_X11_IS_MAPPED (stage_x11))
804 set_stage_x11_state (stage_x11, 0, STAGE_X11_WITHDRAWN);
806 g_assert (!STAGE_X11_IS_MAPPED (stage_x11));
808 clutter_actor_unmap (CLUTTER_ACTOR (stage_cogl->wrapper));
810 if (!stage_x11->is_foreign_xwin)
811 XWithdrawWindow (backend_x11->xdpy, stage_x11->xwin, 0);
816 clutter_stage_x11_can_clip_redraws (ClutterStageWindow *stage_window)
818 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_window);
820 /* while resizing a window, clipped redraws are disabled in order to
821 * avoid artefacts. see clutter-event-x11.c:event_translate for a more
822 * detailed explanation
824 return stage_x11->clipped_redraws_cool_off == 0;
828 clutter_stage_x11_finalize (GObject *gobject)
830 ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject);
832 g_free (stage_x11->title);
834 G_OBJECT_CLASS (clutter_stage_x11_parent_class)->finalize (gobject);
838 clutter_stage_x11_dispose (GObject *gobject)
840 ClutterEventTranslator *translator = CLUTTER_EVENT_TRANSLATOR (gobject);
841 ClutterBackend *backend = CLUTTER_STAGE_COGL (gobject)->backend;
843 _clutter_backend_remove_event_translator (backend, translator);
845 G_OBJECT_CLASS (clutter_stage_x11_parent_class)->dispose (gobject);
849 clutter_stage_x11_class_init (ClutterStageX11Class *klass)
851 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
853 gobject_class->finalize = clutter_stage_x11_finalize;
854 gobject_class->dispose = clutter_stage_x11_dispose;
858 clutter_stage_x11_init (ClutterStageX11 *stage)
861 stage->xwin_width = 640;
862 stage->xwin_height = 480;
864 stage->wm_state = STAGE_X11_WITHDRAWN;
866 stage->is_foreign_xwin = FALSE;
867 stage->fullscreening = FALSE;
868 stage->is_cursor_visible = TRUE;
869 stage->accept_focus = TRUE;
875 clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
877 clutter_stage_window_parent_iface = g_type_interface_peek_parent (iface);
879 iface->set_title = clutter_stage_x11_set_title;
880 iface->set_fullscreen = clutter_stage_x11_set_fullscreen;
881 iface->set_cursor_visible = clutter_stage_x11_set_cursor_visible;
882 iface->set_user_resizable = clutter_stage_x11_set_user_resizable;
883 iface->set_accept_focus = clutter_stage_x11_set_accept_focus;
884 iface->show = clutter_stage_x11_show;
885 iface->hide = clutter_stage_x11_hide;
886 iface->resize = clutter_stage_x11_resize;
887 iface->get_geometry = clutter_stage_x11_get_geometry;
888 iface->realize = clutter_stage_x11_realize;
889 iface->unrealize = clutter_stage_x11_unrealize;
890 iface->can_clip_redraws = clutter_stage_x11_can_clip_redraws;
894 set_user_time (ClutterBackendX11 *backend_x11,
895 ClutterStageX11 *stage_x11,
898 if (timestamp != CLUTTER_CURRENT_TIME)
900 XChangeProperty (backend_x11->xdpy,
902 backend_x11->atom_NET_WM_USER_TIME,
905 (unsigned char *) ×tamp, 1);
910 handle_wm_protocols_event (ClutterBackendX11 *backend_x11,
911 ClutterStageX11 *stage_x11,
914 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
915 Atom atom = (Atom) xevent->xclient.data.l[0];
917 if (atom == backend_x11->atom_WM_DELETE_WINDOW &&
918 xevent->xany.window == stage_x11->xwin)
920 /* the WM_DELETE_WINDOW is a request: we do not destroy
921 * the window right away, as it might contain vital data;
922 * we relay the event to the application and we let it
925 CLUTTER_NOTE (EVENT, "Delete stage %s[%p], win:0x%x",
926 _clutter_actor_get_debug_name (CLUTTER_ACTOR (stage_cogl->wrapper)),
928 (unsigned int) stage_x11->xwin);
930 set_user_time (backend_x11, stage_x11, xevent->xclient.data.l[1]);
934 else if (atom == backend_x11->atom_NET_WM_PING &&
935 xevent->xany.window == stage_x11->xwin)
937 XClientMessageEvent xclient = xevent->xclient;
939 xclient.window = backend_x11->xwin_root;
940 XSendEvent (backend_x11->xdpy, xclient.window,
942 SubstructureRedirectMask | SubstructureNotifyMask,
943 (XEvent *) &xclient);
947 /* do not send any of the WM_PROTOCOLS events to the queue */
952 clipped_redraws_cool_off_cb (void *data)
954 ClutterStageX11 *stage_x11 = data;
956 stage_x11->clipped_redraws_cool_off = 0;
958 return G_SOURCE_REMOVE;
961 static ClutterTranslateReturn
962 clutter_stage_x11_translate_event (ClutterEventTranslator *translator,
966 ClutterStageX11 *stage_x11;
967 ClutterStageCogl *stage_cogl;
968 ClutterTranslateReturn res = CLUTTER_TRANSLATE_CONTINUE;
969 ClutterBackendX11 *backend_x11;
970 Window stage_xwindow;
971 XEvent *xevent = native;
974 stage_cogl = clutter_x11_get_stage_window_from_window (xevent->xany.window);
975 if (stage_cogl == NULL)
976 return CLUTTER_TRANSLATE_CONTINUE;
978 stage = stage_cogl->wrapper;
979 stage_x11 = CLUTTER_STAGE_X11 (stage_cogl);
980 backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
981 stage_xwindow = stage_x11->xwin;
983 switch (xevent->type)
985 case ConfigureNotify:
986 if (!stage_x11->is_foreign_xwin)
988 gboolean size_changed = FALSE;
990 CLUTTER_NOTE (BACKEND, "ConfigureNotify[%x] (%d, %d)",
991 (unsigned int) stage_x11->xwin,
992 xevent->xconfigure.width,
993 xevent->xconfigure.height);
995 /* When fullscreen, we'll keep the xwin_width/height
996 variables to track the old size of the window and we'll
997 assume all ConfigureNotifies constitute a size change */
998 if (_clutter_stage_is_fullscreen (stage))
1000 else if ((stage_x11->xwin_width != xevent->xconfigure.width) ||
1001 (stage_x11->xwin_height != xevent->xconfigure.height))
1003 size_changed = TRUE;
1004 stage_x11->xwin_width = xevent->xconfigure.width;
1005 stage_x11->xwin_height = xevent->xconfigure.height;
1008 clutter_actor_set_size (CLUTTER_ACTOR (stage),
1009 xevent->xconfigure.width,
1010 xevent->xconfigure.height);
1012 CLUTTER_UNSET_PRIVATE_FLAGS (stage_cogl->wrapper, CLUTTER_IN_RESIZE);
1016 /* XXX: This is a workaround for a race condition when
1017 * resizing windows while there are in-flight
1018 * glXCopySubBuffer blits happening.
1020 * The problem stems from the fact that rectangles for the
1021 * blits are described relative to the bottom left of the
1022 * window and because we can't guarantee control over the X
1023 * window gravity used when resizing so the gravity is
1024 * typically NorthWest not SouthWest.
1026 * This means if you grow a window vertically the server
1027 * will make sure to place the old contents of the window
1028 * at the top-left/north-west of your new larger window, but
1029 * that may happen asynchronous to GLX preparing to do a
1030 * blit specified relative to the bottom-left/south-west of
1031 * the window (based on the old smaller window geometry).
1033 * When the GLX issued blit finally happens relative to the
1034 * new bottom of your window, the destination will have
1035 * shifted relative to the top-left where all the pixels you
1036 * care about are so it will result in a nasty artefact
1037 * making resizing look very ugly!
1039 * We can't currently fix this completely, in-part because
1040 * the window manager tends to trample any gravity we might
1041 * set. This workaround instead simply disables blits for a
1042 * while if we are notified of any resizes happening so if
1043 * the user is resizing a window via the window manager then
1044 * they may see an artefact for one frame but then we will
1045 * fallback to redrawing the full stage until the cooling
1046 * off period is over.
1048 if (stage_x11->clipped_redraws_cool_off)
1049 g_source_remove (stage_x11->clipped_redraws_cool_off);
1051 stage_x11->clipped_redraws_cool_off =
1052 clutter_threads_add_timeout (1000,
1053 clipped_redraws_cool_off_cb,
1056 /* Queue a relayout - we want glViewport to be called
1057 * with the correct values, and this is done in ClutterStage
1058 * via cogl_onscreen_clutter_backend_set_size ().
1060 * We queue a relayout, because if this ConfigureNotify is
1061 * in response to a size we set in the application, the
1062 * set_size() call above is essentially a null-op.
1064 * Make sure we do this only when the size has changed,
1065 * otherwise we end up relayouting on window moves.
1067 clutter_actor_queue_relayout (CLUTTER_ACTOR (stage));
1069 /* the resize process is complete, so we can ask the stage
1070 * to set up the GL viewport with the new size
1072 clutter_stage_ensure_viewport (stage);
1077 case PropertyNotify:
1078 if (xevent->xproperty.atom == backend_x11->atom_NET_WM_STATE &&
1079 xevent->xproperty.window == stage_xwindow &&
1080 !stage_x11->is_foreign_xwin)
1084 gulong n_items, bytes_after;
1085 guchar *data = NULL;
1086 gboolean fullscreen_set = FALSE;
1088 clutter_x11_trap_x_errors ();
1089 XGetWindowProperty (backend_x11->xdpy, stage_xwindow,
1090 backend_x11->atom_NET_WM_STATE,
1093 &type, &format, &n_items,
1094 &bytes_after, &data);
1095 clutter_x11_untrap_x_errors ();
1097 if (type != None && data != NULL)
1099 gboolean is_fullscreen = FALSE;
1100 Atom *atoms = (Atom *) data;
1103 for (i = 0; i < n_items; i++)
1105 if (atoms[i] == backend_x11->atom_NET_WM_STATE_FULLSCREEN)
1106 fullscreen_set = TRUE;
1109 is_fullscreen = _clutter_stage_is_fullscreen (stage_cogl->wrapper);
1111 if (fullscreen_set != is_fullscreen)
1114 _clutter_stage_update_state (stage_cogl->wrapper,
1116 CLUTTER_STAGE_STATE_FULLSCREEN);
1118 _clutter_stage_update_state (stage_cogl->wrapper,
1119 CLUTTER_STAGE_STATE_FULLSCREEN,
1129 if (!_clutter_stage_is_activated (stage_cogl->wrapper))
1131 _clutter_stage_update_state (stage_cogl->wrapper,
1133 CLUTTER_STAGE_STATE_ACTIVATED);
1138 if (_clutter_stage_is_activated (stage_cogl->wrapper))
1140 _clutter_stage_update_state (stage_cogl->wrapper,
1141 CLUTTER_STAGE_STATE_ACTIVATED,
1148 XExposeEvent *expose = (XExposeEvent *) xevent;
1149 cairo_rectangle_int_t clip;
1151 CLUTTER_NOTE (EVENT,
1152 "expose for stage: %s[%p], win:0x%x - "
1153 "redrawing area (x: %d, y: %d, width: %d, height: %d)",
1154 _clutter_actor_get_debug_name (CLUTTER_ACTOR (stage)),
1156 (unsigned int) stage_xwindow,
1164 clip.width = expose->width;
1165 clip.height = expose->height;
1166 clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (stage), &clip);
1171 CLUTTER_NOTE (EVENT,
1172 "Destroy notification received for stage %s[%p], win:0x%x",
1173 _clutter_actor_get_debug_name (CLUTTER_ACTOR (stage)),
1175 (unsigned int) stage_xwindow);
1176 event->any.type = CLUTTER_DESTROY_NOTIFY;
1177 event->any.stage = stage;
1178 res = CLUTTER_TRANSLATE_QUEUE;
1182 CLUTTER_NOTE (EVENT, "Client message for stage %s[%p], win:0x%x",
1183 _clutter_actor_get_debug_name (CLUTTER_ACTOR (stage)),
1185 (unsigned int) stage_xwindow);
1186 if (handle_wm_protocols_event (backend_x11, stage_x11, xevent))
1188 event->any.type = CLUTTER_DELETE;
1189 event->any.stage = stage;
1190 res = CLUTTER_TRANSLATE_QUEUE;
1195 CLUTTER_NOTE (EVENT, "Refresh keyboard mapping");
1196 XRefreshKeyboardMapping (&xevent->xmapping);
1197 backend_x11->keymap_serial += 1;
1198 res = CLUTTER_TRANSLATE_REMOVE;
1202 res = CLUTTER_TRANSLATE_CONTINUE;
1210 clutter_event_translator_iface_init (ClutterEventTranslatorIface *iface)
1212 iface->translate_event = clutter_stage_x11_translate_event;
1216 * clutter_x11_get_stage_window: (skip)
1217 * @stage: a #ClutterStage
1219 * Gets the stages X Window.
1221 * Return value: An XID for the stage window.
1226 clutter_x11_get_stage_window (ClutterStage *stage)
1228 ClutterStageWindow *impl;
1230 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), None);
1232 impl = _clutter_stage_get_window (stage);
1233 g_assert (CLUTTER_IS_STAGE_X11 (impl));
1235 return CLUTTER_STAGE_X11 (impl)->xwin;
1238 static ClutterStageCogl *
1239 clutter_x11_get_stage_window_from_window (Window win)
1241 if (clutter_stages_by_xid == NULL)
1244 return g_hash_table_lookup (clutter_stages_by_xid,
1245 GINT_TO_POINTER (win));
1249 * clutter_x11_get_stage_from_window:
1250 * @win: an X Window ID
1252 * Gets the stage for a particular X window.
1254 * Return value: (transfer none): A #ClutterStage, or% NULL if a stage
1255 * does not exist for the window
1260 clutter_x11_get_stage_from_window (Window win)
1262 ClutterStageCogl *stage_cogl;
1264 stage_cogl = clutter_x11_get_stage_window_from_window (win);
1266 if (stage_cogl != NULL)
1267 return stage_cogl->wrapper;
1273 * clutter_x11_get_stage_visual: (skip)
1274 * @stage: a #ClutterStage
1276 * Returns an XVisualInfo suitable for creating a foreign window for the given
1277 * stage. NOTE: It doesn't do as the name may suggest, which is return the
1278 * XVisualInfo that was used to create an existing window for the given stage.
1280 * XXX: It might be best to deprecate this function and replace with something
1281 * along the lines of clutter_backend_x11_get_foreign_visual () or perhaps
1282 * clutter_stage_x11_get_foreign_visual ()
1284 * Return value: (transfer full): An XVisualInfo suitable for creating a
1285 * foreign stage. Use XFree() to free the returned value instead
1287 * Deprecated: 1.2: Use clutter_x11_get_visual_info() instead
1292 clutter_x11_get_stage_visual (ClutterStage *stage)
1294 ClutterBackend *backend = clutter_get_default_backend ();
1295 ClutterBackendX11 *backend_x11;
1297 g_return_val_if_fail (CLUTTER_IS_BACKEND_X11 (backend), NULL);
1298 backend_x11 = CLUTTER_BACKEND_X11 (backend);
1300 return _clutter_backend_x11_get_visual_info (backend_x11);
1304 ClutterStageX11 *stage_x11;
1305 cairo_rectangle_int_t geom;
1307 guint destroy_old_xwindow : 1;
1308 } ForeignWindowData;
1311 set_foreign_window_callback (ClutterActor *actor,
1314 ForeignWindowData *fwd = data;
1315 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (fwd->stage_x11);
1316 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
1318 CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)",
1319 (unsigned int) fwd->xwindow);
1321 if (fwd->destroy_old_xwindow && fwd->stage_x11->xwin != None)
1323 CLUTTER_NOTE (BACKEND, "Destroying previous window (0x%x)",
1324 (unsigned int) fwd->xwindow);
1325 XDestroyWindow (backend_x11->xdpy, fwd->stage_x11->xwin);
1328 fwd->stage_x11->xwin = fwd->xwindow;
1329 fwd->stage_x11->is_foreign_xwin = TRUE;
1331 fwd->stage_x11->xwin_width = fwd->geom.width;
1332 fwd->stage_x11->xwin_height = fwd->geom.height;
1334 clutter_actor_set_size (actor, fwd->geom.width, fwd->geom.height);
1336 if (clutter_stages_by_xid == NULL)
1337 clutter_stages_by_xid = g_hash_table_new (NULL, NULL);
1339 g_hash_table_insert (clutter_stages_by_xid,
1340 GINT_TO_POINTER (fwd->stage_x11->xwin),
1343 /* calling this with the stage unrealized will unset the stage
1344 * from the GL context; once the stage is realized the GL context
1347 clutter_stage_ensure_current (CLUTTER_STAGE (actor));
1351 * clutter_x11_set_stage_foreign:
1352 * @stage: a #ClutterStage
1353 * @xwindow: an existing X Window id
1355 * Target the #ClutterStage to use an existing external X Window
1357 * Return value: %TRUE if foreign window is valid
1362 clutter_x11_set_stage_foreign (ClutterStage *stage,
1365 ClutterBackendX11 *backend_x11;
1366 ClutterStageX11 *stage_x11;
1367 ClutterStageCogl *stage_cogl;
1368 ClutterStageWindow *impl;
1369 ClutterActor *actor;
1371 guint width, height, border, depth;
1374 ForeignWindowData fwd;
1375 XVisualInfo *xvisinfo;
1377 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
1378 g_return_val_if_fail (!CLUTTER_ACTOR_IN_DESTRUCTION (stage), FALSE);
1379 g_return_val_if_fail (xwindow != None, FALSE);
1381 impl = _clutter_stage_get_window (stage);
1382 stage_x11 = CLUTTER_STAGE_X11 (impl);
1383 stage_cogl = CLUTTER_STAGE_COGL (impl);
1384 backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
1386 xvisinfo = _clutter_backend_x11_get_visual_info (backend_x11);
1387 g_return_val_if_fail (xvisinfo != NULL, FALSE);
1389 clutter_x11_trap_x_errors ();
1391 status = XGetGeometry (backend_x11->xdpy, xwindow,
1398 if (clutter_x11_untrap_x_errors () || !status)
1400 g_critical ("Unable to retrieve the geometry of the foreign window: "
1401 "XGetGeometry() failed (status code: %d)", status);
1405 if (width == 0 || height == 0)
1407 g_warning ("The size of the foreign window is 0x0");
1411 if (depth != xvisinfo->depth)
1413 g_warning ("The depth of the visual of the foreign window is %d, but "
1414 "Clutter has been initialized to require a visual depth "
1421 fwd.stage_x11 = stage_x11;
1422 fwd.xwindow = xwindow;
1424 /* destroy the old Window, if we have one and it's ours */
1425 if (stage_x11->xwin != None && !stage_x11->is_foreign_xwin)
1426 fwd.destroy_old_xwindow = TRUE;
1428 fwd.destroy_old_xwindow = FALSE;
1432 fwd.geom.width = width;
1433 fwd.geom.height = height;
1435 actor = CLUTTER_ACTOR (stage);
1437 _clutter_actor_rerealize (actor,
1438 set_foreign_window_callback,
1441 /* Queue a relayout - so the stage will be allocated the new
1444 * Note also that when the stage gets allocated the new
1445 * window size that will result in the stage's
1446 * priv->viewport being changed, which will in turn result
1447 * in the Cogl viewport changing when _clutter_do_redraw
1448 * calls _clutter_stage_maybe_setup_viewport().
1450 clutter_actor_queue_relayout (actor);
1456 _clutter_stage_x11_set_user_time (ClutterStageX11 *stage_x11,
1459 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
1460 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
1462 set_user_time (backend_x11, stage_x11, user_time);
1466 _clutter_stage_x11_get_root_coords (ClutterStageX11 *stage_x11,
1470 ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_x11);
1471 ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (stage_cogl->backend);
1476 return_val = XTranslateCoordinates (backend_x11->xdpy,
1478 backend_x11->xwin_root,
1488 return (return_val == 0);