[mutter] clutter: Add pointer accessibility features



commit db11a37a68f469e374c3e353cf20dca701f053ab
Author: Olivier Fourdan <ofourdan redhat com>
Date:   Wed Mar 6 09:04:37 2019 +0100

    clutter: Add pointer accessibility features
    
    Add support for click assist, namely simulated secondary click (on a
    long primary button press) and hover click support (simulate a click when
    the pointer remains static for some time).
    
    https://gitlab.gnome.org/GNOME/mutter/merge_requests/512

 clutter/clutter/clutter-device-manager-private.h   |  17 +
 clutter/clutter/clutter-device-manager.c           |  88 +++
 clutter/clutter/clutter-device-manager.h           |  34 ++
 .../clutter/clutter-input-pointer-a11y-private.h   |  42 ++
 clutter/clutter/clutter-input-pointer-a11y.c       | 669 +++++++++++++++++++++
 clutter/clutter/meson.build                        |   2 +
 6 files changed, 852 insertions(+)
---
diff --git a/clutter/clutter/clutter-device-manager-private.h 
b/clutter/clutter/clutter-device-manager-private.h
index d08e66ba4..712dd404a 100644
--- a/clutter/clutter/clutter-device-manager-private.h
+++ b/clutter/clutter/clutter-device-manager-private.h
@@ -69,6 +69,22 @@ typedef struct _ClutterTouchInfo
   gfloat current_y;
 } ClutterTouchInfo;
 
+typedef struct _ClutterPtrA11yData
+{
+  int n_btn_pressed;
+  float current_x;
+  float current_y;
+
+  float dwell_x;
+  float dwell_y;
+  gboolean dwell_drag_started;
+  gboolean dwell_gesture_started;
+  guint dwell_timer;
+
+  guint secondary_click_timer;
+  gboolean secondary_click_triggered;
+} ClutterPtrA11yData;
+
 struct _ClutterInputDevice
 {
   GObject parent_instance;
@@ -146,6 +162,7 @@ struct _ClutterInputDevice
 
   /* Accessiblity */
   ClutterVirtualInputDevice *accessibility_virtual_device;
+  ClutterPtrA11yData *ptr_a11y_data;
 };
 
 typedef void (*ClutterEmitInputDeviceEvent) (ClutterEvent       *event,
diff --git a/clutter/clutter/clutter-device-manager.c b/clutter/clutter/clutter-device-manager.c
index 4e527182b..fd485a0ae 100644
--- a/clutter/clutter/clutter-device-manager.c
+++ b/clutter/clutter/clutter-device-manager.c
@@ -47,6 +47,7 @@
 #include "clutter-stage-private.h"
 #include "clutter-virtual-input-device.h"
 #include "clutter-input-device-tool.h"
+#include "clutter-input-pointer-a11y-private.h"
 
 struct _ClutterDeviceManagerPrivate
 {
@@ -55,6 +56,8 @@ struct _ClutterDeviceManagerPrivate
 
   /* Keyboard a11y */
   ClutterKbdA11ySettings kbd_a11y_settings;
+  /* Pointer a11y */
+  ClutterPointerA11ySettings pointer_a11y_settings;
 };
 
 enum
@@ -643,3 +646,88 @@ clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_man
 
   *settings = device_manager->priv->kbd_a11y_settings;
 }
