[gimp/gtk3-port: 245/249] app: make input devices, grabs and therefore generally tools work again



commit e8afc02b90a2612898612a6089baa2d45b598b4f
Author: Michael Natterer <mitch gimp org>
Date:   Sun Feb 27 16:38:51 2011 +0100

    app: make input devices, grabs and therefore generally tools work again
    
    - add new "device from event" apparatus that works on GTK+ 3.x
    - fix the active device selection mechanism
    - use the new device grabbing functions
    - make sure we don't process events while we have a grab on
      another device
    - compensate for some really obscure (and likely broken) behavior
      of XI2, it feels like we are the first real users...

 app/display/gimpdisplayshell-grab.c        |  158 ++++++++++++++++++++--------
 app/display/gimpdisplayshell-tool-events.c |   94 ++++++++++++++---
 app/display/gimpdisplayshell.h             |    7 +-
 app/widgets/gimpdevices.c                  |  116 +++++++++++++++++----
 app/widgets/gimpdevices.h                  |   34 ++++---
 5 files changed, 311 insertions(+), 98 deletions(-)
---
diff --git a/app/display/gimpdisplayshell-grab.c b/app/display/gimpdisplayshell-grab.c
index 1320a47..ec77eb6 100644
--- a/app/display/gimpdisplayshell-grab.c
+++ b/app/display/gimpdisplayshell-grab.c
@@ -23,58 +23,107 @@
 
 #include "display-types.h"
 
-#include "widgets/gimpdeviceinfo.h"
 #include "widgets/gimpdevices.h"
-#include "widgets/gimpdevicemanager.h"
 
 #include "gimpdisplay.h"
 #include "gimpdisplayshell.h"
 #include "gimpdisplayshell-grab.h"
 
 
