gnome-control-center/panels/common/gsd-device-manager.c
Carlos Garnacho 17d8013769 common: Query Virtual core pointer for currently used tablet
This code does not work as expected in X11, since the GdkDevice
that GTK gives us is the Virtual core pointer, and not the hardware
device that is driving it at the moment.

Since there's no way to ask for this information to GTK4, query
XI2 indirectly about it, by checking the source device that the
Virtual core pointer inherited its XIValuatorClass classes from
(e.g. pressure), then querying that source device /dev node path.
2023-12-20 20:57:52 +01:00

708 lines
18 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2014 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Author: Carlos Garnacho <carlosg@gnome.org>
*/
#include "config.h"
#include <string.h>
#include <gudev/gudev.h>
#include "gsd-device-manager.h"
#include "gsd-common-enums.h"
#include "gnome-settings-bus.h"
#include "gsd-input-helper.h"
#ifdef GDK_WINDOWING_X11
#include <gdk/x11/gdkx.h>
#include <X11/extensions/XInput2.h>
#endif
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#endif
typedef struct
{
gchar *name;
gchar *device_file;
gchar *vendor_id;
gchar *product_id;
gchar *group;
GsdDeviceType type;
guint width;
guint height;
} GsdDevicePrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GsdDevice, gsd_device, G_TYPE_OBJECT)
typedef struct
{
GObject parent_instance;
GHashTable *devices;
GUdevClient *udev_client;
} GsdDeviceManagerPrivate;
enum {
PROP_NAME = 1,
PROP_DEVICE_FILE,
PROP_VENDOR_ID,
PROP_PRODUCT_ID,
PROP_TYPE,
PROP_WIDTH,
PROP_HEIGHT,
PROP_GROUP
};
enum {
DEVICE_ADDED,
DEVICE_REMOVED,
DEVICE_CHANGED,
N_SIGNALS
};
/* Index matches GsdDeviceType */
const gchar *udev_ids[] = {
"ID_INPUT_MOUSE",
"ID_INPUT_KEYBOARD",
"ID_INPUT_TOUCHPAD",
"ID_INPUT_TABLET",
"ID_INPUT_TOUCHSCREEN",
"ID_INPUT_TABLET_PAD",
};
static guint signals[N_SIGNALS] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (GsdDeviceManager, gsd_device_manager, G_TYPE_OBJECT)
static void
gsd_device_init (GsdDevice *device)
{
}
static void
gsd_device_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GsdDevicePrivate *priv;
priv = gsd_device_get_instance_private (GSD_DEVICE (object));
switch (prop_id) {
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
case PROP_DEVICE_FILE:
priv->device_file = g_value_dup_string (value);
break;
case PROP_VENDOR_ID:
priv->vendor_id = g_value_dup_string (value);
break;
case PROP_PRODUCT_ID:
priv->product_id = g_value_dup_string (value);
break;
case PROP_TYPE:
priv->type = g_value_get_flags (value);
break;
case PROP_WIDTH:
priv->width = g_value_get_uint (value);
break;
case PROP_HEIGHT:
priv->height = g_value_get_uint (value);
break;
case PROP_GROUP:
priv->group = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gsd_device_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GsdDevicePrivate *priv;
priv = gsd_device_get_instance_private (GSD_DEVICE (object));
switch (prop_id) {
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_DEVICE_FILE:
g_value_set_string (value, priv->device_file);
break;
case PROP_VENDOR_ID:
g_value_set_string (value, priv->vendor_id);
break;
case PROP_PRODUCT_ID:
g_value_set_string (value, priv->product_id);
break;
case PROP_TYPE:
g_value_set_flags (value, priv->type);
break;
case PROP_WIDTH:
g_value_set_uint (value, priv->width);
break;
case PROP_HEIGHT:
g_value_set_uint (value, priv->height);
break;
case PROP_GROUP:
g_value_set_string (value, priv->group);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gsd_device_finalize (GObject *object)
{
GsdDevicePrivate *priv;
priv = gsd_device_get_instance_private (GSD_DEVICE (object));
g_free (priv->name);
g_free (priv->vendor_id);
g_free (priv->product_id);
g_free (priv->device_file);
g_free (priv->group);
G_OBJECT_CLASS (gsd_device_parent_class)->finalize (object);
}
static void
gsd_device_class_init (GsdDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gsd_device_set_property;
object_class->get_property = gsd_device_get_property;
object_class->finalize = gsd_device_finalize;
g_object_class_install_property (object_class,
PROP_NAME,
g_param_spec_string ("name",
"Name",
"Name",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_DEVICE_FILE,
g_param_spec_string ("device-file",
"Device file",
"Device file",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_VENDOR_ID,
g_param_spec_string ("vendor-id",
"Vendor ID",
"Vendor ID",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_PRODUCT_ID,
g_param_spec_string ("product-id",
"Product ID",
"Product ID",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_TYPE,
g_param_spec_flags ("type",
"Device type",
"Device type",
GSD_TYPE_DEVICE_TYPE, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_WIDTH,
g_param_spec_uint ("width",
"Width",
"Width",
0, G_MAXUINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_HEIGHT,
g_param_spec_uint ("height",
"Height",
"Height",
0, G_MAXUINT, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class,
PROP_GROUP,
g_param_spec_string ("group",
"Group",
"Group",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
gsd_device_manager_finalize (GObject *object)
{
GsdDeviceManager *manager = GSD_DEVICE_MANAGER (object);
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
g_hash_table_destroy (priv->devices);
g_object_unref (priv->udev_client);
G_OBJECT_CLASS (gsd_device_manager_parent_class)->finalize (object);
}
static GList *
gsd_device_manager_real_list_devices (GsdDeviceManager *manager,
GsdDeviceType type)
{
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
GsdDeviceType device_type;
GList *devices = NULL;
GHashTableIter iter;
GsdDevice *device;
g_hash_table_iter_init (&iter, priv->devices);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &device)) {
device_type = gsd_device_get_device_type (device);
if ((device_type & type) == type)
devices = g_list_prepend (devices, device);
}
return devices;
}
static GsdDevice *
gsd_device_manager_real_lookup_device (GsdDeviceManager *manager,
GdkDevice *gdk_device)
{
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
GdkDisplay *display = gdk_device_get_display (gdk_device);
const gchar *node_path = NULL;
GHashTableIter iter;
GsdDevice *device;
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (display)) {
XIDeviceInfo *info;
int n_infos, i, source_id = 0;
gdk_x11_display_error_trap_push (display);
info = XIQueryDevice (gdk_x11_display_get_xdisplay (display),
gdk_x11_device_get_id (gdk_device),
&n_infos);
if (gdk_x11_display_error_trap_pop (display) != 0)
return NULL;
if (!info || n_infos != 1)
return NULL;
for (i = 0; i < info->num_classes; i++) {
if (info->classes[i]->type == XIValuatorClass)
source_id = info->classes[i]->sourceid;
}
if (source_id != 0)
node_path = xdevice_get_device_node (source_id);
}
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (display))
node_path = g_strdup (gdk_wayland_device_get_node_path (gdk_device));
#endif
if (!node_path)
return NULL;
g_hash_table_iter_init (&iter, priv->devices);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &device)) {
if (g_strcmp0 (node_path,
gsd_device_get_device_file (device)) == 0) {
return device;
}
}
return NULL;
}
static void
gsd_device_manager_class_init (GsdDeviceManagerClass *klass)
{
GsdDeviceManagerClass *manager_class = GSD_DEVICE_MANAGER_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsd_device_manager_finalize;
manager_class->list_devices = gsd_device_manager_real_list_devices;
manager_class->lookup_device = gsd_device_manager_real_lookup_device;
signals[DEVICE_ADDED] =
g_signal_new ("device-added",
GSD_TYPE_DEVICE_MANAGER,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsdDeviceManagerClass, device_added),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[DEVICE_REMOVED] =
g_signal_new ("device-removed",
GSD_TYPE_DEVICE_MANAGER,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsdDeviceManagerClass, device_removed),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[DEVICE_CHANGED] =
g_signal_new ("device-changed",
GSD_TYPE_DEVICE_MANAGER,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GsdDeviceManagerClass, device_changed),
NULL, NULL, NULL,
G_TYPE_NONE, 1,
GSD_TYPE_DEVICE | G_SIGNAL_TYPE_STATIC_SCOPE);
}
static GsdDeviceType
udev_device_get_device_type (GUdevDevice *device)
{
GsdDeviceType type = 0;
gint i;
for (i = 0; i < G_N_ELEMENTS (udev_ids); i++) {
if (g_udev_device_get_property_as_boolean (device, udev_ids[i]))
type |= (1 << i);
}
return type;
}
static gboolean
device_is_evdev (GUdevDevice *device)
{
const gchar *device_file;
device_file = g_udev_device_get_device_file (device);
if (!device_file || strstr (device_file, "/event") == NULL)
return FALSE;
return g_udev_device_get_property_as_boolean (device, "ID_INPUT");
}
static GsdDevice *
create_device (GUdevDevice *udev_device, GUdevDevice *parent)
{
const gchar *vendor, *product, *name, *group;
guint width, height;
name = g_udev_device_get_sysfs_attr (parent, "name");
vendor = g_udev_device_get_property (udev_device, "ID_VENDOR_ID");
product = g_udev_device_get_property (udev_device, "ID_MODEL_ID");
if (!vendor || !product) {
vendor = g_udev_device_get_sysfs_attr (udev_device, "device/id/vendor");
product = g_udev_device_get_sysfs_attr (udev_device, "device/id/product");
}
width = g_udev_device_get_property_as_int (udev_device, "ID_INPUT_WIDTH_MM");
height = g_udev_device_get_property_as_int (udev_device, "ID_INPUT_HEIGHT_MM");
group = g_udev_device_get_property (udev_device, "LIBINPUT_DEVICE_GROUP");
return g_object_new (GSD_TYPE_DEVICE,
"name", name,
"device-file", g_udev_device_get_device_file (udev_device),
"type", udev_device_get_device_type (udev_device),
"vendor-id", vendor,
"product-id", product,
"width", width,
"height", height,
"group", group,
NULL);
}
static void
add_device (GsdDeviceManager *manager,
GUdevDevice *udev_device)
{
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
g_autoptr(GUdevDevice) parent = NULL;
GsdDevice *device;
const gchar *syspath;
parent = g_udev_device_get_parent (udev_device);
if (!parent)
return;
device = create_device (udev_device, parent);
syspath = g_udev_device_get_sysfs_path (udev_device);
g_hash_table_insert (priv->devices, g_strdup (syspath), device);
g_signal_emit_by_name (manager, "device-added", device);
}
static void
remove_device (GsdDeviceManager *manager,
GUdevDevice *udev_device)
{
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
GsdDevice *device;
const gchar *syspath;
syspath = g_udev_device_get_sysfs_path (udev_device);
device = g_hash_table_lookup (priv->devices, syspath);
if (!device)
return;
g_hash_table_steal (priv->devices, syspath);
g_signal_emit_by_name (manager, "device-removed", device);
g_object_unref (device);
}
static void
udev_event_cb (GUdevClient *client,
gchar *action,
GUdevDevice *device,
GsdDeviceManager *manager)
{
if (!device_is_evdev (device))
return;
if (g_strcmp0 (action, "add") == 0) {
add_device (manager, device);
} else if (g_strcmp0 (action, "remove") == 0) {
remove_device (manager, device);
}
}
static void
gsd_device_manager_init (GsdDeviceManager *manager)
{
GsdDeviceManagerPrivate *priv = gsd_device_manager_get_instance_private (manager);
const gchar *subsystems[] = { "input", NULL };
g_autoptr(GList) devices = NULL;
GList *l;
priv->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
priv->udev_client = g_udev_client_new (subsystems);
g_signal_connect (priv->udev_client, "uevent",
G_CALLBACK (udev_event_cb), manager);
devices = g_udev_client_query_by_subsystem (priv->udev_client,
subsystems[0]);
for (l = devices; l; l = l->next) {
g_autoptr(GUdevDevice) device = l->data;
if (device_is_evdev (device))
add_device (manager, device);
}
}
GsdDeviceManager *
gsd_device_manager_get (void)
{
GsdDeviceManager *manager;
GdkDisplay *display;
display = gdk_display_get_default ();
g_return_val_if_fail (display != NULL, NULL);
manager = g_object_get_data (G_OBJECT (display), "gsd-device-manager-data");
if (!manager) {
manager = g_object_new (GSD_TYPE_DEVICE_MANAGER,
NULL);
g_object_set_data_full (G_OBJECT (display), "gsd-device-manager-data",
manager, (GDestroyNotify) g_object_unref);
}
return manager;
}
GList *
gsd_device_manager_list_devices (GsdDeviceManager *manager,
GsdDeviceType type)
{
g_return_val_if_fail (GSD_IS_DEVICE_MANAGER (manager), NULL);
return GSD_DEVICE_MANAGER_GET_CLASS (manager)->list_devices (manager, type);
}
GsdDeviceType
gsd_device_get_device_type (GsdDevice *device)
{
GsdDevicePrivate *priv;
g_return_val_if_fail (GSD_IS_DEVICE (device), 0);
priv = gsd_device_get_instance_private (device);
return priv->type;
}
void
gsd_device_get_device_ids (GsdDevice *device,
const gchar **vendor,
const gchar **product)
{
GsdDevicePrivate *priv;
g_return_if_fail (GSD_IS_DEVICE (device));
priv = gsd_device_get_instance_private (device);
if (vendor)
*vendor = priv->vendor_id;
if (product)
*product = priv->product_id;
}
GSettings *
gsd_device_get_settings (GsdDevice *device)
{
const gchar *schema = NULL, *vendor, *product;
GsdDeviceType type;
g_autofree gchar *path = NULL;
g_return_val_if_fail (GSD_IS_DEVICE (device), NULL);
type = gsd_device_get_device_type (device);
if (type & (GSD_DEVICE_TYPE_TOUCHSCREEN | GSD_DEVICE_TYPE_TABLET)) {
gsd_device_get_device_ids (device, &vendor, &product);
if (type & GSD_DEVICE_TYPE_TOUCHSCREEN) {
schema = "org.gnome.desktop.peripherals.touchscreen";
path = g_strdup_printf ("/org/gnome/desktop/peripherals/touchscreens/%s:%s/",
vendor, product);
} else if (type & GSD_DEVICE_TYPE_TABLET) {
schema = "org.gnome.desktop.peripherals.tablet";
path = g_strdup_printf ("/org/gnome/desktop/peripherals/tablets/%s:%s/",
vendor, product);
}
} else if (type & (GSD_DEVICE_TYPE_MOUSE | GSD_DEVICE_TYPE_TOUCHPAD)) {
schema = "org.gnome.desktop.peripherals.mouse";
} else if (type & GSD_DEVICE_TYPE_KEYBOARD) {
schema = "org.gnome.desktop.peripherals.keyboard";
} else {
return NULL;
}
if (path) {
return g_settings_new_with_path (schema, path);
} else {
return g_settings_new (schema);
}
}
const gchar *
gsd_device_get_name (GsdDevice *device)
{
GsdDevicePrivate *priv;
g_return_val_if_fail (GSD_IS_DEVICE (device), NULL);
priv = gsd_device_get_instance_private (device);
return priv->name;
}
const gchar *
gsd_device_get_device_file (GsdDevice *device)
{
GsdDevicePrivate *priv;
g_return_val_if_fail (GSD_IS_DEVICE (device), NULL);
priv = gsd_device_get_instance_private (device);
return priv->device_file;
}
gboolean
gsd_device_get_dimensions (GsdDevice *device,
guint *width,
guint *height)
{
GsdDevicePrivate *priv;
g_return_val_if_fail (GSD_IS_DEVICE (device), FALSE);
priv = gsd_device_get_instance_private (device);
if (width)
*width = priv->width;
if (height)
*height = priv->height;
return priv->width > 0 && priv->height > 0;
}
GsdDevice *
gsd_device_manager_lookup_gdk_device (GsdDeviceManager *manager,
GdkDevice *gdk_device)
{
GsdDeviceManagerClass *klass;
g_return_val_if_fail (GSD_IS_DEVICE_MANAGER (manager), NULL);
g_return_val_if_fail (GDK_IS_DEVICE (gdk_device), NULL);
klass = GSD_DEVICE_MANAGER_GET_CLASS (manager);
if (!klass->lookup_device)
return NULL;
return klass->lookup_device (manager, gdk_device);
}
gboolean
gsd_device_shares_group (GsdDevice *device1,
GsdDevice *device2)
{
GsdDevicePrivate *priv1, *priv2;
priv1 = gsd_device_get_instance_private (GSD_DEVICE (device1));
priv2 = gsd_device_get_instance_private (GSD_DEVICE (device2));
/* Don't group NULLs together */
if (!priv1->group && !priv2->group)
return FALSE;
return g_strcmp0 (priv1->group, priv2->group) == 0;
}