[mutter] Add a modal mode for plugins



commit 67682a2683fead792ca3027ed72a1c9ef4cf281b
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Wed Aug 12 00:12:52 2009 -0400

    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

 src/compositor/compositor-private.h    |   14 +++
 src/compositor/compositor.c            |  141 ++++++++++++++++++++++++++++++++
 src/compositor/mutter-plugin-manager.c |    5 +
 src/compositor/mutter-plugin.c         |   58 +++++++++++++
 src/core/display.c                     |   29 ++++++-
 src/include/common.h                   |    5 +-
 src/include/mutter-plugin.h            |   25 ++++++
 7 files changed, 271 insertions(+), 6 deletions(-)
---
diff --git a/src/compositor/compositor-private.h b/src/compositor/compositor-private.h
index 52e656b..9a57030 100644
--- a/src/compositor/compositor-private.h
+++ b/src/compositor/compositor-private.h
@@ -23,6 +23,8 @@ struct _MetaCompositor
 
   ClutterActor   *shadow_src;
 
+  MutterPlugin   *modal_plugin;
+
   gboolean        show_redraw : 1;
   gboolean        debug       : 1;
   gboolean        no_mipmaps  : 1;
@@ -51,4 +53,16 @@ void mutter_set_stage_input_region     (MetaScreen    *screen,
                                         XserverRegion  region);
 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 */
diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c
index 12f2aa2..37bb24c 100644
--- a/src/compositor/compositor.c
+++ b/src/compositor/compositor.c
@@ -350,6 +350,117 @@ mutter_empty_stage_input_region (MetaScreen *screen)
   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
 meta_compositor_manage_screen (MetaCompositor *compositor,
                                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
 meta_compositor_process_event (MetaCompositor *compositor,
                                XEvent         *event,
                                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)
     {
       MetaCompScreen *info;
diff --git a/src/compositor/mutter-plugin-manager.c b/src/compositor/mutter-plugin-manager.c
index dff5122..26e6697 100644
--- a/src/compositor/mutter-plugin-manager.c
+++ b/src/compositor/mutter-plugin-manager.c
@@ -22,6 +22,7 @@
  */
 
 #include "config.h"
+#include "compositor-private.h"
 #include "mutter-plugin-manager.h"
 #include "prefs.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 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);
   return mutter_plugin_manager_load (plugin_mgr);
 }
diff --git a/src/compositor/mutter-plugin.c b/src/compositor/mutter-plugin.c
index 18763da..83b936f 100644
--- a/src/compositor/mutter-plugin.c
+++ b/src/compositor/mutter-plugin.c
@@ -467,6 +467,64 @@ mutter_plugin_get_windows (MutterPlugin *plugin)
   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 *
 mutter_plugin_get_xdisplay (MutterPlugin *plugin)
 {
diff --git a/src/core/display.c b/src/core/display.c
index 1008544..3b09645 100644
--- a/src/core/display.c
+++ b/src/core/display.c
@@ -1199,6 +1199,7 @@ grab_op_is_mouse (MetaGrabOp op)
     case META_GRAB_OP_KEYBOARD_RESIZING_SW:
     case META_GRAB_OP_KEYBOARD_RESIZING_NW:
     case META_GRAB_OP_KEYBOARD_MOVING:
+    case META_GRAB_OP_COMPOSITOR:
       return TRUE;
 
     default:
@@ -1228,6 +1229,7 @@ grab_op_is_keyboard (MetaGrabOp op)
     case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK:
     case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP:
     case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING:
+    case META_GRAB_OP_COMPOSITOR:
       return TRUE;
 
     default:
@@ -1670,6 +1672,9 @@ event_callback (XEvent   *event,
     {
     case KeyPress:
     case KeyRelease:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       /* For key events, it's important to enforce single-handling, or
        * 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
@@ -1679,12 +1684,14 @@ event_callback (XEvent   *event,
       bypass_compositor = meta_display_process_key_event (display, window, event);
       break;
     case ButtonPress:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       if (event->xbutton.button == 4 || event->xbutton.button == 5)
-        {
-          /* Scrollwheel event, do nothing and deliver event to compositor below
-           */
-        }
-      else if ((window &&
+        /* Scrollwheel event, do nothing and deliver event to compositor below */
+        break;
+
+      if ((window &&
            grab_op_is_mouse (display->grab_op) &&
            display->grab_button != (int) event->xbutton.button &&
            display->grab_window == window) ||
@@ -1874,16 +1881,25 @@ event_callback (XEvent   *event,
         }
       break;
     case ButtonRelease:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       if (display->grab_window == window &&
           grab_op_is_mouse (display->grab_op))
         meta_window_handle_mouse_grab_op_event (window, event);
       break;
     case MotionNotify:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       if (display->grab_window == window &&
           grab_op_is_mouse (display->grab_op))
         meta_window_handle_mouse_grab_op_event (window, event);
       break;
     case EnterNotify:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       if (display->grab_window == window &&
           grab_op_is_mouse (display->grab_op))
         {
@@ -1976,6 +1992,9 @@ event_callback (XEvent   *event,
         }
       break;
     case LeaveNotify:
+      if (display->grab_op == META_GRAB_OP_COMPOSITOR)
+        break;
+
       if (display->grab_window == window &&
           grab_op_is_mouse (display->grab_op))
         meta_window_handle_mouse_grab_op_event (window, event);
diff --git a/src/include/common.h b/src/include/common.h
index 1b89c62..b4afc83 100644
--- a/src/include/common.h
+++ b/src/include/common.h
@@ -138,7 +138,10 @@ typedef enum
   META_GRAB_OP_CLICKING_ABOVE,
   META_GRAB_OP_CLICKING_UNABOVE,
   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;
 
 typedef enum
diff --git a/src/include/mutter-plugin.h b/src/include/mutter-plugin.h
index b844fde..282b272 100644
--- a/src/include/mutter-plugin.h
+++ b/src/include/mutter-plugin.h
@@ -255,6 +255,31 @@ void
 mutter_plugin_set_stage_input_region (MutterPlugin *plugin,
                                       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 *
 mutter_plugin_get_windows (MutterPlugin *plugin);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]