+static GdkDevice *
+get_associated_pointer (GdkDevice *device)
+{
+  switch (gdk_device_get_device_type (device))
+    {
+    case GDK_DEVICE_TYPE_SLAVE:
+      device = gdk_device_get_associated_device (device);
+      break;
+
+    case GDK_DEVICE_TYPE_FLOATING:
+      {
+        GdkDisplay       *display = gdk_device_get_display (device);
+        GdkDeviceManager *manager = gdk_display_get_device_manager (display);
+
+        return gdk_device_manager_get_client_pointer (manager);
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    device = gdk_device_get_associated_device (device);
+
+  return device;
+}
+
+static GdkDevice *
+get_associated_keyboard (GdkDevice *device)
+{
+  switch (gdk_device_get_device_type (device))
+    {
+    case GDK_DEVICE_TYPE_SLAVE:
+      device = gdk_device_get_associated_device (device);
+      break;
+
+    case GDK_DEVICE_TYPE_FLOATING:
+      {
+        GdkDisplay       *display = gdk_device_get_display (device);
+        GdkDeviceManager *manager = gdk_display_get_device_manager (display);
+
+        device = gdk_device_manager_get_client_pointer (manager);
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
+    device = gdk_device_get_associated_device (device);
+
+  return device;
+}
+
 gboolean
 gimp_display_shell_pointer_grab (GimpDisplayShell *shell,
                                  GdkEvent         *event,
                                  GdkEventMask      event_mask)
 {
-  GdkGrabStatus status;
+  GdkDevice     *device;
+  GdkDevice     *source_device;
+  GdkGrabStatus  status;
 
   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
-  g_return_val_if_fail (shell->pointer_grabbed == FALSE, FALSE);
+  g_return_val_if_fail (shell->grab_pointer == NULL, FALSE);
+
+  source_device = gimp_devices_get_from_event (shell->display->gimp,
+                                               event, &device);
 
-  status = gdk_pointer_grab (gtk_widget_get_window (shell->canvas),
-                             FALSE, event_mask, NULL, NULL,
-                             gdk_event_get_time (event));
+  if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    {
+      device        = get_associated_pointer (device);
+      source_device = NULL;
+    }
+
+  status = gdk_device_grab (device,
+                            gtk_widget_get_window (shell->canvas),
+                            GDK_OWNERSHIP_APPLICATION,
+                            FALSE, event_mask, NULL,
+                            gdk_event_get_time (event));
 
   if (status == GDK_GRAB_SUCCESS)
     {
-      shell->pointer_grabbed = TRUE;
+      shell->grab_pointer        = device;
+      shell->grab_pointer_source = source_device;
 
       return TRUE;
     }
-  else if (status == GDK_GRAB_ALREADY_GRABBED)
-    {
-      GimpDeviceManager *manager;
-      GdkDisplay        *gdk_display;
-
-      manager = gimp_devices_get_manager (shell->display->gimp);
-      gdk_display = gtk_widget_get_display (GTK_WIDGET (shell));
-
-      /*  EEK: trying to grab an extended device always returns
-       *  ALREADY_GRABBED, so simply assume the grab succeeded anyway
-       */
-      if (gimp_device_manager_get_current_device (manager)->device !=
-          gdk_display_get_core_pointer (gdk_display))
-        {
-          shell->pointer_grabbed = TRUE;
-
-          return TRUE;
-        }
-    }
 
-  g_printerr ("%s: gdk_pointer_grab failed with status %d\n",
-              G_STRFUNC, status);
+  g_printerr ("%s: gdk_device_grab(%s) failed with status %d\n",
+              G_STRFUNC, gdk_device_get_name (device), status);
 
   return FALSE;
 }
@@ -85,37 +134,55 @@ gimp_display_shell_pointer_ungrab (GimpDisplayShell *shell,
 {
   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
   g_return_if_fail (event != NULL);
-  g_return_if_fail (shell->pointer_grabbed == TRUE);
+  g_return_if_fail (shell->grab_pointer != NULL);
 
-  gdk_display_pointer_ungrab (gtk_widget_get_display (shell->canvas),
-                              gdk_event_get_time (event));
+  gdk_device_ungrab (shell->grab_pointer,
+                     gdk_event_get_time (event));
 
-  shell->pointer_grabbed = FALSE;
+  shell->grab_pointer        = NULL;
+  shell->grab_pointer_source = NULL;
 }
 
 gboolean
 gimp_display_shell_keyboard_grab (GimpDisplayShell *shell,
                                   GdkEvent         *event)
 {
-  GdkGrabStatus status;
+  GdkDevice     *device;
+  GdkDevice     *source_device;
+  GdkGrabStatus  status;
 
   g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), FALSE);
   g_return_val_if_fail (event != NULL, FALSE);
-  g_return_val_if_fail (shell->keyboard_grabbed == FALSE, FALSE);
+  g_return_val_if_fail (shell->grab_keyboard == NULL, FALSE);
+
+  source_device = gimp_devices_get_from_event (shell->display->gimp,
+                                               event, &device);
+
+  if (gdk_device_get_source (device) != GDK_SOURCE_KEYBOARD)
+    {
+      device        = get_associated_keyboard (device);
+      source_device = NULL;
+    }
 
-  status = gdk_keyboard_grab (gtk_widget_get_window (shell->canvas),
-                              FALSE,
-                              gdk_event_get_time (event));
+  status = gdk_device_grab (device,
+                            gtk_widget_get_window (shell->canvas),
+                            GDK_OWNERSHIP_APPLICATION,
+                            FALSE,
+                            GDK_KEY_PRESS_MASK   |
+                            GDK_KEY_RELEASE_MASK |
+                            GDK_FOCUS_CHANGE_MASK,
+                            NULL, gdk_event_get_time (event));
 
   if (status == GDK_GRAB_SUCCESS)
     {
-      shell->keyboard_grabbed = TRUE;
+      shell->grab_keyboard        = device;
+      shell->grab_keyboard_source = source_device;
 
       return TRUE;
     }
 
-  g_printerr ("%s: gdk_keyboard_grab failed with status %d\n",
-              G_STRFUNC, status);
+  g_printerr ("%s: gdk_device_grab(%s) failed with status %d\n",
+              G_STRFUNC, gdk_device_get_name (device), status);
 
   return FALSE;
 }
@@ -126,10 +193,11 @@ gimp_display_shell_keyboard_ungrab (GimpDisplayShell *shell,
 {
   g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell));
   g_return_if_fail (event != NULL);
-  g_return_if_fail (shell->keyboard_grabbed == TRUE);
+  g_return_if_fail (shell->grab_keyboard != NULL);
 
-  gdk_display_keyboard_ungrab (gtk_widget_get_display (shell->canvas),
-                               gdk_event_get_time (event));
+  gdk_device_ungrab (shell->grab_keyboard,
+                     gdk_event_get_time (event));
 
-  shell->keyboard_grabbed = FALSE;
+  shell->grab_keyboard        = NULL;
+  shell->grab_keyboard_source = NULL;
 }
diff --git a/app/display/gimpdisplayshell-tool-events.c b/app/display/gimpdisplayshell-tool-events.c
index 0e1e6bf..5f6a786 100644
--- a/app/display/gimpdisplayshell-tool-events.c
+++ b/app/display/gimpdisplayshell-tool-events.c
@@ -70,6 +70,9 @@ static GdkModifierType
 static void       gimp_display_shell_proximity_in             (GimpDisplayShell *shell);
 static void       gimp_display_shell_proximity_out            (GimpDisplayShell *shell);
 
+static gboolean   gimp_display_shell_check_device             (GimpDisplayShell *shell,
+                                                               GdkEvent         *event,
+                                                               gboolean         *device_changed);
 static void       gimp_display_shell_check_device_cursor      (GimpDisplayShell *shell);
 
 static void       gimp_display_shell_start_scrolling          (GimpDisplayShell *shell,
@@ -319,12 +322,8 @@ gimp_display_shell_canvas_tool_events (GtkWidget        *canvas,
   GIMP_LOG (TOOL_EVENTS, "event (display %p): %s",
             display, gimp_print_event (event));
 
-  /*  Find out what device the event occurred upon  */
-  if (! gimp->busy && gimp_devices_check_change (gimp, event))
-    {
-      gimp_display_shell_check_device_cursor (shell);
-      device_changed = TRUE;
-    }
+  if (gimp_display_shell_check_device (shell, event, &device_changed))
+    return TRUE;
 
   gimp_display_shell_get_event_coords (shell, event,
                                        &display_coords,
@@ -418,6 +417,13 @@ gimp_display_shell_canvas_tool_events (GtkWidget        *canvas,
             if (G_UNLIKELY (gtk_widget_has_focus (canvas)))
               g_warning ("%s: FOCUS_OUT but canvas has focus", G_STRFUNC);
 
+            if (shell->grab_keyboard)
+              {
+                g_printerr ("%s: ignoring FOCUS_OUT while we have a grab\n",
+                            G_STRFUNC);
+                return TRUE;
+              }
+
             /*  reset it here to be prepared for the next
              *  FOCUS_IN / BUTTON_PRESS confusion
              */
@@ -688,12 +694,12 @@ gimp_display_shell_canvas_tool_events (GtkWidget        *canvas,
           case 1:
             state &= ~GDK_BUTTON1_MASK;
 
-            if (! shell->space_pressed && ! shell->space_release_pending)
-              gimp_display_shell_keyboard_ungrab (shell, event);
-
-            gimp_display_shell_pointer_ungrab (shell, event);
-
-            gtk_grab_add (canvas);
+            /*  If we don't have a grab, this is a release paired with
+             *  a button press we intentionally ignored because we had
+             *  a grab on another device at the time of the press
+             */
+            if (! shell->grab_pointer)
+              return TRUE;
 
             if (active_tool &&
                 (! gimp_image_is_empty (image) ||
@@ -716,7 +722,10 @@ gimp_display_shell_canvas_tool_events (GtkWidget        *canvas,
              */
             gimp_display_shell_update_focus (shell, &image_coords, state);
 
-            gtk_grab_remove (canvas);
+            if (! shell->space_pressed && ! shell->space_release_pending)
+              gimp_display_shell_keyboard_ungrab (shell, event);
+
+            gimp_display_shell_pointer_ungrab (shell, event);
 
             if (shell->space_release_pending)
               gimp_display_shell_space_released (shell, event);
@@ -1398,6 +1407,61 @@ gimp_display_shell_proximity_out (GimpDisplayShell *shell)
     }
 }
 
+static gboolean
+gimp_display_shell_check_device (GimpDisplayShell *shell,
+                                 GdkEvent         *event,
+                                 gboolean         *device_changed)
+{
+  Gimp      *gimp = gimp_display_get_gimp (shell->display);
+  GdkDevice *device;
+  GdkDevice *grab_device;
+
+  /*  Find out what device the event occurred upon  */
+  device = gimp_devices_get_from_event (gimp, event, &grab_device);
+
+  if (device)
+    {
+      /*  While we have a grab, ignore all events from all other devices
+       *  of the same type
+       */
+      if (event->type == GDK_KEY_PRESS   ||
+          event->type == GDK_KEY_RELEASE ||
+          event->type == GDK_FOCUS_CHANGE)
+        {
+          if ((shell->grab_keyboard && (shell->grab_keyboard != grab_device)) ||
+              (shell->grab_keyboard_source && (shell->grab_keyboard_source != device)))
+            {
+              GIMP_LOG (TOOL_EVENTS,
+                        "ignoring key event from '%s' while waiting for event from '%s'\n",
+                        gdk_device_get_name (device),
+                        gdk_device_get_name (shell->grab_keyboard_source));
+              return TRUE;
+            }
+        }
+      else
+        {
+          if ((shell->grab_pointer && (shell->grab_pointer != grab_device)) ||
+              (shell->grab_pointer_source && (shell->grab_pointer_source != device)))
+            {
+              GIMP_LOG (TOOL_EVENTS,
+                        "ignoring pointer event from '%s' while waiting for event from '%s'\n",
+                        gdk_device_get_name (device),
+                        gdk_device_get_name (shell->grab_pointer_source));
+              return TRUE;
+            }
+        }
+
+      if (! gimp->busy && gimp_devices_check_change (gimp, device))
+        {
+          gimp_display_shell_check_device_cursor (shell);
+
+          *device_changed = TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
 static void
 gimp_display_shell_check_device_cursor (GimpDisplayShell *shell)
 {
@@ -1434,8 +1498,6 @@ gimp_display_shell_start_scrolling (GimpDisplayShell *shell,
   shell->scroll_start_y = y + shell->offset_y;
 
   gimp_display_shell_set_override_cursor (shell, GDK_FLEUR);
-
-  gtk_grab_add (shell->canvas);
 }
 
 static void
@@ -1448,8 +1510,6 @@ gimp_display_shell_stop_scrolling (GimpDisplayShell *shell)
   shell->scroll_start_y = 0;
 
   gimp_display_shell_unset_override_cursor (shell);
-
-  gtk_grab_remove (shell->canvas);
 }
 
 static void
diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h
index 1f89087..ab7f68f 100644
--- a/app/display/gimpdisplayshell.h
+++ b/app/display/gimpdisplayshell.h
@@ -184,8 +184,11 @@ struct _GimpDisplayShell
   gboolean           size_allocate_from_configure_event;
 
   /*  the state of gimp_display_shell_tool_events()  */
-  gboolean           pointer_grabbed;
-  gboolean           keyboard_grabbed;
+  GdkDevice         *grab_pointer;
+  GdkDevice         *grab_pointer_source;
+
+  GdkDevice         *grab_keyboard;
+  GdkDevice         *grab_keyboard_source;
 
   gboolean           space_pressed;
   gboolean           space_release_pending;
diff --git a/app/widgets/gimpdevices.c b/app/widgets/gimpdevices.c
index 1799cc5..f541f20 100644
--- a/app/widgets/gimpdevices.c
+++ b/app/widgets/gimpdevices.c
@@ -231,6 +231,89 @@ gimp_devices_get_manager (Gimp *gimp)
   return manager;
 }
 
+GdkDevice *
+gimp_devices_get_from_event (Gimp       *gimp,
+                             GdkEvent   *event,
+                             GdkDevice **grab_device)
+{
+  GdkDevice *device;
+
+  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+  g_return_val_if_fail (event != NULL, NULL);
+
+  device = gdk_event_get_source_device (event);
+
+  /*  initialize the default grab device to the event's device,
+   *  because that is always either a master or a floating device,
+   *  which is the types of devices we can make grabs on without
+   *  disturbing side effects.
+   */
+  if (grab_device)
+    *grab_device = gdk_event_get_device (event);
+
+  if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD)
+    {
+      switch (gdk_device_get_device_type (device))
+        {
+        case GDK_DEVICE_TYPE_MASTER:
+          /*  this happens on focus synthesized focus changed events,
+           *  and we can't do anything with the returned device, so
+           *  return NULL
+           */
+          return NULL;
+
+        case GDK_DEVICE_TYPE_SLAVE:
+          /*  it makes no sense for us to distinguigh between
+           *  different slave keyboards, so just always return
+           *  their respective master
+           */
+          return gdk_device_get_associated_device (device);
+
+        case GDK_DEVICE_TYPE_FLOATING:
+          /*  we have no way of explicitly enabling floating
+           *  keyboards, so we cannot get their events
+           */
+          g_return_val_if_reached (device);
+       }
+    }
+  else
+    {
+      switch (gdk_device_get_device_type (device))
+        {
+        case GDK_DEVICE_TYPE_MASTER:
+          /*  this can only happen for synthesized events which have
+           *  no actual source, so return NULL to indicate that we
+           *  cannot do anything with the event's device information
+           */
+          return NULL;
+
+        case GDK_DEVICE_TYPE_SLAVE:
+          /*  this is the tricky part: we do want to distingiugh slave
+           *  devices, but only if we actually enabled them ourselves
+           *  explicitely (like the pens of a tablet); however we
+           *  usually don't enable the different incarnations of the
+           *  mouse itself (like touchpad, trackpoint, usb mouse
+           *  etc.), so for these return their respective master so
+           *  its settings are used
+           */
+          if (gdk_device_get_mode (device) == GDK_MODE_DISABLED)
+            {
+              return gdk_device_get_associated_device (device);
+            }
+
+          return device;
+
+        case GDK_DEVICE_TYPE_FLOATING:
+          /*  we only get events for floating devices which have
+           *  enabled ourselves, so return the floating device
+           */
+          return device;
+        }
+    }
+
+  g_return_val_if_reached (device);
+}
+
 void
 gimp_devices_add_widget (Gimp      *gimp,
                          GtkWidget *widget)
@@ -252,44 +335,39 @@ gimp_devices_check_callback (GtkWidget *widget,
   g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
 
   if (! gimp->busy)
-    gimp_devices_check_change (gimp, event);
+    {
+      GdkDevice *device;
+
+      device = gimp_devices_get_from_event (gimp, event, NULL);
+
+      if (device)
+        gimp_devices_check_change (gimp, device);
+    }
 
   return FALSE;
 }
 
 gboolean
-gimp_devices_check_change (Gimp     *gimp,
-                           GdkEvent *event)
+gimp_devices_check_change (Gimp      *gimp,
+                           GdkDevice *device)
 {
   GimpDeviceManager *manager;
-  GdkDevice         *device;
   GimpDeviceInfo    *device_info;
 
   g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  g_return_val_if_fail (GDK_IS_DEVICE (device), FALSE);
 
   manager = gimp_devices_get_manager (gimp);
 
   g_return_val_if_fail (GIMP_IS_DEVICE_MANAGER (manager), FALSE);
 
-  device = gdk_event_get_source_device (event);
-
-  if (! device)
-    device = gimp_device_manager_get_current_device (manager)->device;
-
   device_info = gimp_device_info_get_by_device (device);
 
   if (! device_info)
-    {
-      device = gdk_event_get_device (event);
-
-      if (! device)
-        device = gimp_device_manager_get_current_device (manager)->device;
-
-      device_info = gimp_device_info_get_by_device (device);
-    }
+    device_info = gimp_device_manager_get_current_device (manager);
 
-  if (device_info && device_info != gimp_device_manager_get_current_device (manager))
+  if (device_info &&
+      device_info != gimp_device_manager_get_current_device (manager))
     {
       gimp_device_manager_set_current_device (manager, device_info);
       return TRUE;
diff --git a/app/widgets/gimpdevices.h b/app/widgets/gimpdevices.h
index a2cdbc8..060f38b 100644
--- a/app/widgets/gimpdevices.h
+++ b/app/widgets/gimpdevices.h
@@ -19,26 +19,30 @@
 #define __GIMP_DEVICES_H__
 
 
-void                gimp_devices_init           (Gimp      *gimp);
-void                gimp_devices_exit           (Gimp      *gimp);
+void                gimp_devices_init           (Gimp       *gimp);
+void                gimp_devices_exit           (Gimp       *gimp);
 
-void                gimp_devices_restore        (Gimp      *gimp);
-void                gimp_devices_save           (Gimp      *gimp,
-                                                 gboolean   always_save);
+void                gimp_devices_restore        (Gimp       *gimp);
+void                gimp_devices_save           (Gimp       *gimp,
+                                                 gboolean    always_save);
 
-gboolean            gimp_devices_clear          (Gimp      *gimp,
-                                                 GError   **error);
+gboolean            gimp_devices_clear          (Gimp       *gimp,
+                                                 GError    **error);
 
-GimpDeviceManager * gimp_devices_get_manager    (Gimp      *gimp);
+GimpDeviceManager * gimp_devices_get_manager    (Gimp       *gimp);
 
-void                gimp_devices_add_widget     (Gimp      *gimp,
-                                                 GtkWidget *widget);
+GdkDevice         * gimp_devices_get_from_event (Gimp       *gimp,
+                                                 GdkEvent   *event,
+                                                 GdkDevice **grab_device);
 
-gboolean            gimp_devices_check_callback (GtkWidget *widget,
-                                                 GdkEvent  *event,
-                                                 Gimp      *gimp);
-gboolean            gimp_devices_check_change   (Gimp      *gimp,
-                                                 GdkEvent  *event);
+void                gimp_devices_add_widget     (Gimp       *gimp,
+                                                 GtkWidget  *widget);
+
+gboolean            gimp_devices_check_callback (GtkWidget  *widget,
+                                                 GdkEvent   *event,
+                                                 Gimp       *gimp);
+gboolean            gimp_devices_check_change   (Gimp       *gimp,
+                                                 GdkDevice  *device);
 
 
 #endif /* __GIMP_DEVICES_H__ */



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