+
+static gboolean
+are_pointer_a11y_settings_equal (ClutterPointerA11ySettings *a,
+                                 ClutterPointerA11ySettings *b)
+{
+  return (memcmp (a, b, sizeof (ClutterPointerA11ySettings)) == 0);
+}
+
+static void
+clutter_device_manager_enable_pointer_a11y (ClutterDeviceManager *device_manager)
+{
+  ClutterInputDevice *core_pointer;
+
+  core_pointer = clutter_device_manager_get_core_device (device_manager,
+                                                         CLUTTER_POINTER_DEVICE);
+
+  _clutter_input_pointer_a11y_add_device (core_pointer);
+}
+
+static void
+clutter_device_manager_disable_pointer_a11y (ClutterDeviceManager *device_manager)
+{
+  ClutterInputDevice *core_pointer;
+
+  core_pointer = clutter_device_manager_get_core_device (device_manager,
+                                                         CLUTTER_POINTER_DEVICE);
+
+  _clutter_input_pointer_a11y_remove_device (core_pointer);
+}
+
+/**
+ * clutter_device_manager_set_pointer_a11y_settings:
+ * @device_manager: a #ClutterDeviceManager
+ * @settings: a pointer to a #ClutterPointerA11ySettings
+ *
+ * Sets the pointer accessibility settings
+ **/
+void
+clutter_device_manager_set_pointer_a11y_settings (ClutterDeviceManager       *device_manager,
+                                                  ClutterPointerA11ySettings *settings)
+{
+  g_return_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager));
+
+  if (are_pointer_a11y_settings_equal (&device_manager->priv->pointer_a11y_settings, settings))
+    return;
+
+  if (device_manager->priv->pointer_a11y_settings.controls == 0 && settings->controls != 0)
+    clutter_device_manager_enable_pointer_a11y (device_manager);
+  else if (device_manager->priv->pointer_a11y_settings.controls != 0 && settings->controls == 0)
+    clutter_device_manager_disable_pointer_a11y (device_manager);
+
+  device_manager->priv->pointer_a11y_settings = *settings;
+}
+
+/**
+ * clutter_device_manager_get_pointer_a11y_settings:
+ * @device_manager: a #ClutterDeviceManager
+ * @settings: a pointer to a #ClutterPointerA11ySettings
+ *
+ * Gets the current pointer accessibility settings
+ **/
+void
+clutter_device_manager_get_pointer_a11y_settings (ClutterDeviceManager       *device_manager,
+                                                  ClutterPointerA11ySettings *settings)
+{
+  g_return_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager));
+
+  *settings = device_manager->priv->pointer_a11y_settings;
+}
+
+/**
+ * clutter_device_manager_set_pointer_a11y_dwell_click_type:
+ * @device_manager: a #ClutterDeviceManager
+ * @click_type: type of click as #ClutterPointerA11yDwellClickType
+ *
+ * Sets the dwell click type
+ **/
+void
+clutter_device_manager_set_pointer_a11y_dwell_click_type (ClutterDeviceManager             *device_manager,
+                                                          ClutterPointerA11yDwellClickType  click_type)
+{
+  g_return_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager));
+
+  device_manager->priv->pointer_a11y_settings.dwell_click_type = click_type;
+}
diff --git a/clutter/clutter/clutter-device-manager.h b/clutter/clutter/clutter-device-manager.h
index 1cbf0307b..5fbd3952b 100644
--- a/clutter/clutter/clutter-device-manager.h
+++ b/clutter/clutter/clutter-device-manager.h
@@ -73,6 +73,27 @@ typedef struct _ClutterKbdA11ySettings
   gint mousekeys_accel_time;
 } ClutterKbdA11ySettings;
 
