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',