Add a modal mode for plugins

mutter_plugin_begin_modal() and mutter_plugin_begin_modal() allow putting
a plugin into a "modal" state. This means:

 - The plugin has the keyboard and mouse grabbed
 - All keyboard and mouse events go exclusively to the plugin

mutter-plugin.[ch]: Add public API
compositor.c compositor-private.h: Implement the API
mutter-plugin-manager.c: When reloading plugins, make sure none of them
  are modal at that moment, and if so force-unmodal them.
common.h: Add META_GRAB_OP_COMPOSITOR
display: When display->grab_op is META_GRAB_OP_COMPOSITOR forward relevant
  events exclusively to the compositor.

http://bugzilla.gnome.org/show_bug.cgi?id=590754
This commit is contained in:
Owen W. Taylor 2009-08-12 00:12:52 -04:00
parent b1776b5ae5
commit 67682a2683
7 changed files with 271 additions and 6 deletions

View File

@ -23,6 +23,8 @@ struct _MetaCompositor
ClutterActor *shadow_src; ClutterActor *shadow_src;
MutterPlugin *modal_plugin;
gboolean show_redraw : 1; gboolean show_redraw : 1;
gboolean debug : 1; gboolean debug : 1;
gboolean no_mipmaps : 1; gboolean no_mipmaps : 1;
@ -51,4 +53,16 @@ void mutter_set_stage_input_region (MetaScreen *screen,
XserverRegion region); XserverRegion region);
void mutter_empty_stage_input_region (MetaScreen *screen); void mutter_empty_stage_input_region (MetaScreen *screen);
gboolean mutter_begin_modal_for_plugin (MetaScreen *screen,
MutterPlugin *plugin,
Window grab_window,
Cursor cursor,
MetaModalOptions options,
guint32 timestamp);
void mutter_end_modal_for_plugin (MetaScreen *screen,
MutterPlugin *plugin,
guint32 timestamp);
void mutter_check_end_modal (MetaScreen *screen);
#endif /* META_COMPOSITOR_PRIVATE_H */ #endif /* META_COMPOSITOR_PRIVATE_H */

View File

