From 6c77af149389f03951b5b0d24410a0ffdbe02ca6 Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Wed, 6 Mar 2024 16:08:23 +1000 Subject: [PATCH] core: Add a MetaTabletActionMapper as parent of the MetaPadActionMapper This is prep work for using the same functionality for tablet tools as well. The new MetaTabletActionMapper takes care of the event bubbling via the device-added/removed and input-event signals and provides the helper functions to cycle outputs and/or emulate keybindings. Part-of: --- src/core/events.c | 13 +- src/core/meta-pad-action-mapper.c | 357 ++--------------- src/core/meta-pad-action-mapper.h | 5 +- src/core/meta-tablet-action-mapper.c | 578 +++++++++++++++++++++++++++ src/core/meta-tablet-action-mapper.h | 51 +++ src/meson.build | 1 + 6 files changed, 682 insertions(+), 323 deletions(-) create mode 100644 src/core/meta-tablet-action-mapper.c create mode 100644 src/core/meta-tablet-action-mapper.h diff --git a/src/core/events.c b/src/core/events.c index 87270aed8..d0545f700 100644 --- a/src/core/events.c +++ b/src/core/events.c @@ -235,6 +235,7 @@ meta_display_handle_event (MetaDisplay *display, ClutterEventSequence *sequence; ClutterEventType event_type; gboolean has_grab; + MetaTabletActionMapper *mapper; #ifdef HAVE_WAYLAND MetaWaylandCompositor *wayland_compositor; MetaWaylandTextInput *wayland_text_input = NULL; @@ -318,17 +319,23 @@ meta_display_handle_event (MetaDisplay *display, } handle_pad_event = !display->current_pad_osd || is_mode_switch; + mapper = META_TABLET_ACTION_MAPPER (display->pad_action_mapper); if (handle_pad_event && - meta_pad_action_mapper_handle_event (display->pad_action_mapper, event)) + meta_tablet_action_mapper_handle_event (mapper, event)) return CLUTTER_EVENT_STOP; } if (event_type != CLUTTER_DEVICE_ADDED && event_type != CLUTTER_DEVICE_REMOVED) - handle_idletime_for_event (display, event); + { + handle_idletime_for_event (display, event); + } else - meta_pad_action_mapper_handle_event (display->pad_action_mapper, event); + { + mapper = META_TABLET_ACTION_MAPPER (display->pad_action_mapper); + meta_tablet_action_mapper_handle_event (mapper, event); + } if (event_type == CLUTTER_MOTION) { diff --git a/src/core/meta-pad-action-mapper.c b/src/core/meta-pad-action-mapper.c index 8308f7860..cef43baf2 100644 --- a/src/core/meta-pad-action-mapper.c +++ b/src/core/meta-pad-action-mapper.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014-2020 Red Hat + * Copyright (C) 2014-2024 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 @@ -38,7 +38,6 @@ typedef struct _PadMappingInfo PadMappingInfo; struct _PadMappingInfo { ClutterInputDevice *device; - GSettings *settings; guint *group_modes; }; @@ -47,9 +46,6 @@ struct _MetaPadActionMapper GObject parent_class; GHashTable *pads; - ClutterSeat *seat; - ClutterVirtualInputDevice *virtual_pad_keyboard; - MetaMonitorManager *monitor_manager; /* Pad ring/strip emission */ struct { @@ -60,17 +56,17 @@ struct _MetaPadActionMapper } last_pad_action_info; }; -G_DEFINE_TYPE (MetaPadActionMapper, meta_pad_action_mapper, G_TYPE_OBJECT) +G_DEFINE_TYPE (MetaPadActionMapper, meta_pad_action_mapper, META_TYPE_TABLET_ACTION_MAPPER); -static MetaDisplay * -display_from_mapper (MetaPadActionMapper *mapper) -{ - MetaBackend *backend = - meta_monitor_manager_get_backend (mapper->monitor_manager); - MetaContext *context = meta_backend_get_context (backend); - - return meta_context_get_display (context); -} +static gboolean +meta_pad_action_mapper_handle_event (MetaTabletActionMapper *mapper, + const ClutterEvent *event); +static void +device_added (MetaTabletActionMapper *mapper, + ClutterInputDevice *device); +static void +device_removed (MetaTabletActionMapper *mapper, + ClutterInputDevice *device); static void meta_pad_action_mapper_finalize (GObject *object) @@ -78,8 +74,6 @@ meta_pad_action_mapper_finalize (GObject *object) MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (object); g_hash_table_unref (mapper->pads); - g_object_unref (mapper->monitor_manager); - g_clear_object (&mapper->virtual_pad_keyboard); G_OBJECT_CLASS (meta_pad_action_mapper_parent_class)->finalize (object); } @@ -92,25 +86,6 @@ meta_pad_action_mapper_class_init (MetaPadActionMapperClass *klass) object_class->finalize = meta_pad_action_mapper_finalize; } -static GSettings * -lookup_device_settings (ClutterInputDevice *device) -{ - const char *vendor, *product; - GSettings *settings; - char *path; - - vendor = clutter_input_device_get_vendor_id (device); - product = clutter_input_device_get_product_id (device); - path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/", - vendor, product); - - settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet", - path); - g_free (path); - - return settings; -} - static PadMappingInfo * pad_mapping_info_new (ClutterInputDevice *pad) { @@ -118,7 +93,6 @@ pad_mapping_info_new (ClutterInputDevice *pad) info = g_new0 (PadMappingInfo, 1); info->device = pad; - info->settings = lookup_device_settings (pad); info->group_modes = g_new0 (guint, clutter_input_device_get_n_mode_groups (pad)); @@ -128,15 +102,15 @@ pad_mapping_info_new (ClutterInputDevice *pad) static void pad_mapping_info_free (PadMappingInfo *info) { - g_object_unref (info->settings); g_free (info->group_modes); g_free (info); } static void -device_added (MetaPadActionMapper *mapper, - ClutterInputDevice *device) +device_added (MetaTabletActionMapper *tablet_mapper, + ClutterInputDevice *device) { + MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (tablet_mapper); PadMappingInfo *info; if ((clutter_input_device_get_capabilities (device) & @@ -148,26 +122,23 @@ device_added (MetaPadActionMapper *mapper, } static void -device_removed (MetaPadActionMapper *mapper, - ClutterInputDevice *device) +device_removed (MetaTabletActionMapper *tablet_mapper, + ClutterInputDevice *device) { + MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (tablet_mapper); + g_hash_table_remove (mapper->pads, device); } static void meta_pad_action_mapper_init (MetaPadActionMapper *mapper) { - g_autoptr (GList) devices = NULL; - GList *l; + g_signal_connect (mapper, "device-added", G_CALLBACK (device_added), NULL); + g_signal_connect (mapper, "device-removed", G_CALLBACK (device_removed), NULL); + g_signal_connect (mapper, "input-event", G_CALLBACK (meta_pad_action_mapper_handle_event), NULL); mapper->pads = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) pad_mapping_info_free); - - mapper->seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); - devices = clutter_seat_list_devices (mapper->seat); - - for (l = devices; l; l = l->next) - device_added (mapper, l->data); } MetaPadActionMapper * @@ -175,8 +146,9 @@ meta_pad_action_mapper_new (MetaMonitorManager *monitor_manager) { MetaPadActionMapper *action_mapper; - action_mapper = g_object_new (META_TYPE_PAD_ACTION_MAPPER, NULL); - g_set_object (&action_mapper->monitor_manager, monitor_manager); + action_mapper = g_object_new (META_TYPE_PAD_ACTION_MAPPER, + "monitor_manager", monitor_manager, + NULL); return action_mapper; } @@ -268,187 +240,6 @@ meta_pad_action_mapper_get_button_action (MetaPadActionMapper *mapper, return action; } -static gboolean -cycle_logical_monitors (MetaPadActionMapper *mapper, - gboolean skip_all_monitors, - MetaLogicalMonitor *current_logical_monitor, - MetaLogicalMonitor **next_logical_monitor) -{ - MetaMonitorManager *monitor_manager = mapper->monitor_manager; - GList *logical_monitors; - - /* We cycle between: - * - the span of all monitors (current_output = NULL), only for - * non-integrated devices. - * - each monitor individually. - */ - - logical_monitors = - meta_monitor_manager_get_logical_monitors (monitor_manager); - - if (!current_logical_monitor) - { - *next_logical_monitor = logical_monitors->data; - } - else - { - GList *l; - - l = g_list_find (logical_monitors, current_logical_monitor); - if (l->next) - *next_logical_monitor = l->next->data; - else if (skip_all_monitors) - *next_logical_monitor = logical_monitors->data; - else - *next_logical_monitor = NULL; - } - - return TRUE; -} - -static MetaMonitor * -logical_monitor_find_monitor (MetaLogicalMonitor *logical_monitor, - const char *vendor, - const char *product, - const char *serial) -{ - GList *monitors; - GList *l; - - monitors = meta_logical_monitor_get_monitors (logical_monitor); - for (l = monitors; l; l = l->next) - { - MetaMonitor *monitor = l->data; - - if (g_strcmp0 (meta_monitor_get_vendor (monitor), vendor) == 0 && - g_strcmp0 (meta_monitor_get_product (monitor), product) == 0 && - g_strcmp0 (meta_monitor_get_serial (monitor), serial) == 0) - return monitor; - } - - return NULL; -} - -static void -meta_pad_action_mapper_find_monitor (MetaPadActionMapper *mapper, - GSettings *settings, - ClutterInputDevice *device, - MetaMonitor **out_monitor, - MetaLogicalMonitor **out_logical_monitor) -{ - MetaMonitorManager *monitor_manager; - MetaMonitor *monitor; - guint n_values; - GList *logical_monitors; - GList *l; - char **edid; - - edid = g_settings_get_strv (settings, "output"); - n_values = g_strv_length (edid); - - if (n_values != 3) - { - g_warning ("EDID configuration for device '%s' " - "is incorrect, must have 3 values", - clutter_input_device_get_device_name (device)); - goto out; - } - - if (!*edid[0] && !*edid[1] && !*edid[2]) - goto out; - - monitor_manager = mapper->monitor_manager; - logical_monitors = - meta_monitor_manager_get_logical_monitors (monitor_manager); - for (l = logical_monitors; l; l = l->next) - { - MetaLogicalMonitor *logical_monitor = l->data; - - monitor = logical_monitor_find_monitor (logical_monitor, - edid[0], edid[1], edid[2]); - if (monitor) - { - if (out_monitor) - *out_monitor = monitor; - if (out_logical_monitor) - *out_logical_monitor = logical_monitor; - break; - } - } - -out: - g_strfreev (edid); -} - -static void -meta_pad_action_mapper_cycle_tablet_output (MetaPadActionMapper *mapper, - ClutterInputDevice *device) -{ - PadMappingInfo *info; - MetaLogicalMonitor *logical_monitor = NULL; - const char *edid[4] = { 0 }, *pretty_name = NULL; - gboolean is_integrated_device = FALSE; -#ifdef HAVE_LIBWACOM - WacomDevice *wacom_device; -#endif - - g_return_if_fail (META_IS_PAD_ACTION_MAPPER (mapper)); - g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); - g_return_if_fail ((clutter_input_device_get_capabilities (device) & - (CLUTTER_INPUT_CAPABILITY_TABLET_TOOL | - CLUTTER_INPUT_CAPABILITY_TABLET_PAD)) != 0); - - info = g_hash_table_lookup (mapper->pads, device); - g_return_if_fail (info != NULL); - -#ifdef HAVE_LIBWACOM - wacom_device = meta_input_device_get_wacom_device (META_INPUT_DEVICE (device)); - - if (wacom_device) - { - pretty_name = libwacom_get_name (wacom_device); - is_integrated_device = - libwacom_get_integration_flags (wacom_device) != WACOM_DEVICE_INTEGRATED_NONE; - } -#endif - - meta_pad_action_mapper_find_monitor (mapper, info->settings, device, - NULL, &logical_monitor); - - if (!cycle_logical_monitors (mapper, - is_integrated_device, - logical_monitor, - &logical_monitor)) - return; - - if (logical_monitor) - { - MetaMonitor *monitor; - const char *vendor; - const char *product; - const char *serial; - - /* Pick an arbitrary monitor in the logical monitor to represent it. */ - monitor = meta_logical_monitor_get_monitors (logical_monitor)->data; - vendor = meta_monitor_get_vendor (monitor); - product = meta_monitor_get_product (monitor); - serial = meta_monitor_get_serial (monitor); - edid[0] = vendor ? vendor : ""; - edid[1] = product ? product : ""; - edid[2] = serial ? serial : ""; - } - else - { - edid[0] = ""; - edid[1] = ""; - edid[2] = ""; - } - - g_settings_set_strv (info->settings, "output", edid); - meta_display_show_tablet_mapping_notification (display_from_mapper (mapper), - device, pretty_name); -} - gboolean meta_pad_action_mapper_is_button_grabbed (MetaPadActionMapper *mapper, ClutterInputDevice *pad, @@ -463,86 +254,20 @@ meta_pad_action_mapper_is_button_grabbed (MetaPadActionMapper *mapper, G_DESKTOP_PAD_BUTTON_ACTION_NONE); } -static void -emulate_modifiers (ClutterVirtualInputDevice *device, - ClutterModifierType mods, - ClutterKeyState state) -{ - guint i; - struct { - ClutterModifierType mod; - guint keyval; - } mod_map[] = { - { CLUTTER_SHIFT_MASK, CLUTTER_KEY_Shift_L }, - { CLUTTER_CONTROL_MASK, CLUTTER_KEY_Control_L }, - { CLUTTER_MOD1_MASK, CLUTTER_KEY_Alt_L }, - { CLUTTER_META_MASK, CLUTTER_KEY_Meta_L } - }; - - for (i = 0; i < G_N_ELEMENTS (mod_map); i++) - { - if ((mods & mod_map[i].mod) == 0) - continue; - - clutter_virtual_input_device_notify_keyval (device, - clutter_get_current_event_time (), - mod_map[i].keyval, state); - } -} - -static void -meta_pad_action_mapper_emulate_keybinding (MetaPadActionMapper *mapper, - const char *accel, - gboolean is_press) -{ - ClutterKeyState state; - MetaKeyCombo combo = { 0 }; - - if (!accel || !*accel) - return; - - if (!meta_parse_accelerator (accel, &combo)) - { - g_warning ("\"%s\" is not a valid accelerator", accel); - return; - } - - if (!mapper->virtual_pad_keyboard) - { - ClutterBackend *backend; - ClutterSeat *seat; - - backend = clutter_get_default_backend (); - seat = clutter_backend_get_default_seat (backend); - - mapper->virtual_pad_keyboard = - clutter_seat_create_virtual_device (seat, - CLUTTER_KEYBOARD_DEVICE); - } - - state = is_press ? CLUTTER_KEY_STATE_PRESSED : CLUTTER_KEY_STATE_RELEASED; - - if (is_press) - emulate_modifiers (mapper->virtual_pad_keyboard, combo.modifiers, state); - - clutter_virtual_input_device_notify_keyval (mapper->virtual_pad_keyboard, - clutter_get_current_event_time (), - combo.keysym, state); - if (!is_press) - emulate_modifiers (mapper->virtual_pad_keyboard, combo.modifiers, state); -} - static gboolean meta_pad_action_mapper_handle_button (MetaPadActionMapper *mapper, ClutterInputDevice *pad, const ClutterEvent *event) { + MetaTabletActionMapper *tablet_mapper = META_TABLET_ACTION_MAPPER (mapper); + MetaTabletActionMapperClass *tablet_klass = META_TABLET_ACTION_MAPPER_GET_CLASS (mapper); GDesktopPadButtonAction action; int group, n_modes = 0; gboolean is_press; GSettings *settings; char *accel; uint32_t button, mode; + MetaDisplay *display; g_return_val_if_fail (META_IS_PAD_ACTION_MAPPER (mapper), FALSE); g_return_val_if_fail (clutter_event_type (event) == CLUTTER_PAD_BUTTON_PRESS || @@ -551,6 +276,7 @@ meta_pad_action_mapper_handle_button (MetaPadActionMapper *mapper, clutter_event_get_pad_details (event, &button, &mode, NULL, NULL); group = clutter_input_device_get_mode_switch_button_group (pad, button); is_press = clutter_event_type (event) == CLUTTER_PAD_BUTTON_PRESS; + display = tablet_klass->get_display (tablet_mapper); if (group >= 0) n_modes = clutter_input_device_get_group_n_modes (pad, group); @@ -571,7 +297,7 @@ meta_pad_action_mapper_handle_button (MetaPadActionMapper *mapper, if (wacom_device) pretty_name = libwacom_get_name (wacom_device); #endif - meta_display_notify_pad_group_switch (display_from_mapper (mapper), pad, + meta_display_notify_pad_group_switch (display, pad, pretty_name, group, mode, n_modes); info->group_modes[group] = mode; } @@ -582,16 +308,16 @@ meta_pad_action_mapper_handle_button (MetaPadActionMapper *mapper, { case G_DESKTOP_PAD_BUTTON_ACTION_SWITCH_MONITOR: if (is_press) - meta_pad_action_mapper_cycle_tablet_output (mapper, pad); + tablet_klass->cycle_tablet_output (tablet_mapper, pad); return TRUE; case G_DESKTOP_PAD_BUTTON_ACTION_HELP: if (is_press) - meta_display_request_pad_osd (display_from_mapper (mapper), pad, FALSE); + meta_display_request_pad_osd (display, pad, FALSE); return TRUE; case G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING: settings = lookup_pad_button_settings (pad, button); accel = g_settings_get_string (settings, "keybinding"); - meta_pad_action_mapper_emulate_keybinding (mapper, accel, is_press); + tablet_klass->emulate_keybinding (tablet_mapper, accel, is_press); g_object_unref (settings); g_free (accel); return TRUE; @@ -708,18 +434,21 @@ meta_pad_action_mapper_handle_action (MetaPadActionMapper *mapper, if (accel && *accel) { - meta_pad_action_mapper_emulate_keybinding (mapper, accel, TRUE); - meta_pad_action_mapper_emulate_keybinding (mapper, accel, FALSE); + MetaTabletActionMapper *parent = META_TABLET_ACTION_MAPPER (mapper); + MetaTabletActionMapperClass *klass = META_TABLET_ACTION_MAPPER_GET_CLASS (parent); + klass->emulate_keybinding (parent, accel, TRUE); + klass->emulate_keybinding (parent, accel, FALSE); } } return handled; } -gboolean -meta_pad_action_mapper_handle_event (MetaPadActionMapper *mapper, - const ClutterEvent *event) +static gboolean +meta_pad_action_mapper_handle_event (MetaTabletActionMapper *tablet_mapper, + const ClutterEvent *event) { + MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (tablet_mapper); ClutterInputDevice *pad; uint32_t number, mode; @@ -740,12 +469,6 @@ meta_pad_action_mapper_handle_event (MetaPadActionMapper *mapper, return meta_pad_action_mapper_handle_action (mapper, pad, event, META_PAD_FEATURE_STRIP, number, mode); - case CLUTTER_DEVICE_ADDED: - device_added (mapper, clutter_event_get_source_device (event)); - break; - case CLUTTER_DEVICE_REMOVED: - device_removed (mapper, clutter_event_get_source_device (event)); - break; default: break; } diff --git a/src/core/meta-pad-action-mapper.h b/src/core/meta-pad-action-mapper.h index 744e6b48a..5be453a16 100644 --- a/src/core/meta-pad-action-mapper.h +++ b/src/core/meta-pad-action-mapper.h @@ -23,18 +23,17 @@ #include "clutter/clutter.h" #include "meta/display.h" #include "meta/meta-monitor-manager.h" +#include "core/meta-tablet-action-mapper.h" #define META_TYPE_PAD_ACTION_MAPPER (meta_pad_action_mapper_get_type ()) G_DECLARE_FINAL_TYPE (MetaPadActionMapper, meta_pad_action_mapper, - META, PAD_ACTION_MAPPER, GObject) + META, PAD_ACTION_MAPPER, MetaTabletActionMapper) MetaPadActionMapper * meta_pad_action_mapper_new (MetaMonitorManager *monitor_manager); gboolean meta_pad_action_mapper_is_button_grabbed (MetaPadActionMapper *mapper, ClutterInputDevice *pad, guint button); -gboolean meta_pad_action_mapper_handle_event (MetaPadActionMapper *mapper, - const ClutterEvent *event); char * meta_pad_action_mapper_get_button_label (MetaPadActionMapper *mapper, ClutterInputDevice *pad, diff --git a/src/core/meta-tablet-action-mapper.c b/src/core/meta-tablet-action-mapper.c new file mode 100644 index 000000000..87f68184b --- /dev/null +++ b/src/core/meta-tablet-action-mapper.c @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2014-2024 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, see . + * + * Written by: + * Carlos Garnacho + */ + +#include "config.h" + +#include +#include + +#ifdef HAVE_LIBWACOM +#include +#endif + +#include "core/meta-tablet-action-mapper.h" +#include "backends/meta-input-device-private.h" +#include "backends/meta-logical-monitor.h" +#include "backends/meta-monitor.h" +#include "core/display-private.h" + +typedef struct _TabletMappingInfo TabletMappingInfo; + +enum +{ + PROP_MONITOR_MANAGER = 1, + N_PROPERTIES +}; + +static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, }; + +enum +{ + DEVICE_ADDED, + DEVICE_REMOVED, + INPUT_EVENT, + + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _TabletMappingInfo +{ + ClutterInputDevice *device; + GSettings *settings; +}; + +struct _MetaTabletActionMapperPrivate +{ + GObject parent_class; + + GHashTable *tablets; + ClutterSeat *seat; + ClutterVirtualInputDevice *virtual_tablet_keyboard; + MetaMonitorManager *monitor_manager; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (MetaTabletActionMapper, meta_tablet_action_mapper, G_TYPE_OBJECT) + +static void +meta_tablet_action_mapper_constructed (GObject *object); + +static void +meta_tablet_action_mapper_cycle_tablet_output (MetaTabletActionMapper *mapper, + ClutterInputDevice *device); + +static void +meta_tablet_action_mapper_emulate_keybinding (MetaTabletActionMapper *mapper, + const char *accel, + gboolean is_press); + +static MetaDisplay * +meta_tablet_action_mapper_get_display (MetaTabletActionMapper *mapper) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + MetaBackend *backend = + meta_monitor_manager_get_backend (priv->monitor_manager); + MetaContext *context = meta_backend_get_context (backend); + + return meta_context_get_display (context); +} + +static void +meta_tablet_action_mapper_finalize (GObject *object) +{ + MetaTabletActionMapper *mapper = META_TABLET_ACTION_MAPPER (object); + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + + g_hash_table_unref (priv->tablets); + g_object_unref (priv->monitor_manager); + g_clear_object (&priv->virtual_tablet_keyboard); + + G_OBJECT_CLASS (meta_tablet_action_mapper_parent_class)->finalize (object); +} + +static void +meta_tablet_action_mapper_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + MetaTabletActionMapper *mapper = META_TABLET_ACTION_MAPPER (object); + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + + switch (property_id) + { + case PROP_MONITOR_MANAGER: + g_value_set_object (value, priv->monitor_manager); + break; + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +meta_tablet_action_mapper_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaTabletActionMapper *mapper = META_TABLET_ACTION_MAPPER (object); + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + + switch (property_id) + { + case PROP_MONITOR_MANAGER: + if (priv->monitor_manager) + g_object_unref (priv->monitor_manager); + priv->monitor_manager = g_object_ref (g_value_get_object (value)); + break; + + default: + /* We don't have any other property... */ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +meta_tablet_action_mapper_class_init (MetaTabletActionMapperClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = meta_tablet_action_mapper_get_property; + object_class->set_property = meta_tablet_action_mapper_set_property; + object_class->constructed = meta_tablet_action_mapper_constructed; + object_class->finalize = meta_tablet_action_mapper_finalize; + + klass->get_display = meta_tablet_action_mapper_get_display; + klass->emulate_keybinding = meta_tablet_action_mapper_emulate_keybinding; + klass->cycle_tablet_output = meta_tablet_action_mapper_cycle_tablet_output; + + signals[DEVICE_ADDED] = g_signal_new ("device-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + CLUTTER_TYPE_INPUT_DEVICE); + signals[DEVICE_REMOVED] = g_signal_new ("device-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + CLUTTER_TYPE_INPUT_DEVICE); + + signals[INPUT_EVENT] = g_signal_new ("input-event", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_BOOLEAN, 1, + CLUTTER_TYPE_EVENT); + + obj_properties[PROP_MONITOR_MANAGER] = + g_param_spec_object ("monitor-manager", NULL, NULL, + META_TYPE_MONITOR_MANAGER, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, + N_PROPERTIES, + obj_properties); +} + +static GSettings * +lookup_device_settings (ClutterInputDevice *device) +{ + const char *vendor, *product; + GSettings *settings; + char *path; + + vendor = clutter_input_device_get_vendor_id (device); + product = clutter_input_device_get_product_id (device); + path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/", + vendor, product); + + settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet", + path); + g_free (path); + + return settings; +} + +static TabletMappingInfo * +tablet_mapping_info_new (ClutterInputDevice *tablet) +{ + TabletMappingInfo *info; + + info = g_new0 (TabletMappingInfo, 1); + info->device = tablet; + info->settings = lookup_device_settings (tablet); + + return info; +} + +static void +tablet_mapping_info_free (TabletMappingInfo *info) +{ + g_object_unref (info->settings); + g_free (info); +} + +static void +device_added (MetaTabletActionMapper *mapper, + ClutterInputDevice *device) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + TabletMappingInfo *info; + + if ((clutter_input_device_get_capabilities (device) & + CLUTTER_INPUT_CAPABILITY_TABLET_TOOL) != 0) + { + info = tablet_mapping_info_new (device); + g_hash_table_insert (priv->tablets, device, info); + } +} + +static void +device_removed (MetaTabletActionMapper *mapper, + ClutterInputDevice *device) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + + g_hash_table_remove (priv->tablets, device); +} + +static void +meta_tablet_action_mapper_init (MetaTabletActionMapper *mapper) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + + g_signal_connect (mapper, "device-added", G_CALLBACK (device_added), NULL); + g_signal_connect (mapper, "device-removed", G_CALLBACK (device_removed), NULL); + + priv->tablets = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify) tablet_mapping_info_free); +} + +static void +meta_tablet_action_mapper_constructed (GObject *object) +{ + MetaTabletActionMapper *mapper = META_TABLET_ACTION_MAPPER (object); + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + g_autoptr (GList) devices = NULL; + GList *l; + + priv->seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); + devices = clutter_seat_list_devices (priv->seat); + + /* FIXME: is this call correct?? */ + for (l = devices; l; l = l->next) + g_signal_emit (mapper, signals[DEVICE_ADDED], 0, l->data); +} + +static gboolean +cycle_logical_monitors (MetaTabletActionMapperPrivate *mapper, + gboolean skip_all_monitors, + MetaLogicalMonitor *current_logical_monitor, + MetaLogicalMonitor **next_logical_monitor) +{ + MetaMonitorManager *monitor_manager = mapper->monitor_manager; + GList *logical_monitors; + + /* We cycle between: + * - the span of all monitors (current_output = NULL), only for + * non-integrated devices. + * - each monitor individually. + */ + + logical_monitors = + meta_monitor_manager_get_logical_monitors (monitor_manager); + + if (!current_logical_monitor) + { + *next_logical_monitor = logical_monitors->data; + } + else + { + GList *l; + + l = g_list_find (logical_monitors, current_logical_monitor); + if (l->next) + *next_logical_monitor = l->next->data; + else if (skip_all_monitors) + *next_logical_monitor = logical_monitors->data; + else + *next_logical_monitor = NULL; + } + + return TRUE; +} + +static MetaMonitor * +logical_monitor_find_monitor (MetaLogicalMonitor *logical_monitor, + const char *vendor, + const char *product, + const char *serial) +{ + GList *monitors; + GList *l; + + monitors = meta_logical_monitor_get_monitors (logical_monitor); + for (l = monitors; l; l = l->next) + { + MetaMonitor *monitor = l->data; + + if (g_strcmp0 (meta_monitor_get_vendor (monitor), vendor) == 0 && + g_strcmp0 (meta_monitor_get_product (monitor), product) == 0 && + g_strcmp0 (meta_monitor_get_serial (monitor), serial) == 0) + return monitor; + } + + return NULL; +} + +static void +meta_tablet_action_mapper_find_monitor (MetaTabletActionMapperPrivate *mapper, + GSettings *settings, + ClutterInputDevice *device, + MetaMonitor **out_monitor, + MetaLogicalMonitor **out_logical_monitor) +{ + MetaMonitorManager *monitor_manager; + MetaMonitor *monitor; + guint n_values; + GList *logical_monitors; + GList *l; + char **edid; + + edid = g_settings_get_strv (settings, "output"); + n_values = g_strv_length (edid); + + if (n_values != 3) + { + g_warning ("EDID configuration for device '%s' " + "is incorrect, must have 3 values", + clutter_input_device_get_device_name (device)); + goto out; + } + + if (!*edid[0] && !*edid[1] && !*edid[2]) + goto out; + + monitor_manager = mapper->monitor_manager; + logical_monitors = + meta_monitor_manager_get_logical_monitors (monitor_manager); + for (l = logical_monitors; l; l = l->next) + { + MetaLogicalMonitor *logical_monitor = l->data; + + monitor = logical_monitor_find_monitor (logical_monitor, + edid[0], edid[1], edid[2]); + if (monitor) + { + if (out_monitor) + *out_monitor = monitor; + if (out_logical_monitor) + *out_logical_monitor = logical_monitor; + break; + } + } + +out: + g_strfreev (edid); +} + +static void +meta_tablet_action_mapper_cycle_tablet_output (MetaTabletActionMapper *mapper, + ClutterInputDevice *device) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + TabletMappingInfo *info; + MetaLogicalMonitor *logical_monitor = NULL; + const char *edid[4] = { 0 }, *pretty_name = NULL; + gboolean is_integrated_device = FALSE; +#ifdef HAVE_LIBWACOM + WacomDevice *wacom_device; +#endif + + g_return_if_fail (META_IS_TABLET_ACTION_MAPPER (mapper)); + g_return_if_fail (CLUTTER_IS_INPUT_DEVICE (device)); + g_return_if_fail ((clutter_input_device_get_capabilities (device) & + (CLUTTER_INPUT_CAPABILITY_TABLET_TOOL | + CLUTTER_INPUT_CAPABILITY_TABLET_PAD)) != 0); + + info = g_hash_table_lookup (priv->tablets, device); + g_return_if_fail (info != NULL); + +#ifdef HAVE_LIBWACOM + wacom_device = meta_input_device_get_wacom_device (META_INPUT_DEVICE (device)); + + if (wacom_device) + { + pretty_name = libwacom_get_name (wacom_device); + is_integrated_device = + libwacom_get_integration_flags (wacom_device) != WACOM_DEVICE_INTEGRATED_NONE; + } +#endif + + meta_tablet_action_mapper_find_monitor (priv, info->settings, device, + NULL, &logical_monitor); + + if (!cycle_logical_monitors (priv, + is_integrated_device, + logical_monitor, + &logical_monitor)) + return; + + if (logical_monitor) + { + MetaMonitor *monitor; + const char *vendor; + const char *product; + const char *serial; + + /* Pick an arbitrary monitor in the logical monitor to represent it. */ + monitor = meta_logical_monitor_get_monitors (logical_monitor)->data; + vendor = meta_monitor_get_vendor (monitor); + product = meta_monitor_get_product (monitor); + serial = meta_monitor_get_serial (monitor); + edid[0] = vendor ? vendor : ""; + edid[1] = product ? product : ""; + edid[2] = serial ? serial : ""; + } + else + { + edid[0] = ""; + edid[1] = ""; + edid[2] = ""; + } + + g_settings_set_strv (info->settings, "output", edid); + meta_display_show_tablet_mapping_notification (meta_tablet_action_mapper_get_display (mapper), + device, pretty_name); +} + +static void +emulate_modifiers (ClutterVirtualInputDevice *device, + ClutterModifierType mods, + ClutterKeyState state) +{ + guint i; + struct { + ClutterModifierType mod; + guint keyval; + } mod_map[] = { + { CLUTTER_SHIFT_MASK, CLUTTER_KEY_Shift_L }, + { CLUTTER_CONTROL_MASK, CLUTTER_KEY_Control_L }, + { CLUTTER_MOD1_MASK, CLUTTER_KEY_Alt_L }, + { CLUTTER_META_MASK, CLUTTER_KEY_Meta_L } + }; + + for (i = 0; i < G_N_ELEMENTS (mod_map); i++) + { + if ((mods & mod_map[i].mod) == 0) + continue; + + clutter_virtual_input_device_notify_keyval (device, + clutter_get_current_event_time (), + mod_map[i].keyval, state); + } +} + +static void +meta_tablet_action_mapper_emulate_keybinding (MetaTabletActionMapper *mapper, + const char *accel, + gboolean is_press) +{ + MetaTabletActionMapperPrivate *priv = + meta_tablet_action_mapper_get_instance_private (mapper); + ClutterKeyState state; + MetaKeyCombo combo = { 0 }; + + if (!accel || !*accel) + return; + + if (!meta_parse_accelerator (accel, &combo)) + { + g_warning ("\"%s\" is not a valid accelerator", accel); + return; + } + + if (!priv->virtual_tablet_keyboard) + { + ClutterBackend *backend; + ClutterSeat *seat; + + backend = clutter_get_default_backend (); + seat = clutter_backend_get_default_seat (backend); + + priv->virtual_tablet_keyboard = + clutter_seat_create_virtual_device (seat, + CLUTTER_KEYBOARD_DEVICE); + } + + state = is_press ? CLUTTER_KEY_STATE_PRESSED : CLUTTER_KEY_STATE_RELEASED; + + if (is_press) + emulate_modifiers (priv->virtual_tablet_keyboard, combo.modifiers, state); + + clutter_virtual_input_device_notify_keyval (priv->virtual_tablet_keyboard, + clutter_get_current_event_time (), + combo.keysym, state); + if (!is_press) + emulate_modifiers (priv->virtual_tablet_keyboard, combo.modifiers, state); +} + +gboolean +meta_tablet_action_mapper_handle_event (MetaTabletActionMapper *mapper, + const ClutterEvent *event) +{ + ClutterInputDevice *device; + gboolean propagate = CLUTTER_EVENT_PROPAGATE; + + switch (clutter_event_type (event)) + { + case CLUTTER_DEVICE_ADDED: + device = clutter_event_get_source_device (event); + g_signal_emit (mapper, signals[DEVICE_ADDED], 0, device); + break; + case CLUTTER_DEVICE_REMOVED: + device = clutter_event_get_source_device (event); + g_signal_emit (mapper, signals[DEVICE_REMOVED], 0, device); + break; + default: + g_signal_emit (mapper, signals[INPUT_EVENT], 0, event, &propagate); + break; + } + + return propagate; +} diff --git a/src/core/meta-tablet-action-mapper.h b/src/core/meta-tablet-action-mapper.h new file mode 100644 index 000000000..269cc6bee --- /dev/null +++ b/src/core/meta-tablet-action-mapper.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 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, see . + * + * Written by: + * Carlos Garnacho + */ + +#pragma once + +#include "clutter/clutter.h" +#include "meta/display.h" +#include "meta/meta-monitor-manager.h" + +#define META_TYPE_TABLET_ACTION_MAPPER (meta_tablet_action_mapper_get_type ()) +G_DECLARE_DERIVABLE_TYPE (MetaTabletActionMapper, meta_tablet_action_mapper, + META, TABLET_ACTION_MAPPER, GObject) + +typedef struct _MetaTabletActionMapperPrivate MetaTabletActionMapperPrivate; + +/** + * MetaTabletActionMapperClass: + */ +struct _MetaTabletActionMapperClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< private >*/ + MetaDisplay * (* get_display) (MetaTabletActionMapper *mapper); + void (* emulate_keybinding) (MetaTabletActionMapper *mapper, + const char *accel, + gboolean is_press); + void (*cycle_tablet_output) (MetaTabletActionMapper *mapper, + ClutterInputDevice *device); +}; + +gboolean meta_tablet_action_mapper_handle_event (MetaTabletActionMapper *mapper, + const ClutterEvent *event); diff --git a/src/meson.build b/src/meson.build index 2cd164f37..b74fe37fe 100644 --- a/src/meson.build +++ b/src/meson.build @@ -378,6 +378,7 @@ mutter_sources = [ 'core/meta-selection-source.c', 'core/meta-selection-source-memory.c', 'core/meta-sound-player.c', + 'core/meta-tablet-action-mapper.c', 'core/meta-workspace-manager.c', 'core/meta-workspace-manager-private.h', 'core/place.c',