mirror of
https://github.com/brl/mutter.git
synced 2024-11-27 02:20:43 -05:00
99d7d31318
We need to make sure that people disabling event handling in Clutter call clutter_x11_handle_event() to update Clutter's internal state.
1299 lines
39 KiB
C
1299 lines
39 KiB
C
/* Clutter.
|
|
* An OpenGL based 'interactive canvas' library.
|
|
*
|
|
* Copyright (C) 2006, 2007, 2008 OpenedHand Ltd
|
|
* Copyright (C) 2009, 2010 Intel Corp.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
*
|
|
* Authored by:
|
|
* Matthew Allum <mallum@openedhand.com>
|
|
* Emmanuele Bassi <ebassi@linux.intel.com>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-stage-x11.h"
|
|
#include "clutter-backend-x11.h"
|
|
#include "clutter-keymap-x11.h"
|
|
#include "clutter-x11.h"
|
|
|
|
#include "../clutter-backend.h"
|
|
#include "../clutter-event.h"
|
|
#include "../clutter-private.h"
|
|
#include "../clutter-debug.h"
|
|
#include "../clutter-main.h"
|
|
|
|
#include "cogl/cogl-internal.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef HAVE_XFIXES
|
|
#include <X11/extensions/Xfixes.h>
|
|
#endif
|
|
|
|
#include <X11/Xatom.h>
|
|
|
|
#ifdef HAVE_XINPUT
|
|
#include <X11/extensions/XInput.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_XKB
|
|
#include <X11/XKBlib.h>
|
|
#endif
|
|
|
|
/* XEMBED protocol support for toolkit embedding */
|
|
#define XEMBED_MAPPED (1 << 0)
|
|
#define MAX_SUPPORTED_XEMBED_VERSION 1
|
|
|
|
#define XEMBED_EMBEDDED_NOTIFY 0
|
|
#define XEMBED_WINDOW_ACTIVATE 1
|
|
#define XEMBED_WINDOW_DEACTIVATE 2
|
|
#define XEMBED_REQUEST_FOCUS 3
|
|
#define XEMBED_FOCUS_IN 4
|
|
#define XEMBED_FOCUS_OUT 5
|
|
#define XEMBED_FOCUS_NEXT 6
|
|
#define XEMBED_FOCUS_PREV 7
|
|
/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
|
|
#define XEMBED_MODALITY_ON 10
|
|
#define XEMBED_MODALITY_OFF 11
|
|
#define XEMBED_REGISTER_ACCELERATOR 12
|
|
#define XEMBED_UNREGISTER_ACCELERATOR 13
|
|
#define XEMBED_ACTIVATE_ACCELERATOR 14
|
|
|
|
static Window ParentEmbedderWin = None;
|
|
|
|
typedef struct _ClutterEventSource ClutterEventSource;
|
|
|
|
struct _ClutterEventSource
|
|
{
|
|
GSource source;
|
|
|
|
ClutterBackend *backend;
|
|
GPollFD event_poll_fd;
|
|
};
|
|
|
|
struct _ClutterEventX11
|
|
{
|
|
/* additional fields for Key events */
|
|
gint key_group;
|
|
|
|
guint key_is_modifier : 1;
|
|
guint num_lock_set : 1;
|
|
guint caps_lock_set : 1;
|
|
};
|
|
|
|
ClutterEventX11 *
|
|
_clutter_event_x11_new (void)
|
|
{
|
|
return g_slice_new0 (ClutterEventX11);
|
|
}
|
|
|
|
ClutterEventX11 *
|
|
_clutter_event_x11_copy (ClutterEventX11 *event_x11)
|
|
{
|
|
if (event_x11 != NULL)
|
|
return g_slice_dup (ClutterEventX11, event_x11);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
_clutter_event_x11_free (ClutterEventX11 *event_x11)
|
|
{
|
|
if (event_x11 != NULL)
|
|
g_slice_free (ClutterEventX11, event_x11);
|
|
}
|
|
|
|
static gboolean clutter_event_prepare (GSource *source,
|
|
gint *timeout);
|
|
static gboolean clutter_event_check (GSource *source);
|
|
static gboolean clutter_event_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data);
|
|
|
|
static GList *event_sources = NULL;
|
|
|
|
static GSourceFuncs event_funcs = {
|
|
clutter_event_prepare,
|
|
clutter_event_check,
|
|
clutter_event_dispatch,
|
|
NULL
|
|
};
|
|
|
|
static GSource *
|
|
clutter_event_source_new (ClutterBackend *backend)
|
|
{
|
|
GSource *source = g_source_new (&event_funcs, sizeof (ClutterEventSource));
|
|
ClutterEventSource *event_source = (ClutterEventSource *) source;
|
|
|
|
#if GLIB_CHECK_VERSION (2, 25, 8)
|
|
g_source_set_name (source, "Clutter X11 Event");
|
|
#endif
|
|
|
|
event_source->backend = backend;
|
|
|
|
return source;
|
|
}
|
|
|
|
static gboolean
|
|
check_xpending (ClutterBackend *backend)
|
|
{
|
|
return XPending (CLUTTER_BACKEND_X11 (backend)->xdpy);
|
|
}
|
|
|
|
static gboolean
|
|
xembed_send_message (ClutterBackendX11 *backend_x11,
|
|
Window window,
|
|
long message,
|
|
long detail,
|
|
long data1,
|
|
long data2)
|
|
{
|
|
XEvent ev;
|
|
|
|
memset (&ev, 0, sizeof (ev));
|
|
|
|
ev.xclient.type = ClientMessage;
|
|
ev.xclient.window = window;
|
|
ev.xclient.message_type = backend_x11->atom_XEMBED;
|
|
ev.xclient.format = 32;
|
|
ev.xclient.data.l[0] = CurrentTime;
|
|
ev.xclient.data.l[1] = message;
|
|
ev.xclient.data.l[2] = detail;
|
|
ev.xclient.data.l[3] = data1;
|
|
ev.xclient.data.l[4] = data2;
|
|
|
|
clutter_x11_trap_x_errors ();
|
|
|
|
XSendEvent (backend_x11->xdpy, window, False, NoEventMask, &ev);
|
|
XSync (backend_x11->xdpy, False);
|
|
|
|
if (clutter_x11_untrap_x_errors ())
|
|
return False;
|
|
|
|
return True;
|
|
}
|
|
|
|
static void
|
|
xembed_set_info (ClutterBackendX11 *backend_x11,
|
|
Window window,
|
|
gint flags)
|
|
{
|
|
gint32 list[2];
|
|
|
|
list[0] = MAX_SUPPORTED_XEMBED_VERSION;
|
|
list[1] = XEMBED_MAPPED;
|
|
|
|
XChangeProperty (backend_x11->xdpy, window,
|
|
backend_x11->atom_XEMBED_INFO,
|
|
backend_x11->atom_XEMBED_INFO, 32,
|
|
PropModeReplace, (unsigned char *) list, 2);
|
|
}
|
|
|
|
void
|
|
_clutter_backend_x11_events_init (ClutterBackend *backend)
|
|
{
|
|
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
GSource *source;
|
|
ClutterEventSource *event_source;
|
|
int connection_number;
|
|
|
|
connection_number = ConnectionNumber (backend_x11->xdpy);
|
|
CLUTTER_NOTE (EVENT, "Connection number: %d", connection_number);
|
|
|
|
source = backend_x11->event_source = clutter_event_source_new (backend);
|
|
event_source = (ClutterEventSource *) source;
|
|
g_source_set_priority (source, CLUTTER_PRIORITY_EVENTS);
|
|
|
|
event_source->event_poll_fd.fd = connection_number;
|
|
event_source->event_poll_fd.events = G_IO_IN;
|
|
|
|
event_sources = g_list_prepend (event_sources, event_source);
|
|
|
|
g_source_add_poll (source, &event_source->event_poll_fd);
|
|
g_source_set_can_recurse (source, TRUE);
|
|
g_source_attach (source, NULL);
|
|
}
|
|
|
|
void
|
|
_clutter_backend_x11_events_uninit (ClutterBackend *backend)
|
|
{
|
|
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
|
|
if (backend_x11->event_source)
|
|
{
|
|
CLUTTER_NOTE (EVENT, "Destroying the event source");
|
|
|
|
event_sources = g_list_remove (event_sources,
|
|
backend_x11->event_source);
|
|
|
|
g_source_destroy (backend_x11->event_source);
|
|
g_source_unref (backend_x11->event_source);
|
|
backend_x11->event_source = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_last_event_time (ClutterBackendX11 *backend_x11,
|
|
XEvent *xevent)
|
|
{
|
|
Time current_time = CurrentTime;
|
|
Time last_time = backend_x11->last_event_time;
|
|
|
|
switch (xevent->type)
|
|
{
|
|
case KeyPress:
|
|
case KeyRelease:
|
|
current_time = xevent->xkey.time;
|
|
break;
|
|
|
|
case ButtonPress:
|
|
case ButtonRelease:
|
|
current_time = xevent->xbutton.time;
|
|
break;
|
|
|
|
case MotionNotify:
|
|
current_time = xevent->xmotion.time;
|
|
break;
|
|
|
|
case EnterNotify:
|
|
case LeaveNotify:
|
|
current_time = xevent->xcrossing.time;
|
|
break;
|
|
|
|
case PropertyNotify:
|
|
current_time = xevent->xproperty.time;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* only change the current event time if it's after the previous event
|
|
* time, or if it is at least 30 seconds earlier - in case the system
|
|
* clock was changed
|
|
*/
|
|
if ((current_time != CurrentTime) &&
|
|
(current_time > last_time || (last_time - current_time > (30 * 1000))))
|
|
backend_x11->last_event_time = current_time;
|
|
}
|
|
|
|
static void
|
|
set_user_time (ClutterBackendX11 *backend_x11,
|
|
Window *xwindow,
|
|
long timestamp)
|
|
{
|
|
if (timestamp != CLUTTER_CURRENT_TIME)
|
|
{
|
|
XChangeProperty (backend_x11->xdpy, *xwindow,
|
|
backend_x11->atom_NET_WM_USER_TIME,
|
|
XA_CARDINAL, 32, PropModeReplace,
|
|
(unsigned char *) ×tamp, 1);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_XINPUT
|
|
static void
|
|
convert_xdevicekey_to_xkey (XDeviceKeyEvent *xkev,
|
|
XEvent *xevent)
|
|
{
|
|
xevent->xany.type = xevent->xkey.type = xkev->type;
|
|
xevent->xkey.serial = xkev->serial;
|
|
xevent->xkey.display = xkev->display;
|
|
xevent->xkey.window = xkev->window;
|
|
xevent->xkey.root = xkev->root;
|
|
xevent->xkey.subwindow = xkev->subwindow;
|
|
xevent->xkey.time = xkev->time;
|
|
xevent->xkey.x = xkev->x;
|
|
xevent->xkey.y = xkev->y;
|
|
xevent->xkey.x_root = xkev->x_root;
|
|
xevent->xkey.y_root = xkev->y_root;
|
|
xevent->xkey.state = xkev->state;
|
|
xevent->xkey.keycode = xkev->keycode;
|
|
xevent->xkey.same_screen = xkev->same_screen;
|
|
}
|
|
#endif /* HAVE_XINPUT */
|
|
|
|
static inline void
|
|
translate_key_event (ClutterBackendX11 *backend_x11,
|
|
ClutterEvent *event,
|
|
XEvent *xevent)
|
|
{
|
|
ClutterEventX11 *event_x11;
|
|
char buffer[256 + 1];
|
|
int n;
|
|
|
|
/* KeyEvents have platform specific data associated to them */
|
|
event_x11 = _clutter_event_x11_new ();
|
|
_clutter_event_set_platform_data (event, event_x11);
|
|
|
|
event->key.time = xevent->xkey.time;
|
|
event->key.modifier_state = (ClutterModifierType) xevent->xkey.state;
|
|
event->key.hardware_keycode = xevent->xkey.keycode;
|
|
|
|
/* keyval is the key ignoring all modifiers ('1' vs. '!') */
|
|
event->key.keyval =
|
|
_clutter_keymap_x11_translate_key_state (backend_x11->keymap,
|
|
event->key.hardware_keycode,
|
|
event->key.modifier_state,
|
|
NULL);
|
|
|
|
event_x11->key_group =
|
|
_clutter_keymap_x11_get_key_group (backend_x11->keymap,
|
|
event->key.modifier_state);
|
|
event_x11->key_is_modifier =
|
|
_clutter_keymap_x11_get_is_modifier (backend_x11->keymap,
|
|
event->key.hardware_keycode);
|
|
event_x11->num_lock_set =
|
|
_clutter_keymap_x11_get_num_lock_state (backend_x11->keymap);
|
|
event_x11->caps_lock_set =
|
|
_clutter_keymap_x11_get_caps_lock_state (backend_x11->keymap);
|
|
|
|
/* unicode_value is the printable representation */
|
|
n = XLookupString (&xevent->xkey, buffer, sizeof (buffer) - 1, NULL, NULL);
|
|
|
|
if (n != NoSymbol)
|
|
{
|
|
event->key.unicode_value = g_utf8_get_char_validated (buffer, n);
|
|
if ((event->key.unicode_value != -1) &&
|
|
(event->key.unicode_value != -2))
|
|
goto out;
|
|
}
|
|
else
|
|
event->key.unicode_value = (gunichar)'\0';
|
|
|
|
out:
|
|
return;
|
|
}
|
|
|
|
static gboolean
|
|
handle_wm_protocols_event (ClutterBackendX11 *backend_x11,
|
|
Window window,
|
|
XEvent *xevent)
|
|
{
|
|
Atom atom = (Atom) xevent->xclient.data.l[0];
|
|
|
|
if (atom == backend_x11->atom_WM_DELETE_WINDOW &&
|
|
xevent->xany.window == window)
|
|
{
|
|
/* the WM_DELETE_WINDOW is a request: we do not destroy
|
|
* the window right away, as it might contain vital data;
|
|
* we relay the event to the application and we let it
|
|
* handle the request
|
|
*/
|
|
CLUTTER_NOTE (EVENT, "delete window:\txid: %ld",
|
|
xevent->xclient.window);
|
|
|
|
set_user_time (backend_x11,
|
|
&window,
|
|
xevent->xclient.data.l[1]);
|
|
|
|
return TRUE;
|
|
}
|
|
else if (atom == backend_x11->atom_NET_WM_PING &&
|
|
xevent->xany.window == window)
|
|
{
|
|
XClientMessageEvent xclient = xevent->xclient;
|
|
|
|
xclient.window = backend_x11->xwin_root;
|
|
XSendEvent (backend_x11->xdpy, xclient.window,
|
|
False,
|
|
SubstructureRedirectMask | SubstructureNotifyMask,
|
|
(XEvent *) &xclient);
|
|
return FALSE;
|
|
}
|
|
|
|
/* do not send any of the WM_PROTOCOLS events to the queue */
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
handle_xembed_event (ClutterBackendX11 *backend_x11,
|
|
XEvent *xevent)
|
|
{
|
|
ClutterActor *stage;
|
|
|
|
stage = clutter_stage_get_default ();
|
|
|
|
switch (xevent->xclient.data.l[1])
|
|
{
|
|
case XEMBED_EMBEDDED_NOTIFY:
|
|
CLUTTER_NOTE (EVENT, "got XEMBED_EMBEDDED_NOTIFY from %lx",
|
|
xevent->xclient.data.l[3]);
|
|
|
|
ParentEmbedderWin = xevent->xclient.data.l[3];
|
|
|
|
clutter_actor_realize (stage);
|
|
clutter_actor_show (stage);
|
|
|
|
xembed_set_info (backend_x11,
|
|
clutter_x11_get_stage_window (CLUTTER_STAGE (stage)),
|
|
XEMBED_MAPPED);
|
|
break;
|
|
case XEMBED_WINDOW_ACTIVATE:
|
|
CLUTTER_NOTE (EVENT, "got XEMBED_WINDOW_ACTIVATE");
|
|
break;
|
|
case XEMBED_WINDOW_DEACTIVATE:
|
|
CLUTTER_NOTE (EVENT, "got XEMBED_WINDOW_DEACTIVATE");
|
|
break;
|
|
case XEMBED_FOCUS_IN:
|
|
CLUTTER_NOTE (EVENT, "got XEMBED_FOCUS_IN");
|
|
if (ParentEmbedderWin)
|
|
xembed_send_message (backend_x11, ParentEmbedderWin,
|
|
XEMBED_FOCUS_NEXT,
|
|
0, 0, 0);
|
|
break;
|
|
default:
|
|
CLUTTER_NOTE (EVENT, "got unknown XEMBED message");
|
|
break;
|
|
}
|
|
|
|
/* do not propagate the XEMBED events to the stage */
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
event_translate (ClutterBackend *backend,
|
|
ClutterEvent *event,
|
|
XEvent *xevent)
|
|
{
|
|
ClutterBackendX11 *backend_x11;
|
|
ClutterStageX11 *stage_x11;
|
|
ClutterStage *stage;
|
|
ClutterStageWindow *impl;
|
|
ClutterDeviceManager *manager;
|
|
gboolean res, not_yet_handled = FALSE;
|
|
Window xwindow, stage_xwindow;
|
|
ClutterInputDevice *device;
|
|
|
|
backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
|
|
xwindow = xevent->xany.window;
|
|
|
|
if (backend_x11->event_filters)
|
|
{
|
|
GSList *node;
|
|
|
|
node = backend_x11->event_filters;
|
|
|
|
while (node)
|
|
{
|
|
ClutterX11EventFilter *filter = node->data;
|
|
|
|
switch (filter->func (xevent, event, filter->data))
|
|
{
|
|
case CLUTTER_X11_FILTER_CONTINUE:
|
|
break;
|
|
case CLUTTER_X11_FILTER_TRANSLATE:
|
|
return TRUE;
|
|
case CLUTTER_X11_FILTER_REMOVE:
|
|
return FALSE;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
node = node->next;
|
|
}
|
|
}
|
|
|
|
/* Pass the event through Cogl */
|
|
if (_cogl_xlib_handle_event (xevent) == COGL_XLIB_FILTER_REMOVE)
|
|
return FALSE;
|
|
|
|
/* Do further processing only on events for the stage window (the x11
|
|
* filters might be getting events for other windows, so do not mess
|
|
* them about.
|
|
*/
|
|
stage = clutter_x11_get_stage_from_window (xwindow);
|
|
if (stage == NULL)
|
|
return FALSE;
|
|
|
|
impl = _clutter_stage_get_window (stage);
|
|
stage_x11 = CLUTTER_STAGE_X11 (impl);
|
|
stage_xwindow = xwindow; /* clutter_x11_get_stage_window (stage); */
|
|
|
|
event->any.stage = stage;
|
|
|
|
res = TRUE;
|
|
|
|
update_last_event_time (backend_x11, xevent);
|
|
|
|
manager = clutter_device_manager_get_default ();
|
|
|
|
switch (xevent->type)
|
|
{
|
|
case ConfigureNotify:
|
|
if (!stage_x11->is_foreign_xwin)
|
|
{
|
|
CLUTTER_NOTE (BACKEND, "%s: ConfigureNotify[%x] (%d, %d)",
|
|
G_STRLOC,
|
|
(unsigned int) stage_x11->xwin,
|
|
xevent->xconfigure.width,
|
|
xevent->xconfigure.height);
|
|
|
|
/* Queue a relayout - we want glViewport to be called
|
|
* with the correct values, and this is done in ClutterStage
|
|
* via _cogl_onscreen_clutter_backend_set_size ().
|
|
*
|
|
* We queue a relayout, because if this ConfigureNotify is
|
|
* in response to a size we set in the application, the
|
|
* set_size above is essentially a null-op.
|
|
*
|
|
* Make sure we do this only when the size has changed,
|
|
* otherwise we end up relayouting on window moves.
|
|
*/
|
|
if ((stage_x11->state & CLUTTER_STAGE_STATE_FULLSCREEN) ||
|
|
(stage_x11->xwin_width != xevent->xconfigure.width) ||
|
|
(stage_x11->xwin_height != xevent->xconfigure.height))
|
|
{
|
|
clutter_actor_queue_relayout (CLUTTER_ACTOR (stage));
|
|
}
|
|
|
|
/* If we're fullscreened, we want these variables to
|
|
* represent the size of the window before it was set
|
|
* to fullscreen.
|
|
*/
|
|
if (!(stage_x11->state & CLUTTER_STAGE_STATE_FULLSCREEN))
|
|
{
|
|
stage_x11->xwin_width = xevent->xconfigure.width;
|
|
stage_x11->xwin_height = xevent->xconfigure.height;
|
|
}
|
|
|
|
clutter_actor_set_size (CLUTTER_ACTOR (stage),
|
|
xevent->xconfigure.width,
|
|
xevent->xconfigure.height);
|
|
|
|
CLUTTER_UNSET_PRIVATE_FLAGS (stage_x11->wrapper,
|
|
CLUTTER_IN_RESIZE);
|
|
|
|
/* the resize process is complete, so we can ask the stage
|
|
* to set up the GL viewport with the new size
|
|
*/
|
|
clutter_stage_ensure_viewport (stage);
|
|
}
|
|
res = FALSE;
|
|
break;
|
|
|
|
case PropertyNotify:
|
|
if (xevent->xproperty.atom == backend_x11->atom_NET_WM_STATE &&
|
|
xevent->xproperty.window == stage_xwindow &&
|
|
!stage_x11->is_foreign_xwin)
|
|
{
|
|
Atom type;
|
|
gint format;
|
|
gulong n_items, bytes_after;
|
|
guchar *data = NULL;
|
|
gboolean fullscreen_set = FALSE;
|
|
|
|
clutter_x11_trap_x_errors ();
|
|
XGetWindowProperty (backend_x11->xdpy, stage_xwindow,
|
|
backend_x11->atom_NET_WM_STATE,
|
|
0, G_MAXLONG,
|
|
False, XA_ATOM,
|
|
&type, &format, &n_items,
|
|
&bytes_after, &data);
|
|
clutter_x11_untrap_x_errors ();
|
|
|
|
if (type != None && data != NULL)
|
|
{
|
|
Atom *atoms = (Atom *) data;
|
|
gulong i;
|
|
gboolean is_fullscreen = FALSE;
|
|
|
|
for (i = 0; i < n_items; i++)
|
|
{
|
|
if (atoms[i] == backend_x11->atom_NET_WM_STATE_FULLSCREEN)
|
|
fullscreen_set = TRUE;
|
|
}
|
|
|
|
is_fullscreen =
|
|
(stage_x11->state & CLUTTER_STAGE_STATE_FULLSCREEN);
|
|
|
|
if (fullscreen_set != is_fullscreen)
|
|
{
|
|
if (fullscreen_set)
|
|
stage_x11->state |= CLUTTER_STAGE_STATE_FULLSCREEN;
|
|
else
|
|
stage_x11->state &= ~CLUTTER_STAGE_STATE_FULLSCREEN;
|
|
|
|
stage_x11->fullscreening = fullscreen_set;
|
|
|
|
event->type = CLUTTER_STAGE_STATE;
|
|
event->stage_state.changed_mask =
|
|
CLUTTER_STAGE_STATE_FULLSCREEN;
|
|
event->stage_state.new_state = stage_x11->state;
|
|
}
|
|
else
|
|
res = FALSE;
|
|
|
|
XFree (data);
|
|
}
|
|
else
|
|
res = FALSE;
|
|
}
|
|
else
|
|
res = FALSE;
|
|
break;
|
|
|
|
case MapNotify:
|
|
res = FALSE;
|
|
break;
|
|
|
|
case UnmapNotify:
|
|
res = FALSE;
|
|
break;
|
|
|
|
case FocusIn:
|
|
if (!(stage_x11->state & CLUTTER_STAGE_STATE_ACTIVATED))
|
|
{
|
|
/* TODO: check xevent->xfocus.detail ? */
|
|
stage_x11->state |= CLUTTER_STAGE_STATE_ACTIVATED;
|
|
|
|
event->type = CLUTTER_STAGE_STATE;
|
|
event->stage_state.changed_mask = CLUTTER_STAGE_STATE_ACTIVATED;
|
|
event->stage_state.new_state = stage_x11->state;
|
|
}
|
|
else
|
|
res = FALSE;
|
|
break;
|
|
|
|
case FocusOut:
|
|
if (stage_x11->state & CLUTTER_STAGE_STATE_ACTIVATED)
|
|
{
|
|
stage_x11->state &= ~CLUTTER_STAGE_STATE_ACTIVATED;
|
|
|
|
event->type = CLUTTER_STAGE_STATE;
|
|
event->stage_state.changed_mask = CLUTTER_STAGE_STATE_ACTIVATED;
|
|
event->stage_state.new_state = stage_x11->state;
|
|
}
|
|
else
|
|
res = FALSE;
|
|
break;
|
|
|
|
case Expose:
|
|
{
|
|
CLUTTER_NOTE (MULTISTAGE, "expose for stage: %p, redrawing", stage);
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
|
|
res = FALSE;
|
|
}
|
|
break;
|
|
case DestroyNotify:
|
|
CLUTTER_NOTE (EVENT, "destroy notify:\txid: %ld",
|
|
xevent->xdestroywindow.window);
|
|
if (xevent->xdestroywindow.window == stage_xwindow &&
|
|
!stage_x11->is_foreign_xwin)
|
|
event->type = event->any.type = CLUTTER_DESTROY_NOTIFY;
|
|
else
|
|
res = FALSE;
|
|
break;
|
|
|
|
case ClientMessage:
|
|
CLUTTER_NOTE (EVENT, "client message");
|
|
|
|
event->type = event->any.type = CLUTTER_CLIENT_MESSAGE;
|
|
|
|
if (xevent->xclient.message_type == backend_x11->atom_XEMBED)
|
|
res = handle_xembed_event (backend_x11, xevent);
|
|
else if (xevent->xclient.message_type == backend_x11->atom_WM_PROTOCOLS)
|
|
{
|
|
res = handle_wm_protocols_event (backend_x11, stage_xwindow, xevent);
|
|
event->type = event->any.type = CLUTTER_DELETE;
|
|
}
|
|
break;
|
|
|
|
case KeyPress:
|
|
event->key.type = event->type = CLUTTER_KEY_PRESS;
|
|
event->key.device =
|
|
clutter_device_manager_get_core_device (manager,
|
|
CLUTTER_KEYBOARD_DEVICE);
|
|
|
|
translate_key_event (backend_x11, event, xevent);
|
|
|
|
set_user_time (backend_x11, &xwindow, xevent->xkey.time);
|
|
break;
|
|
|
|
case KeyRelease:
|
|
/* old-style X11 terminals require that even modern X11 send
|
|
* KeyPress/KeyRelease pairs when auto-repeating. for this
|
|
* reason modern(-ish) API like XKB has a way to detect
|
|
* auto-repeat and do a single KeyRelease at the end of a
|
|
* KeyPress sequence.
|
|
*
|
|
* this check emulates XKB's detectable auto-repeat; we peek
|
|
* the next event and check if it's a KeyPress for the same key
|
|
* and timestamp - and then ignore it if it matches the
|
|
* KeyRelease
|
|
*
|
|
* if we have XKB, and autorepeat is enabled, then this becomes
|
|
* a no-op
|
|
*/
|
|
if (!backend_x11->have_xkb_autorepeat && XPending (xevent->xkey.display))
|
|
{
|
|
XEvent next_event;
|
|
|
|
XPeekEvent (xevent->xkey.display, &next_event);
|
|
|
|
if (next_event.type == KeyPress &&
|
|
next_event.xkey.keycode == xevent->xkey.keycode &&
|
|
next_event.xkey.time == xevent->xkey.time)
|
|
{
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
event->key.type = event->type = CLUTTER_KEY_RELEASE;
|
|
event->key.device =
|
|
clutter_device_manager_get_core_device (manager,
|
|
CLUTTER_KEYBOARD_DEVICE);
|
|
|
|
translate_key_event (backend_x11, event, xevent);
|
|
break;
|
|
|
|
default:
|
|
/* ignore every other event */
|
|
not_yet_handled = TRUE;
|
|
break;
|
|
}
|
|
|
|
/* Input device event handling.. */
|
|
if (not_yet_handled)
|
|
{
|
|
device = clutter_device_manager_get_core_device (manager,
|
|
CLUTTER_POINTER_DEVICE);
|
|
|
|
/* Regular X event */
|
|
switch (xevent->type)
|
|
{
|
|
case ButtonPress:
|
|
switch (xevent->xbutton.button)
|
|
{
|
|
case 4: /* up */
|
|
case 5: /* down */
|
|
case 6: /* left */
|
|
case 7: /* right */
|
|
event->scroll.type = event->type = CLUTTER_SCROLL;
|
|
|
|
if (xevent->xbutton.button == 4)
|
|
event->scroll.direction = CLUTTER_SCROLL_UP;
|
|
else if (xevent->xbutton.button == 5)
|
|
event->scroll.direction = CLUTTER_SCROLL_DOWN;
|
|
else if (xevent->xbutton.button == 6)
|
|
event->scroll.direction = CLUTTER_SCROLL_LEFT;
|
|
else
|
|
event->scroll.direction = CLUTTER_SCROLL_RIGHT;
|
|
|
|
event->scroll.time = xevent->xbutton.time;
|
|
event->scroll.x = xevent->xbutton.x;
|
|
event->scroll.y = xevent->xbutton.y;
|
|
event->scroll.modifier_state = xevent->xbutton.state;
|
|
event->scroll.device = device;
|
|
break;
|
|
|
|
default:
|
|
event->button.type = event->type = CLUTTER_BUTTON_PRESS;
|
|
event->button.time = xevent->xbutton.time;
|
|
event->button.x = xevent->xbutton.x;
|
|
event->button.y = xevent->xbutton.y;
|
|
event->button.modifier_state = xevent->xbutton.state;
|
|
event->button.button = xevent->xbutton.button;
|
|
event->button.device = device;
|
|
break;
|
|
}
|
|
|
|
set_user_time (backend_x11, &xwindow, event->button.time);
|
|
|
|
res = TRUE;
|
|
break;
|
|
|
|
case ButtonRelease:
|
|
/* scroll events don't have a corresponding release */
|
|
if (xevent->xbutton.button == 4 ||
|
|
xevent->xbutton.button == 5 ||
|
|
xevent->xbutton.button == 6 ||
|
|
xevent->xbutton.button == 7)
|
|
{
|
|
res = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
event->button.type = event->type = CLUTTER_BUTTON_RELEASE;
|
|
event->button.time = xevent->xbutton.time;
|
|
event->button.x = xevent->xbutton.x;
|
|
event->button.y = xevent->xbutton.y;
|
|
event->button.modifier_state = xevent->xbutton.state;
|
|
event->button.button = xevent->xbutton.button;
|
|
event->button.device = device;
|
|
|
|
res = TRUE;
|
|
break;
|
|
|
|
case MotionNotify:
|
|
event->motion.type = event->type = CLUTTER_MOTION;
|
|
event->motion.time = xevent->xmotion.time;
|
|
event->motion.x = xevent->xmotion.x;
|
|
event->motion.y = xevent->xmotion.y;
|
|
event->motion.modifier_state = xevent->xmotion.state;
|
|
event->motion.device = device;
|
|
|
|
res = TRUE;
|
|
break;
|
|
|
|
case EnterNotify:
|
|
/* we know that we are entering the stage here */
|
|
_clutter_input_device_set_stage (device, stage);
|
|
CLUTTER_NOTE (EVENT, "Entering the stage");
|
|
|
|
/* Convert enter notifies to motion events because X
|
|
doesn't emit the corresponding motion notify */
|
|
event->motion.type = event->type = CLUTTER_MOTION;
|
|
event->motion.time = xevent->xcrossing.time;
|
|
event->motion.x = xevent->xcrossing.x;
|
|
event->motion.y = xevent->xcrossing.y;
|
|
event->motion.modifier_state = xevent->xcrossing.state;
|
|
event->motion.source = CLUTTER_ACTOR (stage);
|
|
event->motion.device = device;
|
|
|
|
res = TRUE;
|
|
break;
|
|
|
|
case LeaveNotify:
|
|
if (device->stage == NULL)
|
|
{
|
|
CLUTTER_NOTE (EVENT,
|
|
"Discarding LeaveNotify for ButtonRelease "
|
|
"event off-stage");
|
|
res = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
/* we know that we are leaving the stage here */
|
|
_clutter_input_device_set_stage (device, NULL);
|
|
CLUTTER_NOTE (EVENT, "Leaving the stage (time:%u)",
|
|
event->crossing.time);
|
|
|
|
event->crossing.type = event->type = CLUTTER_LEAVE;
|
|
event->crossing.time = xevent->xcrossing.time;
|
|
event->crossing.x = xevent->xcrossing.x;
|
|
event->crossing.y = xevent->xcrossing.y;
|
|
event->crossing.source = CLUTTER_ACTOR (stage);
|
|
event->crossing.device = device;
|
|
res = TRUE;
|
|
break;
|
|
|
|
default:
|
|
res = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* XInput fun...*/
|
|
if (!res && clutter_x11_has_xinput ())
|
|
{
|
|
#ifdef HAVE_XINPUT
|
|
int *ev_types = backend_x11->event_types;
|
|
int button_press, button_release;
|
|
int key_press, key_release;
|
|
int motion_notify;
|
|
|
|
button_press = ev_types[CLUTTER_X11_XINPUT_BUTTON_PRESS_EVENT];
|
|
button_release = ev_types[CLUTTER_X11_XINPUT_BUTTON_RELEASE_EVENT];
|
|
motion_notify = ev_types[CLUTTER_X11_XINPUT_MOTION_NOTIFY_EVENT];
|
|
key_press = ev_types[CLUTTER_X11_XINPUT_KEY_PRESS_EVENT];
|
|
key_release = ev_types[CLUTTER_X11_XINPUT_KEY_RELEASE_EVENT];
|
|
|
|
CLUTTER_NOTE (EVENT, "XInput event type: %d", xevent->type);
|
|
|
|
if (xevent->type == button_press)
|
|
{
|
|
XDeviceButtonEvent *xbev = (XDeviceButtonEvent *) xevent;
|
|
|
|
device = _clutter_x11_get_device_for_xid (xbev->deviceid);
|
|
_clutter_input_device_set_stage (device, stage);
|
|
|
|
CLUTTER_NOTE (EVENT,
|
|
"XI ButtonPress for %li ('%s') at %d, %d",
|
|
xbev->deviceid,
|
|
device->device_name,
|
|
xbev->x,
|
|
xbev->y);
|
|
|
|
switch (xbev->button)
|
|
{
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
event->scroll.type = event->type = CLUTTER_SCROLL;
|
|
|
|
if (xbev->button == 4)
|
|
event->scroll.direction = CLUTTER_SCROLL_UP;
|
|
else if (xbev->button == 5)
|
|
event->scroll.direction = CLUTTER_SCROLL_DOWN;
|
|
else if (xbev->button == 6)
|
|
event->scroll.direction = CLUTTER_SCROLL_LEFT;
|
|
else
|
|
event->scroll.direction = CLUTTER_SCROLL_RIGHT;
|
|
|
|
event->scroll.time = xbev->time;
|
|
event->scroll.x = xbev->x;
|
|
event->scroll.y = xbev->y;
|
|
event->scroll.modifier_state = xbev->state;
|
|
event->scroll.device = device;
|
|
break;
|
|
|
|
default:
|
|
event->button.type = event->type = CLUTTER_BUTTON_PRESS;
|
|
event->button.time = xbev->time;
|
|
event->button.x = xbev->x;
|
|
event->button.y = xbev->y;
|
|
event->button.modifier_state = xbev->state;
|
|
event->button.button = xbev->button;
|
|
event->button.device = device;
|
|
break;
|
|
}
|
|
|
|
set_user_time (backend_x11, &xwindow, xbev->time);
|
|
|
|
res = TRUE;
|
|
}
|
|
else if (xevent->type == button_release)
|
|
{
|
|
XDeviceButtonEvent *xbev = (XDeviceButtonEvent *)xevent;
|
|
|
|
device = _clutter_x11_get_device_for_xid (xbev->deviceid);
|
|
_clutter_input_device_set_stage (device, stage);
|
|
|
|
CLUTTER_NOTE (EVENT, "XI ButtonRelease for %li ('%s') at %d, %d",
|
|
xbev->deviceid,
|
|
device->device_name,
|
|
xbev->x,
|
|
xbev->y);
|
|
|
|
/* scroll events don't have a corresponding release */
|
|
if (xbev->button == 4 ||
|
|
xbev->button == 5 ||
|
|
xbev->button == 6 ||
|
|
xbev->button == 7)
|
|
{
|
|
res = FALSE;
|
|
goto out;
|
|
}
|
|
|
|
event->button.type = event->type = CLUTTER_BUTTON_RELEASE;
|
|
event->button.time = xbev->time;
|
|
event->button.x = xbev->x;
|
|
event->button.y = xbev->y;
|
|
event->button.modifier_state = xbev->state;
|
|
event->button.button = xbev->button;
|
|
event->button.device = device;
|
|
|
|
res = TRUE;
|
|
}
|
|
else if (xevent->type == motion_notify)
|
|
{
|
|
XDeviceMotionEvent *xmev = (XDeviceMotionEvent *)xevent;
|
|
|
|
device = _clutter_x11_get_device_for_xid (xmev->deviceid);
|
|
_clutter_input_device_set_stage (device, stage);
|
|
|
|
CLUTTER_NOTE (EVENT, "XI Motion for %li ('%s') at %d, %d",
|
|
xmev->deviceid,
|
|
device->device_name,
|
|
xmev->x,
|
|
xmev->y);
|
|
|
|
event->motion.type = event->type = CLUTTER_MOTION;
|
|
event->motion.time = xmev->time;
|
|
event->motion.x = xmev->x;
|
|
event->motion.y = xmev->y;
|
|
event->motion.modifier_state = xmev->state;
|
|
event->motion.device = device;
|
|
|
|
res = TRUE;
|
|
}
|
|
else if (xevent->type == key_press || xevent->type == key_release)
|
|
{
|
|
/* the XInput 1.x handling of key presses/releases is broken:
|
|
* it makes key repeat, key presses and releases outside the
|
|
* window not generate events even when the window has focus
|
|
*/
|
|
XDeviceKeyEvent *xkev = (XDeviceKeyEvent *) xevent;
|
|
XEvent xevent_converted;
|
|
|
|
convert_xdevicekey_to_xkey (xkev, &xevent_converted);
|
|
|
|
event->key.type = event->type = (xevent->type == key_press)
|
|
? CLUTTER_KEY_PRESS
|
|
: CLUTTER_KEY_RELEASE;
|
|
|
|
translate_key_event (backend_x11, event, &xevent_converted);
|
|
|
|
if (xevent->type == key_press)
|
|
set_user_time (backend_x11, &xwindow, xkev->time);
|
|
}
|
|
else
|
|
#endif /* HAVE_XINPUT */
|
|
{
|
|
CLUTTER_NOTE (EVENT, "Uknown Event");
|
|
res = FALSE;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
events_queue (ClutterBackend *backend)
|
|
{
|
|
ClutterBackendX11 *backend_x11 = CLUTTER_BACKEND_X11 (backend);
|
|
ClutterBackendX11Class *backend_x11_class =
|
|
CLUTTER_BACKEND_X11_GET_CLASS (backend_x11);
|
|
ClutterEvent *event;
|
|
Display *xdisplay = backend_x11->xdpy;
|
|
XEvent xevent;
|
|
ClutterMainContext *clutter_context;
|
|
|
|
clutter_context = _clutter_context_get_default ();
|
|
|
|
while (!clutter_events_pending () && XPending (xdisplay))
|
|
{
|
|
XNextEvent (xdisplay, &xevent);
|
|
|
|
if (backend_x11_class->handle_event (backend_x11, &xevent))
|
|
continue;
|
|
|
|
event = clutter_event_new (CLUTTER_NOTHING);
|
|
|
|
if (event_translate (backend, event, &xevent))
|
|
{
|
|
/* push directly here to avoid copy of queue_put */
|
|
g_queue_push_head (clutter_context->events_queue, event);
|
|
}
|
|
else
|
|
clutter_event_free (event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_x11_handle_event:
|
|
* @xevent: pointer to XEvent structure
|
|
*
|
|
* This function processes a single X event; it can be used to hook
|
|
* into external X11 event processing (for example, a GDK filter
|
|
* function).
|
|
*
|
|
* If clutter_x11_disable_event_retrieval() has been called, you must
|
|
* let this function process events to update Clutter's internal state.
|
|
*
|
|
* Return value: #ClutterX11FilterReturn. %CLUTTER_X11_FILTER_REMOVE
|
|
* indicates that Clutter has internally handled the event and the
|
|
* caller should do no further processing. %CLUTTER_X11_FILTER_CONTINUE
|
|
* indicates that Clutter is either not interested in the event,
|
|
* or has used the event to update internal state without taking
|
|
* any exclusive action. %CLUTTER_X11_FILTER_TRANSLATE will not
|
|
* occur.
|
|
*
|
|
* Since: 0.8
|
|
*/
|
|
ClutterX11FilterReturn
|
|
clutter_x11_handle_event (XEvent *xevent)
|
|
{
|
|
ClutterBackend *backend;
|
|
ClutterBackendX11Class *backend_x11_class;
|
|
ClutterEvent *event;
|
|
ClutterMainContext *clutter_context;
|
|
ClutterX11FilterReturn result;
|
|
gint spin = 1;
|
|
|
|
/* The return values here are someone approximate; we return
|
|
* CLUTTER_X11_FILTER_REMOVE if a clutter event is
|
|
* generated for the event. This mostly, but not entirely,
|
|
* corresponds to whether other event processing should be
|
|
* excluded. As long as the stage window is not shared with another
|
|
* toolkit it should be safe, and never return
|
|
* %CLUTTER_X11_FILTER_REMOVE when more processing is needed.
|
|
*/
|
|
|
|
result = CLUTTER_X11_FILTER_CONTINUE;
|
|
|
|
clutter_threads_enter ();
|
|
|
|
clutter_context = _clutter_context_get_default ();
|
|
backend = clutter_context->backend;
|
|
backend_x11_class = CLUTTER_BACKEND_X11_GET_CLASS (backend);
|
|
|
|
/* If the backend just observed the event and didn't want it
|
|
* removed it could return FALSE, so assume that a TRUE return
|
|
* means that our caller should also do no further processing. */
|
|
if (backend_x11_class->handle_event (CLUTTER_BACKEND_X11(backend), xevent))
|
|
return CLUTTER_X11_FILTER_REMOVE;
|
|
|
|
event = clutter_event_new (CLUTTER_NOTHING);
|
|
|
|
if (event_translate (backend, event, xevent))
|
|
{
|
|
/* push directly here to avoid copy of queue_put */
|
|
result = CLUTTER_X11_FILTER_REMOVE;
|
|
g_queue_push_head (clutter_context->events_queue, event);
|
|
}
|
|
else
|
|
{
|
|
clutter_event_free (event);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Motion events can generate synthetic enter and leave events, so if we
|
|
* are processing a motion event, we need to spin the event loop at least
|
|
* two extra times to pump the enter/leave events through (otherwise they
|
|
* just get pushed down the queue and never processed).
|
|
*/
|
|
if (event->type == CLUTTER_MOTION)
|
|
spin += 2;
|
|
|
|
while (spin > 0 && (event = clutter_event_get ()))
|
|
{
|
|
/* forward the event into clutter for emission etc. */
|
|
clutter_do_event (event);
|
|
clutter_event_free (event);
|
|
--spin;
|
|
}
|
|
|
|
out:
|
|
clutter_threads_leave ();
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_prepare (GSource *source,
|
|
gint *timeout)
|
|
{
|
|
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
|
|
gboolean retval;
|
|
|
|
clutter_threads_enter ();
|
|
|
|
*timeout = -1;
|
|
retval = (clutter_events_pending () || check_xpending (backend));
|
|
|
|
clutter_threads_leave ();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_check (GSource *source)
|
|
{
|
|
ClutterEventSource *event_source = (ClutterEventSource *) source;
|
|
ClutterBackend *backend = event_source->backend;
|
|
gboolean retval;
|
|
|
|
clutter_threads_enter ();
|
|
|
|
if (event_source->event_poll_fd.revents & G_IO_IN)
|
|
retval = (clutter_events_pending () || check_xpending (backend));
|
|
else
|
|
retval = FALSE;
|
|
|
|
clutter_threads_leave ();
|
|
|
|
return retval;
|
|
}
|
|
|
|
static gboolean
|
|
clutter_event_dispatch (GSource *source,
|
|
GSourceFunc callback,
|
|
gpointer user_data)
|
|
{
|
|
ClutterBackend *backend = ((ClutterEventSource *) source)->backend;
|
|
ClutterEvent *event;
|
|
|
|
clutter_threads_enter ();
|
|
|
|
/* Grab the event(s), translate and figure out double click.
|
|
* The push onto queue (stack) if valid.
|
|
*/
|
|
events_queue (backend);
|
|
|
|
/* Pop an event off the queue if any */
|
|
event = clutter_event_get ();
|
|
|
|
if (event)
|
|
{
|
|
/* forward the event into clutter for emission etc. */
|
|
clutter_do_event (event);
|
|
clutter_event_free (event);
|
|
}
|
|
|
|
clutter_threads_leave ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_x11_get_current_event_time:
|
|
*
|
|
* Retrieves the timestamp of the last X11 event processed by
|
|
* Clutter. This might be different from the timestamp returned
|
|
* by clutter_get_current_event_time(), as Clutter may synthesize
|
|
* or throttle events.
|
|
*
|
|
* Return value: a timestamp, in milliseconds
|
|
*
|
|
* Since: 1.0
|
|
*/
|
|
Time
|
|
clutter_x11_get_current_event_time (void)
|
|
{
|
|
ClutterBackend *backend = clutter_get_default_backend ();
|
|
|
|
return CLUTTER_BACKEND_X11 (backend)->last_event_time;
|
|
}
|
|
|
|
/**
|
|
* clutter_x11_event_get_key_group:
|
|
* @event: a #ClutterEvent of type %CLUTTER_KEY_PRESS or %CLUTTER_KEY_RELEASE
|
|
*
|
|
* Retrieves the group for the modifiers set in @event
|
|
*
|
|
* Return value: the group id
|
|
*
|
|
* Since: 1.4
|
|
*/
|
|
gint
|
|
clutter_x11_event_get_key_group (const ClutterEvent *event)
|
|
{
|
|
ClutterEventX11 *event_x11;
|
|
|
|
g_return_val_if_fail (event != NULL, 0);
|
|
g_return_val_if_fail (event->type == CLUTTER_KEY_PRESS ||
|
|
event->type == CLUTTER_KEY_RELEASE, 0);
|
|
|
|
event_x11 = _clutter_event_get_platform_data (event);
|
|
if (event_x11 == NULL)
|
|
return 0;
|
|
|
|
return event_x11->key_group;
|
|
}
|