+/**
+ * ClutterPointerA11ySettings:
+ *
+ * The #ClutterPointerA11ySettings structure contains pointer accessibility
+ * settings
+ *
+ */
+typedef struct _ClutterPointerA11ySettings
+{
+  ClutterPointerA11yFlags controls;
+  ClutterPointerA11yDwellClickType dwell_click_type;
+  ClutterPointerA11yDwellMode dwell_mode;
+  ClutterPointerA11yDwellDirection dwell_gesture_single;
+  ClutterPointerA11yDwellDirection dwell_gesture_double;
+  ClutterPointerA11yDwellDirection dwell_gesture_drag;
+  ClutterPointerA11yDwellDirection dwell_gesture_secondary;
+  gint secondary_click_delay;
+  gint dwell_delay;
+  gint dwell_threshold;
+} ClutterPointerA11ySettings;
+
 /**
  * ClutterDeviceManager:
  *
@@ -152,10 +173,23 @@ ClutterVirtualDeviceType clutter_device_manager_get_supported_virtual_device_typ
 CLUTTER_EXPORT
 void clutter_device_manager_set_kbd_a11y_settings (ClutterDeviceManager   *device_manager,
                                                    ClutterKbdA11ySettings *settings);
+
 CLUTTER_EXPORT
 void clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager   *device_manager,
                                                    ClutterKbdA11ySettings *settings);
 
+CLUTTER_EXPORT
+void clutter_device_manager_set_pointer_a11y_settings (ClutterDeviceManager       *device_manager,
+                                                       ClutterPointerA11ySettings *settings);
+
+CLUTTER_EXPORT
+void clutter_device_manager_get_pointer_a11y_settings (ClutterDeviceManager       *device_manager,
+                                                       ClutterPointerA11ySettings *settings);
+
+CLUTTER_EXPORT
+void clutter_device_manager_set_pointer_a11y_dwell_click_type (ClutterDeviceManager             
*device_manager,
+                                                               ClutterPointerA11yDwellClickType  click_type);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_DEVICE_MANAGER_H__ */
diff --git a/clutter/clutter/clutter-input-pointer-a11y-private.h 
b/clutter/clutter/clutter-input-pointer-a11y-private.h
new file mode 100644
index 000000000..6648909fc
--- /dev/null
+++ b/clutter/clutter/clutter-input-pointer-a11y-private.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Olivier Fourdan <ofourdan redhat com>
+ */
+
+#ifndef __CLUTTER_INPUT_POINTER_A11Y_H__
+#define __CLUTTER_INPUT_POINTER_A11Y_H__
+
+#include <clutter/clutter-types.h>
+#include "clutter-enum-types.h"
+
+G_BEGIN_DECLS
+
+void _clutter_input_pointer_a11y_add_device      (ClutterInputDevice   *device);
+void _clutter_input_pointer_a11y_remove_device   (ClutterInputDevice   *device);
+void _clutter_input_pointer_a11y_on_motion_event (ClutterInputDevice   *device,
+                                                  float                 x,
+                                                  float                 y);
+void _clutter_input_pointer_a11y_on_button_event (ClutterInputDevice   *device,
+                                                  int                   button,
+                                                  gboolean              pressed);
+gboolean _clutter_is_input_pointer_a11y_enabled  (ClutterInputDevice     *device);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_INPUT_POINTER_A11Y_H__ */
diff --git a/clutter/clutter/clutter-input-pointer-a11y.c b/clutter/clutter/clutter-input-pointer-a11y.c
new file mode 100644
index 000000000..e55e0a6a9
--- /dev/null
+++ b/clutter/clutter/clutter-input-pointer-a11y.c
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2019 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Olivier Fourdan <ofourdan redhat com>
+ *
+ * This reimplements in Clutter the same behavior as mousetweaks original
+ * implementation by Gerd Kohlberger <gerdko gmail com>
+ * mousetweaks Copyright (C) 2007-2010 Gerd Kohlberger <gerdko gmail com>
+ */
+
+#include "clutter-build-config.h"
+
+#include "clutter-device-manager.h"
+#include "clutter-device-manager-private.h"
+#include "clutter-enum-types.h"
+#include "clutter-input-device.h"
+#include "clutter-input-pointer-a11y-private.h"
+#include "clutter-main.h"
+#include "clutter-virtual-input-device.h"
+
+static gboolean
+is_secondary_click_enabled (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return (settings.controls & CLUTTER_A11Y_SECONDARY_CLICK_ENABLED);
+}
+
+static gboolean
+is_dwell_click_enabled (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return (settings.controls & CLUTTER_A11Y_DWELL_ENABLED);
+}
+
+static unsigned int
+get_secondary_click_delay (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return settings.secondary_click_delay;
+}
+
+static unsigned int
+get_dwell_delay (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return settings.dwell_delay;
+}
+
+static unsigned int
+get_dwell_threshold (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return settings.dwell_threshold;
+}
+
+static ClutterPointerA11yDwellMode
+get_dwell_mode (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  return settings.dwell_mode;
+}
+
+static ClutterPointerA11yDwellClickType
+get_dwell_click_type (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+#
+  return settings.dwell_click_type;
+}
+
+static ClutterPointerA11yDwellClickType
+get_dwell_click_type_for_direction (ClutterInputDevice               *device,
+                                    ClutterPointerA11yDwellDirection  direction)
+{
+  ClutterPointerA11ySettings settings;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  if (direction == settings.dwell_gesture_single)
+    return CLUTTER_A11Y_DWELL_CLICK_TYPE_PRIMARY;
+  else if (direction == settings.dwell_gesture_double)
+    return CLUTTER_A11Y_DWELL_CLICK_TYPE_DOUBLE;
+  else if (direction == settings.dwell_gesture_drag)
+    return CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG;
+  else if (direction == settings.dwell_gesture_secondary)
+    return CLUTTER_A11Y_DWELL_CLICK_TYPE_SECONDARY;
+
+  return CLUTTER_A11Y_DWELL_CLICK_TYPE_NONE;
+}
+
+static void
+emit_button_press (ClutterInputDevice *device,
+                   gint                button)
+{
+  clutter_virtual_input_device_notify_button (device->accessibility_virtual_device,
+                                              g_get_monotonic_time (),
+                                              button,
+                                              CLUTTER_BUTTON_STATE_PRESSED);
+}
+
+static void
+emit_button_release (ClutterInputDevice *device,
+                     gint                button)
+{
+  clutter_virtual_input_device_notify_button (device->accessibility_virtual_device,
+                                              g_get_monotonic_time (),
+                                              button,
+                                              CLUTTER_BUTTON_STATE_RELEASED);
+}
+
+static void
+emit_button_click (ClutterInputDevice *device,
+                   gint                button)
+{
+  emit_button_press (device, button);
+  emit_button_release (device, button);
+}
+
+static void
+restore_dwell_position (ClutterInputDevice *device)
+{
+  clutter_virtual_input_device_notify_absolute_motion (device->accessibility_virtual_device,
+                                                       g_get_monotonic_time (),
+                                                       device->ptr_a11y_data->dwell_x,
+                                                       device->ptr_a11y_data->dwell_y);
+}
+
+static gboolean
+trigger_secondary_click (gpointer data)
+{
+  ClutterInputDevice *device = data;
+
+  device->ptr_a11y_data->secondary_click_triggered = TRUE;
+  device->ptr_a11y_data->secondary_click_timer = 0;
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-stopped",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_SECONDARY_CLICK);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+start_secondary_click_timeout (ClutterInputDevice *device)
+{
+  unsigned int delay = get_secondary_click_delay (device);
+
+  device->ptr_a11y_data->secondary_click_timer =
+    clutter_threads_add_timeout (delay, trigger_secondary_click, device);
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-started",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_SECONDARY_CLICK,
+                         delay);
+}
+
+static void
+stop_secondary_click_timeout (ClutterInputDevice *device)
+{
+  if (device->ptr_a11y_data->secondary_click_timer)
+    {
+      g_source_remove (device->ptr_a11y_data->secondary_click_timer);
+      device->ptr_a11y_data->secondary_click_timer = 0;
+
+      g_signal_emit_by_name (device->device_manager,
+                             "ptr-a11y-timeout-stopped",
+                             device,
+                             CLUTTER_A11Y_TIMEOUT_TYPE_SECONDARY_CLICK);
+    }
+  device->ptr_a11y_data->secondary_click_triggered = FALSE;
+}
+
+static gboolean
+pointer_has_moved (ClutterInputDevice *device)
+{
+  float dx, dy;
+  gint threshold;
+
+  dx = device->ptr_a11y_data->dwell_x - device->ptr_a11y_data->current_x;
+  dy = device->ptr_a11y_data->dwell_y - device->ptr_a11y_data->current_y;
+  threshold = get_dwell_threshold (device);
+
+  /* Pythagorean theorem */
+  return ((dx * dx) + (dy * dy)) > (threshold * threshold);
+}
+
+static gboolean
+is_secondary_click_pending (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->secondary_click_timer != 0;
+}
+
+static gboolean
+is_secondary_click_triggered (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->secondary_click_triggered;
+}
+
+static gboolean
+is_dwell_click_pending (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->dwell_timer != 0;
+}
+
+static gboolean
+is_dwell_dragging (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->dwell_drag_started;
+}
+
+static gboolean
+is_dwell_gesturing (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->dwell_gesture_started;
+}
+
+static gboolean
+has_button_pressed (ClutterInputDevice *device)
+{
+  return device->ptr_a11y_data->n_btn_pressed > 0;
+}
+
+static gboolean
+should_start_secondary_click_timeout (ClutterInputDevice *device)
+{
+  return !is_dwell_dragging (device);
+}
+
+static gboolean
+should_start_dwell (ClutterInputDevice *device)
+{
+  /* We should trigger a dwell if we've not already started one, and if
+   * no button is currently pressed or we are in the middle of a dwell
+   * drag action.
+   */
+  return !is_dwell_click_pending (device) &&
+         (is_dwell_dragging (device) ||
+          !has_button_pressed (device));
+}
+
+static gboolean
+should_stop_dwell (ClutterInputDevice *device)
+{
+  /* We should stop a dwell if the motion exceeds the threshold, unless
+   * we've started a gesture, because we want to keep the original dwell
+   * location to both detect a gesture and restore the original pointer
+   * location once the gesture is finished.
+   */
+  return pointer_has_moved (device) &&
+         !is_dwell_gesturing (device);
+}
+
+
+static gboolean
+should_update_dwell_position (ClutterInputDevice *device)
+{
+  return !is_dwell_gesturing (device) &&
+         !is_dwell_click_pending (device) &&
+         !is_secondary_click_pending (device);
+}
+
+static void
+update_dwell_click_type (ClutterInputDevice *device)
+{
+  ClutterPointerA11ySettings settings;
+  ClutterPointerA11yDwellClickType dwell_click_type;
+
+  clutter_device_manager_get_pointer_a11y_settings (device->device_manager, &settings);
+
+  dwell_click_type = settings.dwell_click_type;
+  switch (dwell_click_type)
+    {
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_DOUBLE:
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_SECONDARY:
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_MIDDLE:
+      dwell_click_type = CLUTTER_A11Y_DWELL_CLICK_TYPE_PRIMARY;
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG:
+      if (!is_dwell_dragging (device))
+        dwell_click_type = CLUTTER_A11Y_DWELL_CLICK_TYPE_PRIMARY;
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_PRIMARY:
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_NONE:
+    default:
+      break;
+    }
+
+  if (dwell_click_type != settings.dwell_click_type)
+    {
+      settings.dwell_click_type = dwell_click_type;
+      clutter_device_manager_set_pointer_a11y_settings (device->device_manager,
+                                                        &settings);
+
+      g_signal_emit_by_name (device->device_manager,
+                             "ptr-a11y-dwell-click-type-changed",
+                             dwell_click_type);
+    }
+}
+
+static void
+emit_dwell_click (ClutterInputDevice               *device,
+                  ClutterPointerA11yDwellClickType  dwell_click_type)
+{
+  switch (dwell_click_type)
+    {
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_PRIMARY:
+      emit_button_click (device, CLUTTER_BUTTON_PRIMARY);
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_DOUBLE:
+      emit_button_click (device, CLUTTER_BUTTON_PRIMARY);
+      emit_button_click (device, CLUTTER_BUTTON_PRIMARY);
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG:
+      if (is_dwell_dragging (device))
+        {
+          emit_button_release (device, CLUTTER_BUTTON_PRIMARY);
+          device->ptr_a11y_data->dwell_drag_started = FALSE;
+        }
+      else
+        {
+          emit_button_press (device, CLUTTER_BUTTON_PRIMARY);
+          device->ptr_a11y_data->dwell_drag_started = TRUE;
+        }
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_SECONDARY:
+      emit_button_click (device, CLUTTER_BUTTON_SECONDARY);
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_MIDDLE:
+      emit_button_click (device, CLUTTER_BUTTON_MIDDLE);
+      break;
+
+    case CLUTTER_A11Y_DWELL_CLICK_TYPE_NONE:
+    default:
+      break;
+    }
+}
+
+static ClutterPointerA11yDwellDirection
+get_dwell_direction (ClutterInputDevice *device)
+{
+  float dx, dy;
+
+  dx = ABS (device->ptr_a11y_data->dwell_x - device->ptr_a11y_data->current_x);
+  dy = ABS (device->ptr_a11y_data->dwell_y - device->ptr_a11y_data->current_y);
+
+  /* The pointer hasn't moved */
+  if (!pointer_has_moved (device))
+    return CLUTTER_A11Y_DWELL_DIRECTION_NONE;
+
+  if (device->ptr_a11y_data->dwell_x < device->ptr_a11y_data->current_x)
+    {
+      if (dx > dy)
+        return CLUTTER_A11Y_DWELL_DIRECTION_LEFT;
+    }
+  else
+    {
+      if (dx > dy)
+        return CLUTTER_A11Y_DWELL_DIRECTION_RIGHT;
+    }
+
+  if (device->ptr_a11y_data->dwell_y < device->ptr_a11y_data->current_y)
+    return CLUTTER_A11Y_DWELL_DIRECTION_UP;
+
+  return CLUTTER_A11Y_DWELL_DIRECTION_DOWN;
+}
+
+static gboolean
+trigger_clear_dwell_gesture (gpointer data)
+{
+  ClutterInputDevice *device = data;
+
+  device->ptr_a11y_data->dwell_timer = 0;
+  device->ptr_a11y_data->dwell_gesture_started = FALSE;
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+trigger_dwell_gesture (gpointer data)
+{
+  ClutterInputDevice *device = data;
+  ClutterPointerA11yDwellDirection direction;
+  unsigned int delay = get_dwell_delay (device);
+
+  restore_dwell_position (device);
+  direction = get_dwell_direction (device);
+  emit_dwell_click (device,
+                    get_dwell_click_type_for_direction (device,
+                                                        direction));
+
+  /* Do not clear the gesture right away, otherwise we'll start another one */
+  device->ptr_a11y_data->dwell_timer =
+    clutter_threads_add_timeout (delay, trigger_clear_dwell_gesture, device);
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-stopped",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_GESTURE);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+start_dwell_gesture_timeout (ClutterInputDevice *device)
+{
+  unsigned int delay = get_dwell_delay (device);
+
+  device->ptr_a11y_data->dwell_timer =
+    clutter_threads_add_timeout (delay, trigger_dwell_gesture, device);
+  device->ptr_a11y_data->dwell_gesture_started = TRUE;
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-started",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_GESTURE,
+                         delay);
+}
+
+static gboolean
+trigger_dwell_click (gpointer data)
+{
+  ClutterInputDevice *device = data;
+
+  device->ptr_a11y_data->dwell_timer = 0;
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-stopped",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_DWELL);
+
+  if (get_dwell_mode (device) == CLUTTER_A11Y_DWELL_MODE_GESTURE)
+    {
+      if (is_dwell_dragging (device))
+        emit_dwell_click (device, CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG);
+      else
+        start_dwell_gesture_timeout (device);
+    }
+  else
+    {
+      emit_dwell_click (device, get_dwell_click_type (device));
+      update_dwell_click_type (device);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+start_dwell_timeout (ClutterInputDevice *device)
+{
+  unsigned int delay = get_dwell_delay (device);
+
+  device->ptr_a11y_data->dwell_timer =
+    clutter_threads_add_timeout (delay, trigger_dwell_click, device);
+
+  g_signal_emit_by_name (device->device_manager,
+                         "ptr-a11y-timeout-started",
+                         device,
+                         CLUTTER_A11Y_TIMEOUT_TYPE_DWELL,
+                         delay);
+}
+
+static void
+stop_dwell_timeout (ClutterInputDevice *device)
+{
+  if (device->ptr_a11y_data->dwell_timer)
+    {
+      g_source_remove (device->ptr_a11y_data->dwell_timer);
+      device->ptr_a11y_data->dwell_timer = 0;
+      device->ptr_a11y_data->dwell_gesture_started = FALSE;
+
+      g_signal_emit_by_name (device->device_manager,
+                             "ptr-a11y-timeout-stopped",
+                             device,
+                             CLUTTER_A11Y_TIMEOUT_TYPE_DWELL);
+    }
+}
+
+static void
+update_dwell_position (ClutterInputDevice *device)
+{
+  device->ptr_a11y_data->dwell_x = device->ptr_a11y_data->current_x;
+  device->ptr_a11y_data->dwell_y = device->ptr_a11y_data->current_y;
+}
+
+static void
+update_current_position (ClutterInputDevice *device,
+                         float               x,
+                         float               y)
+{
+  device->ptr_a11y_data->current_x = x;
+  device->ptr_a11y_data->current_y = y;
+}
+
+static gboolean
+is_device_core_pointer (ClutterInputDevice *device)
+{
+  ClutterInputDevice *core_pointer;
+
+  core_pointer = clutter_device_manager_get_core_device (device->device_manager,
+                                                         CLUTTER_POINTER_DEVICE);
+  if (core_pointer == NULL)
+    return FALSE;
+
+  return (core_pointer == device);
+}
+
+void
+_clutter_input_pointer_a11y_add_device (ClutterInputDevice *device)
+{
+  if (!is_device_core_pointer (device))
+    return;
+
+  device->accessibility_virtual_device =
+    clutter_device_manager_create_virtual_device (device->device_manager,
+                                                  CLUTTER_POINTER_DEVICE);
+
+  device->ptr_a11y_data = g_new0 (ClutterPtrA11yData, 1);
+}
+
+void
+_clutter_input_pointer_a11y_remove_device (ClutterInputDevice *device)
+{
+  if (!is_device_core_pointer (device))
+    return;
+
+  /* Terminate a drag if started */
+  if (is_dwell_dragging (device))
+    emit_dwell_click (device, CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG);
+
+  stop_dwell_timeout (device);
+  stop_secondary_click_timeout (device);
+
+  g_clear_pointer (&device->ptr_a11y_data, g_free);
+}
+
+void
+_clutter_input_pointer_a11y_on_motion_event (ClutterInputDevice *device,
+                                             float               x,
+                                             float               y)
+{
+  if (!is_device_core_pointer (device))
+    return;
+
+  if (!_clutter_is_input_pointer_a11y_enabled (device))
+    return;
+
+  update_current_position (device, x, y);
+
+  if (is_secondary_click_enabled (device))
+    {
+      if (pointer_has_moved (device))
+        stop_secondary_click_timeout (device);
+    }
+
+  if (is_dwell_click_enabled (device))
+    {
+      if (should_stop_dwell (device))
+        stop_dwell_timeout (device);
+      else if (should_start_dwell (device))
+        start_dwell_timeout (device);
+    }
+
+  if (should_update_dwell_position (device))
+    update_dwell_position (device);
+}
+
+void
+_clutter_input_pointer_a11y_on_button_event (ClutterInputDevice *device,
+                                             int                 button,
+                                             gboolean            pressed)
+{
+  if (!is_device_core_pointer (device))
+    return;
+
+  if (!_clutter_is_input_pointer_a11y_enabled (device))
+    return;
+
+  if (pressed)
+    {
+      device->ptr_a11y_data->n_btn_pressed++;
+
+      if (is_dwell_click_enabled (device))
+        stop_dwell_timeout (device);
+
+      if (is_dwell_dragging (device))
+        stop_dwell_timeout (device);
+
+      if (is_secondary_click_enabled (device))
+        {
+          if (button == CLUTTER_BUTTON_PRIMARY)
+            {
+              if (should_start_secondary_click_timeout (device))
+                start_secondary_click_timeout (device);
+            }
+          else if (is_secondary_click_pending (device))
+            {
+              stop_secondary_click_timeout (device);
+            }
+        }
+    }
+  else
+    {
+      if (has_button_pressed (device))
+        device->ptr_a11y_data->n_btn_pressed--;
+
+      if (is_secondary_click_triggered (device))
+        {
+          emit_button_click (device, CLUTTER_BUTTON_SECONDARY);
+          stop_secondary_click_timeout (device);
+        }
+
+      if (is_secondary_click_pending (device))
+        stop_secondary_click_timeout (device);
+
+      if (is_dwell_dragging (device))
+        emit_dwell_click (device, CLUTTER_A11Y_DWELL_CLICK_TYPE_DRAG);
+    }
+}
+
+gboolean
+_clutter_is_input_pointer_a11y_enabled (ClutterInputDevice *device)
+{
+  g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), FALSE);
+
+  return (is_secondary_click_enabled (device) || is_dwell_click_enabled (device));
+}
diff --git a/clutter/clutter/meson.build b/clutter/clutter/meson.build
index abc251413..6210333a1 100644
--- a/clutter/clutter/meson.build
+++ b/clutter/clutter/meson.build
@@ -133,6 +133,7 @@ clutter_sources = [
   'clutter-input-device-tool.c',
   'clutter-input-focus.c',
   'clutter-input-method.c',
+  'clutter-input-pointer-a11y.c',
   'clutter-virtual-input-device.c',
   'clutter-interval.c',
   'clutter-keyframe-transition.c',
@@ -195,6 +196,7 @@ clutter_private_headers = [
   'clutter-id-pool.h',
   'clutter-input-focus-private.h',
   'clutter-input-method-private.h',
+  'clutter-input-pointer-a11y-private.h',
   'clutter-master-clock.h',
   'clutter-master-clock-default.h',
   'clutter-offscreen-effect-private.h',


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