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 + */ + +#ifndef __CLUTTER_INPUT_POINTER_A11Y_H__ +#define __CLUTTER_INPUT_POINTER_A11Y_H__ + +#include +#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 + * + * This reimplements in Clutter the same behavior as mousetweaks original + * implementation by Gerd Kohlberger + * mousetweaks Copyright (C) 2007-2010 Gerd Kohlberger + */ + +#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',