@ -350,6 +350,117 @@ mutter_empty_stage_input_region (MetaScreen *screen)
mutter_set_stage_input_region (screen, region); mutter_set_stage_input_region (screen, region);
} }
gboolean
mutter_begin_modal_for_plugin (MetaScreen *screen,
MutterPlugin *plugin,
Window grab_window,
Cursor cursor,
MetaModalOptions options,
guint32 timestamp)
{
/* To some extent this duplicates code in meta_display_begin_grab_op(), but there
* are significant differences in how we handle grabs that make it difficult to
* merge the two.
*/
MetaDisplay *display = meta_screen_get_display (screen);
Display *xdpy = meta_display_get_xdisplay (display);
MetaCompositor *compositor = display->compositor;
gboolean pointer_grabbed = FALSE;
gboolean keyboard_grabbed = FALSE;
int result;
if (compositor->modal_plugin != NULL || display->grab_op != META_GRAB_OP_NONE)
return FALSE;
if ((options & META_MODAL_POINTER_ALREADY_GRABBED) == 0)
{
result = XGrabPointer (xdpy, grab_window,
False, /* owner_events */
(ButtonPressMask | ButtonReleaseMask |
EnterWindowMask | LeaveWindowMask | PointerMotionMask),
GrabModeAsync, GrabModeAsync,
None, /* confine to */
cursor,
timestamp);
if (result != Success)
goto fail;
pointer_grabbed = TRUE;
}
if ((options & META_MODAL_KEYBOARD_ALREADY_GRABBED) == 0)
{
XGrabKeyboard (xdpy, grab_window,
False, /* owner_events */
GrabModeAsync, GrabModeAsync,
timestamp);
if (result != Success)
goto fail;
keyboard_grabbed = TRUE;
}
display->grab_op = META_GRAB_OP_COMPOSITOR;
display->grab_window = NULL;
display->grab_screen = screen;
display->grab_have_pointer = TRUE;
display->grab_have_keyboard = TRUE;
compositor->modal_plugin = plugin;
return TRUE;
fail:
if (pointer_grabbed)
XUngrabPointer (xdpy, timestamp);
if (keyboard_grabbed)
XUngrabKeyboard (xdpy, timestamp);
return FALSE;
}
void
mutter_end_modal_for_plugin (MetaScreen *screen,
MutterPlugin *plugin,
guint32 timestamp)
{
MetaDisplay *display = meta_screen_get_display (screen);
Display *xdpy = meta_display_get_xdisplay (display);
MetaCompositor *compositor = display->compositor;
g_return_if_fail (compositor->modal_plugin == plugin);
XUngrabPointer (xdpy, timestamp);
XUngrabKeyboard (xdpy, timestamp);
display->grab_op = META_GRAB_OP_NONE;
display->grab_window = NULL;
display->grab_screen = NULL;
display->grab_have_pointer = FALSE;
display->grab_have_keyboard = FALSE;
compositor->modal_plugin = NULL;
}
/* This is used when reloading plugins to make sure we don't have
* a left-over modal grab for this screen.
*/
void
mutter_check_end_modal (MetaScreen *screen)
{
MetaDisplay *display = meta_screen_get_display (screen);
MetaCompositor *compositor = display->compositor;
if (compositor->modal_plugin &&
mutter_plugin_get_screen (compositor->modal_plugin) == screen)
{
mutter_end_modal_for_plugin (screen,
compositor->modal_plugin,
CurrentTime);
}
}
void void
meta_compositor_manage_screen (MetaCompositor *compositor, meta_compositor_manage_screen (MetaCompositor *compositor,
MetaScreen *screen) MetaScreen *screen)
@ -513,11 +624,41 @@ meta_compositor_set_updates (MetaCompositor *compositor,
{ {
} }
static gboolean
is_grabbed_event (XEvent *event)
{
switch (event->xany.type)
{
case ButtonPress:
case ButtonRelease:
case EnterNotify:
case LeaveNotify:
case MotionNotify:
case KeyPressMask:
case KeyReleaseMask:
return TRUE;
}
return FALSE;
}
gboolean gboolean
meta_compositor_process_event (MetaCompositor *compositor, meta_compositor_process_event (MetaCompositor *compositor,
XEvent *event, XEvent *event,
MetaWindow *window) MetaWindow *window)
{ {
if (compositor->modal_plugin && is_grabbed_event (event))
{
MutterPluginClass *klass = MUTTER_PLUGIN_GET_CLASS (compositor->modal_plugin);
if (klass->xevent_filter)
klass->xevent_filter (compositor->modal_plugin, event);
/* We always consume events even if the plugin says it didn't handle them;
* exclusive is exclusive */
return TRUE;
}
if (window) if (window)
{ {
MetaCompScreen *info; MetaCompScreen *info;

View File

@ -22,6 +22,7 @@
*/ */
#include "config.h" #include "config.h"
#include "compositor-private.h"
#include "mutter-plugin-manager.h" #include "mutter-plugin-manager.h"
#include "prefs.h" #include "prefs.h"
#include "errors.h" #include "errors.h"
@ -340,6 +341,10 @@ mutter_plugin_manager_reload (MutterPluginManager *plugin_mgr)
* plugins to unload? We are probably not going to have large numbers of * plugins to unload? We are probably not going to have large numbers of
* plugins loaded at the same time, so it might not be worth it. * plugins loaded at the same time, so it might not be worth it.
*/ */
/* Prevent stale grabs on unloaded plugins */
mutter_check_end_modal (plugin_mgr->screen);
mutter_plugin_manager_unload (plugin_mgr); mutter_plugin_manager_unload (plugin_mgr);
return mutter_plugin_manager_load (plugin_mgr); return mutter_plugin_manager_load (plugin_mgr);
} }

View File

@ -467,6 +467,64 @@ mutter_plugin_get_windows (MutterPlugin *plugin)
return mutter_get_windows (priv->screen); return mutter_get_windows (priv->screen);
} }
/**
* mutter_plugin_begin_modal:
* @plugin: a #MutterPlugin
* @grab_window: the X window to grab the keyboard and mouse on
* @cursor: the cursor to use for the pointer grab, or None,
* to use the normal cursor for the grab window and
* its descendants.
* @options: flags that modify the behavior of the modal grab
* @timestamp: the timestamp used for establishing grabs
*
* This function is used to grab the keyboard and mouse for the exclusive
* use of the plugin. Correct operation requires that both the keyboard
* and mouse are grabbed, or thing will break. (In particular, other
* passive X grabs in Mutter can trigger but not be handled by the normal
* keybinding handling code.) However, the plugin can establish the keyboard
* and/or mouse grabs ahead of time and pass in the
* %META_MODAL_POINTER_ALREADY_GRABBED and/or %META_MODAL_KEYBOARD_ALREADY_GRABBED
* options. This facility is provided for two reasons: first to allow using
* this function to establish modality after a passive grab, and second to
* allow using obscure features of XGrabPointer() and XGrabKeyboard() without
* having to add them to this API.
*
* Return value: whether we successfully grabbed the keyboard and
* mouse and made the plugin modal.
*/
gboolean
mutter_plugin_begin_modal (MutterPlugin *plugin,
Window grab_window,
Cursor cursor,
MetaModalOptions options,
guint32 timestamp)
{
MutterPluginPrivate *priv = MUTTER_PLUGIN (plugin)->priv;
return mutter_begin_modal_for_plugin (priv->screen, plugin,
grab_window, cursor, options, timestamp);
}
/**
* mutter_plugin_end_modal
* @plugin: a #MutterPlugin
* @timestamp: the time used for releasing grabs
*
* Ends the modal operation begun with meta_plugin_begin_modal(). This
* ungrabs both the mouse and keyboard even when
* %META_MODAL_POINTER_ALREADY_GRABBED or
* %META_MODAL_KEYBOARD_ALREADY_GRABBED were provided as options
* when beginnning the modal operation.
*/
void
mutter_plugin_end_modal (MutterPlugin *plugin,
guint32 timestamp)
{
MutterPluginPrivate *priv = MUTTER_PLUGIN (plugin)->priv;
mutter_end_modal_for_plugin (priv->screen, plugin, timestamp);
}
Display * Display *
mutter_plugin_get_xdisplay (MutterPlugin *plugin) mutter_plugin_get_xdisplay (MutterPlugin *plugin)
{ {

View File

@ -1199,6 +1199,7 @@ grab_op_is_mouse (MetaGrabOp op)
case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_KEYBOARD_RESIZING_SW:
case META_GRAB_OP_KEYBOARD_RESIZING_NW: case META_GRAB_OP_KEYBOARD_RESIZING_NW:
case META_GRAB_OP_KEYBOARD_MOVING: case META_GRAB_OP_KEYBOARD_MOVING:
case META_GRAB_OP_COMPOSITOR:
return TRUE; return TRUE;
default: default:
@ -1228,6 +1229,7 @@ grab_op_is_keyboard (MetaGrabOp op)
case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING: case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
case META_GRAB_OP_COMPOSITOR:
return TRUE; return TRUE;
default: default:
@ -1670,6 +1672,9 @@ event_callback (XEvent *event,
{ {
case KeyPress: case KeyPress:
case KeyRelease: case KeyRelease:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
/* For key events, it's important to enforce single-handling, or /* For key events, it's important to enforce single-handling, or
* we can get into a confused state. So if a keybinding is * we can get into a confused state. So if a keybinding is
* handled (because it's one of our hot-keys, or because we are * handled (because it's one of our hot-keys, or because we are
@ -1679,12 +1684,14 @@ event_callback (XEvent *event,
bypass_compositor = meta_display_process_key_event (display, window, event); bypass_compositor = meta_display_process_key_event (display, window, event);
break; break;
case ButtonPress: case ButtonPress:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
if (event->xbutton.button == 4 || event->xbutton.button == 5) if (event->xbutton.button == 4 || event->xbutton.button == 5)
{ /* Scrollwheel event, do nothing and deliver event to compositor below */
/* Scrollwheel event, do nothing and deliver event to compositor below break;
*/
} if ((window &&
else if ((window &&
grab_op_is_mouse (display->grab_op) && grab_op_is_mouse (display->grab_op) &&
display->grab_button != (int) event->xbutton.button && display->grab_button != (int) event->xbutton.button &&
display->grab_window == window) || display->grab_window == window) ||
@ -1874,16 +1881,25 @@ event_callback (XEvent *event,
} }
break; break;
case ButtonRelease: case ButtonRelease:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
if (display->grab_window == window && if (display->grab_window == window &&
grab_op_is_mouse (display->grab_op)) grab_op_is_mouse (display->grab_op))
meta_window_handle_mouse_grab_op_event (window, event); meta_window_handle_mouse_grab_op_event (window, event);
break; break;
case MotionNotify: case MotionNotify:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
if (display->grab_window == window && if (display->grab_window == window &&
grab_op_is_mouse (display->grab_op)) grab_op_is_mouse (display->grab_op))
meta_window_handle_mouse_grab_op_event (window, event); meta_window_handle_mouse_grab_op_event (window, event);
break; break;
case EnterNotify: case EnterNotify:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
if (display->grab_window == window && if (display->grab_window == window &&
grab_op_is_mouse (display->grab_op)) grab_op_is_mouse (display->grab_op))
{ {
@ -1976,6 +1992,9 @@ event_callback (XEvent *event,
} }
break; break;
case LeaveNotify: case LeaveNotify:
if (display->grab_op == META_GRAB_OP_COMPOSITOR)
break;
if (display->grab_window == window && if (display->grab_window == window &&
grab_op_is_mouse (display->grab_op)) grab_op_is_mouse (display->grab_op))
meta_window_handle_mouse_grab_op_event (window, event); meta_window_handle_mouse_grab_op_event (window, event);

View File

@ -138,7 +138,10 @@ typedef enum
META_GRAB_OP_CLICKING_ABOVE, META_GRAB_OP_CLICKING_ABOVE,
META_GRAB_OP_CLICKING_UNABOVE, META_GRAB_OP_CLICKING_UNABOVE,
META_GRAB_OP_CLICKING_STICK, META_GRAB_OP_CLICKING_STICK,
META_GRAB_OP_CLICKING_UNSTICK META_GRAB_OP_CLICKING_UNSTICK,
/* Special grab op when the compositor asked for a grab */
META_GRAB_OP_COMPOSITOR
} MetaGrabOp; } MetaGrabOp;
typedef enum typedef enum

View File

@ -255,6 +255,31 @@ void
mutter_plugin_set_stage_input_region (MutterPlugin *plugin, mutter_plugin_set_stage_input_region (MutterPlugin *plugin,
XserverRegion region); XserverRegion region);
/**
* MetaModalOptions:
* @META_MODAL_POINTER_ALREADY_GRABBED: if set the pointer is already
* grabbed by the plugin and should not be grabbed again.
* @META_MODAL_KEYBOARD_ALREADY_GRABBED: if set the keyboard is already
* grabbed by the plugin and should not be grabbed again.
*
* Options that can be provided when calling mutter_plugin_begin_modal().
*/
typedef enum {
META_MODAL_POINTER_ALREADY_GRABBED = 1 << 0,
META_MODAL_KEYBOARD_ALREADY_GRABBED = 1 << 1
} MetaModalOptions;
gboolean
mutter_plugin_begin_modal (MutterPlugin *plugin,
Window grab_window,
Cursor cursor,
MetaModalOptions options,
guint32 timestamp);
void
mutter_plugin_end_modal (MutterPlugin *plugin,
guint32 timestamp);
GList * GList *
mutter_plugin_get_windows (MutterPlugin *plugin); mutter_plugin_get_windows (MutterPlugin *plugin);