1
0
Fork 0

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: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3649>
This commit is contained in:
Peter Hutterer 2024-03-06 16:08:23 +10:00 committed by Marge Bot
parent 94e497f630
commit 6c77af1493
6 changed files with 682 additions and 323 deletions

View file

@ -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)
{

View file

@ -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;
}

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Written by:
* Carlos Garnacho <carlosg@gnome.org>
*/
#include "config.h"
#include <glib/gi18n-lib.h>
#include <gsettings-desktop-schemas/gdesktop-enums.h>
#ifdef HAVE_LIBWACOM
#include <libwacom/libwacom.h>
#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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*
* Written by:
* Carlos Garnacho <carlosg@gnome.org>
*/
#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);

View file

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