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