mutter/src/core/meta-pad-action-mapper.c
Peter Hutterer 65db49cafa core: Don't show the pad mode button group if we only have a single group
Only a tiny number of tablet pads that have more than one mode
group, everyone else has one mode toggle button and one group.
Let's not display "Group 0" for everyone if (almost) no-one has a Group
1 anyway.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4248>
2025-02-05 13:21:54 +00:00

637 lines
20 KiB
C

/*
* 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 <gdesktop-enums.h>
#ifdef HAVE_LIBWACOM
#include <libwacom/libwacom.h>
#endif
#include "core/meta-pad-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 _PadMappingInfo PadMappingInfo;
struct _PadMappingInfo
{
ClutterInputDevice *device;
guint *group_modes;
};
struct _MetaPadActionMapper
{
GObject parent_class;
GHashTable *pads;
/* Pad ring/strip emission */
struct {
ClutterInputDevice *pad;
MetaPadFeatureType feature;
guint number;
double value;
} last_pad_action_info;
};
G_DEFINE_TYPE (MetaPadActionMapper, meta_pad_action_mapper, META_TYPE_TABLET_ACTION_MAPPER);
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)
{
MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (object);
g_hash_table_unref (mapper->pads);
G_OBJECT_CLASS (meta_pad_action_mapper_parent_class)->finalize (object);
}
static void
meta_pad_action_mapper_class_init (MetaPadActionMapperClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = meta_pad_action_mapper_finalize;
}
static PadMappingInfo *
pad_mapping_info_new (ClutterInputDevice *pad)
{
PadMappingInfo *info;
info = g_new0 (PadMappingInfo, 1);
info->device = pad;
info->group_modes =
g_new0 (guint, clutter_input_device_get_n_mode_groups (pad));
return info;
}
static void
pad_mapping_info_free (PadMappingInfo *info)
{
g_free (info->group_modes);
g_free (info);
}
static void
device_added (MetaTabletActionMapper *tablet_mapper,
ClutterInputDevice *device)
{
MetaPadActionMapper *mapper = META_PAD_ACTION_MAPPER (tablet_mapper);
PadMappingInfo *info;
if ((clutter_input_device_get_capabilities (device) &
CLUTTER_INPUT_CAPABILITY_TABLET_PAD) != 0)
{
info = pad_mapping_info_new (device);
g_hash_table_insert (mapper->pads, device, info);
}
}
static void
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_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);
}
MetaPadActionMapper *
meta_pad_action_mapper_new (MetaMonitorManager *monitor_manager)
{
MetaPadActionMapper *action_mapper;
action_mapper = g_object_new (META_TYPE_PAD_ACTION_MAPPER,
"monitor_manager", monitor_manager,
NULL);
return action_mapper;
}
static GSettings *
get_pad_feature_gsettings (ClutterInputDevice *device,
const char *feature,
int feature_number,
const char *suffix)
{
GSettings *settings;
g_autofree char *path = NULL;
const gchar *vendor, *product;
char tag;
tag = 'A' + feature_number;
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/%s%c%s/",
vendor, product, feature, tag,
suffix ? suffix : "");
settings = g_settings_new_with_path ("org.gnome.desktop.peripherals.tablet.pad-button",
path);
return settings;
}
static GSettings *
lookup_pad_button_settings (ClutterInputDevice *device,
int button)
{
return get_pad_feature_gsettings (device, "button", button, NULL);
}
static GSettings *
lookup_pad_feature_settings (ClutterInputDevice *device,
MetaPadFeatureType feature,
guint number,
MetaPadDirection direction,
int mode)
{
g_autofree char *suffix = NULL;
const char *feature_type, *detail_type;
switch (feature)
{
case META_PAD_FEATURE_RING:
g_assert (direction == META_PAD_DIRECTION_CW ||
direction == META_PAD_DIRECTION_CCW);
feature_type = "ring";
detail_type = (direction == META_PAD_DIRECTION_CW) ? "cw" : "ccw";
break;
case META_PAD_FEATURE_STRIP:
g_assert (direction == META_PAD_DIRECTION_UP ||
direction == META_PAD_DIRECTION_DOWN);
feature_type = "strip";
detail_type = (direction == META_PAD_DIRECTION_UP) ? "up" : "down";
break;
default:
return NULL;
}
if (mode >= 0)
suffix = g_strdup_printf ("-%s-mode-%d", detail_type, mode);
else
suffix = g_strdup_printf ("-%s", detail_type);
return get_pad_feature_gsettings (device, feature_type, number, suffix);
}
static GDesktopPadButtonAction
meta_pad_action_mapper_get_button_action (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
guint button)
{
GDesktopPadButtonAction action;
GSettings *settings;
g_return_val_if_fail (META_IS_PAD_ACTION_MAPPER (mapper),
G_DESKTOP_PAD_BUTTON_ACTION_NONE);
g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (pad),
G_DESKTOP_PAD_BUTTON_ACTION_NONE);
settings = lookup_pad_button_settings (pad, button);
action = g_settings_get_enum (settings, "action");
g_object_unref (settings);
return action;
}
gboolean
meta_pad_action_mapper_is_button_grabbed (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
guint button)
{
g_return_val_if_fail (META_IS_PAD_ACTION_MAPPER (mapper), FALSE);
g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (pad), FALSE);
g_return_val_if_fail ((clutter_input_device_get_capabilities (pad) &
CLUTTER_INPUT_CAPABILITY_TABLET_PAD) != 0, FALSE);
return (meta_pad_action_mapper_get_button_action (mapper, pad, button) !=
G_DESKTOP_PAD_BUTTON_ACTION_NONE);
}
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 ||
clutter_event_type (event) == CLUTTER_PAD_BUTTON_RELEASE, FALSE);
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);
if (is_press && n_modes > 0)
{
const char *pretty_name = NULL;
PadMappingInfo *info;
#ifdef HAVE_LIBWACOM
WacomDevice *wacom_device;
#endif
info = g_hash_table_lookup (mapper->pads, pad);
#ifdef HAVE_LIBWACOM
wacom_device = meta_input_device_get_wacom_device (META_INPUT_DEVICE (pad));
if (wacom_device)
pretty_name = libwacom_get_name (wacom_device);
#endif
meta_display_notify_pad_group_switch (display, pad,
pretty_name, group, mode, n_modes);
info->group_modes[group] = mode;
}
action = meta_pad_action_mapper_get_button_action (mapper, pad, button);
switch (action)
{
case G_DESKTOP_PAD_BUTTON_ACTION_SWITCH_MONITOR:
if (is_press)
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, 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");
tablet_klass->emulate_keybinding (tablet_mapper, accel, is_press);
g_object_unref (settings);
g_free (accel);
return TRUE;
case G_DESKTOP_PAD_BUTTON_ACTION_NONE:
default:
return FALSE;
}
}
static gboolean
meta_pad_action_mapper_get_action_direction (MetaPadActionMapper *mapper,
const ClutterEvent *event,
MetaPadDirection *direction)
{
ClutterInputDevice *pad = clutter_event_get_device (event);
MetaPadFeatureType pad_feature;
gboolean has_direction = FALSE;
MetaPadDirection inc_dir, dec_dir;
uint32_t number;
double value;
gboolean detect_wraparound = FALSE;
switch (clutter_event_type (event))
{
case CLUTTER_PAD_RING:
pad_feature = META_PAD_FEATURE_RING;
clutter_event_get_pad_details (event, &number, NULL, NULL, &value);
inc_dir = META_PAD_DIRECTION_CW;
dec_dir = META_PAD_DIRECTION_CCW;
detect_wraparound = TRUE;
break;
case CLUTTER_PAD_STRIP:
pad_feature = META_PAD_FEATURE_STRIP;
clutter_event_get_pad_details (event, &number, NULL, NULL, &value);
inc_dir = META_PAD_DIRECTION_DOWN;
dec_dir = META_PAD_DIRECTION_UP;
break;
default:
return FALSE;
}
if (mapper->last_pad_action_info.pad == pad &&
mapper->last_pad_action_info.feature == pad_feature &&
mapper->last_pad_action_info.number == number &&
value >= 0 && mapper->last_pad_action_info.value >= 0)
{
double delta = value - mapper->last_pad_action_info.value;
if (detect_wraparound)
{
if (delta < -180.0)
delta += 360;
else if (delta > 180.0)
delta -= 360;
}
*direction = delta > 0 ? inc_dir : dec_dir;
has_direction = TRUE;
}
mapper->last_pad_action_info.pad = pad;
mapper->last_pad_action_info.feature = pad_feature;
mapper->last_pad_action_info.number = number;
mapper->last_pad_action_info.value = value;
return has_direction;
}
static gboolean
meta_pad_action_mapper_handle_action (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
const ClutterEvent *event,
MetaPadFeatureType feature,
guint number,
guint mode)
{
MetaPadDirection direction;
g_autoptr (GSettings) settings1 = NULL, settings2 = NULL;
g_autofree char *accel1 = NULL, *accel2 = NULL;
gboolean handled;
if (feature == META_PAD_FEATURE_RING)
{
settings1 = lookup_pad_feature_settings (pad, feature, number,
META_PAD_DIRECTION_CW, mode);
settings2 = lookup_pad_feature_settings (pad, feature, number,
META_PAD_DIRECTION_CCW, mode);
}
else if (feature == META_PAD_FEATURE_STRIP)
{
settings1 = lookup_pad_feature_settings (pad, feature, number,
META_PAD_DIRECTION_UP, mode);
settings2 = lookup_pad_feature_settings (pad, feature, number,
META_PAD_DIRECTION_DOWN, mode);
}
else
{
return FALSE;
}
accel1 = g_settings_get_string (settings1, "keybinding");
accel2 = g_settings_get_string (settings2, "keybinding");
handled = ((accel1 && *accel1) || (accel2 && *accel2));
if (meta_pad_action_mapper_get_action_direction (mapper, event, &direction))
{
const gchar *accel = NULL;
if (direction == META_PAD_DIRECTION_UP ||
direction == META_PAD_DIRECTION_CW)
accel = accel1;
else if (direction == META_PAD_DIRECTION_DOWN ||
direction == META_PAD_DIRECTION_CCW)
accel = accel2;
if (accel && *accel)
{
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;
}
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;
pad = clutter_event_get_source_device ((ClutterEvent *) event);
switch (clutter_event_type (event))
{
case CLUTTER_PAD_BUTTON_PRESS:
case CLUTTER_PAD_BUTTON_RELEASE:
return meta_pad_action_mapper_handle_button (mapper, pad, event);
case CLUTTER_PAD_RING:
clutter_event_get_pad_details (event, &number, &mode, NULL, NULL);
return meta_pad_action_mapper_handle_action (mapper, pad, event,
META_PAD_FEATURE_RING,
number, mode);
case CLUTTER_PAD_STRIP:
clutter_event_get_pad_details (event, &number, &mode, NULL, NULL);
return meta_pad_action_mapper_handle_action (mapper, pad, event,
META_PAD_FEATURE_STRIP,
number, mode);
default:
break;
}
return CLUTTER_EVENT_PROPAGATE;
}
static char *
meta_pad_action_mapper_get_ring_label (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
int number,
unsigned int mode,
MetaPadDirection direction)
{
g_autoptr (GSettings) settings = NULL;
g_autofree char *action = NULL;
if (direction != META_PAD_DIRECTION_CW &&
direction != META_PAD_DIRECTION_CCW)
return NULL;
settings = lookup_pad_feature_settings (pad, META_PAD_FEATURE_RING,
number, direction, mode);
/* We only allow keybinding actions with those */
action = g_settings_get_string (settings, "keybinding");
if (action && *action)
return g_steal_pointer (&action);
return NULL;
}
static char *
meta_pad_action_mapper_get_strip_label (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
int number,
unsigned int mode,
MetaPadDirection direction)
{
g_autoptr (GSettings) settings = NULL;
g_autofree char *action = NULL;
if (direction != META_PAD_DIRECTION_UP &&
direction != META_PAD_DIRECTION_DOWN)
return NULL;
settings = lookup_pad_feature_settings (pad, META_PAD_FEATURE_STRIP,
number, direction, mode);
/* We only allow keybinding actions with those */
action = g_settings_get_string (settings, "keybinding");
if (action && *action)
return g_steal_pointer (&action);
return NULL;
}
char *
meta_pad_action_mapper_get_button_label (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
int button)
{
GDesktopPadButtonAction action;
int group;
g_return_val_if_fail (META_IS_PAD_ACTION_MAPPER (mapper), NULL);
g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (pad), NULL);
g_return_val_if_fail ((clutter_input_device_get_capabilities (pad) &
CLUTTER_INPUT_CAPABILITY_TABLET_PAD) != 0, NULL);
group = clutter_input_device_get_mode_switch_button_group (pad, button);
if (group >= 0)
{
int n_groups = clutter_input_device_get_n_mode_groups (pad);
if (n_groups > 1)
{
/* TRANSLATORS: This string refers to a button that switches between
* different modes in that button group.
*/
return g_strdup_printf (_("Mode Switch (Group %d)"), group);
}
else
{
/* TRANSLATORS: This string refers to a button that switches between
* different modes.
*/
return g_strdup_printf (_("Mode Switch"));
}
}
action = meta_pad_action_mapper_get_button_action (mapper, pad, button);
switch (action)
{
case G_DESKTOP_PAD_BUTTON_ACTION_KEYBINDING:
{
GSettings *settings;
char *accel;
settings = lookup_pad_button_settings (pad, button);
accel = g_settings_get_string (settings, "keybinding");
g_object_unref (settings);
return accel;
}
case G_DESKTOP_PAD_BUTTON_ACTION_SWITCH_MONITOR:
/* TRANSLATORS: This string refers to an action, cycles drawing tablets'
* mapping through the available outputs.
*/
return g_strdup (_("Switch monitor"));
case G_DESKTOP_PAD_BUTTON_ACTION_HELP:
return g_strdup (_("Show on-screen help"));
case G_DESKTOP_PAD_BUTTON_ACTION_NONE:
default:
return NULL;
}
}
static guint
get_current_pad_mode (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
MetaPadFeatureType feature,
guint number)
{
PadMappingInfo *info;
guint group = 0, n_groups;
info = g_hash_table_lookup (mapper->pads, pad);
n_groups = clutter_input_device_get_n_mode_groups (pad);
if (!info->group_modes || n_groups == 0)
return 0;
if (feature == META_PAD_FEATURE_RING ||
feature == META_PAD_FEATURE_STRIP)
{
/* Assume features are evenly distributed in groups */
group = number % n_groups;
}
return info->group_modes[group];
}
char *
meta_pad_action_mapper_get_feature_label (MetaPadActionMapper *mapper,
ClutterInputDevice *pad,
MetaPadFeatureType feature,
MetaPadDirection direction,
int number)
{
unsigned int mode;
switch (feature)
{
case META_PAD_FEATURE_RING:
mode = get_current_pad_mode (mapper, pad, feature, number);
return meta_pad_action_mapper_get_ring_label (mapper, pad, number, mode, direction);
case META_PAD_FEATURE_STRIP:
mode = get_current_pad_mode (mapper, pad, feature, number);
return meta_pad_action_mapper_get_strip_label (mapper, pad, number, mode, direction);
}
return NULL;
}