gnome-control-center/panels/common/gsd-device-manager-udev.c
Peter Hutterer 008b1f68c8 common: fix udev-based device removal
libgudev allocs a new GUdevDevice object for each event, so the pointer value
for the 'add' udev event differs from the one for the 'remove' event. If we
use the pointer value as hash table key, we'll never remove the device.
Switch to use the syspath of the device instead, that one is unique per
device.

Fixes #309
2018-12-11 10:56:28 +10:00

256 lines
6.7 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2015 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 <gdk/gdkwayland.h>
#include "gsd-device-manager-udev.h"
struct _GsdUdevDeviceManager
{
GsdDeviceManager parent_instance;
GHashTable *devices;
GUdevClient *udev_client;
};
G_DEFINE_TYPE (GsdUdevDeviceManager, gsd_udev_device_manager, GSD_TYPE_DEVICE_MANAGER)
/* 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 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)
{
const gchar *vendor, *product, *name;
guint width, height;
g_autoptr(GUdevDevice) parent = NULL;
parent = g_udev_device_get_parent (udev_device);
g_assert (parent != NULL);
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");
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,
NULL);
}
static void
add_device (GsdUdevDeviceManager *manager,
GUdevDevice *udev_device)
{
GUdevDevice *parent;
GsdDevice *device;
gchar *syspath;
parent = g_udev_device_get_parent (udev_device);
if (!parent)
return;
device = create_device (udev_device);
syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
g_hash_table_insert (manager->devices, syspath, device);
g_signal_emit_by_name (manager, "device-added", device);
}
static void
remove_device (GsdUdevDeviceManager *manager,
GUdevDevice *udev_device)
{
GsdDevice *device;
gchar *syspath;
syspath = g_strdup (g_udev_device_get_sysfs_path (udev_device));
device = g_hash_table_lookup (manager->devices, syspath);
if (!device)
return;
g_hash_table_steal (manager->devices, syspath);
g_signal_emit_by_name (manager, "device-removed", device);
g_object_unref (device);
g_free (syspath);
}
static void
udev_event_cb (GUdevClient *client,
gchar *action,
GUdevDevice *device,
GsdUdevDeviceManager *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_udev_device_manager_init (GsdUdevDeviceManager *manager)
{
const gchar *subsystems[] = { "input", NULL };
g_autoptr(GList) devices = NULL;
GList *l;
manager->devices = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_object_unref);
manager->udev_client = g_udev_client_new (subsystems);
g_signal_connect (manager->udev_client, "uevent",
G_CALLBACK (udev_event_cb), manager);
devices = g_udev_client_query_by_subsystem (manager->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);
}
}
static void
gsd_udev_device_manager_finalize (GObject *object)
{
GsdUdevDeviceManager *manager = GSD_UDEV_DEVICE_MANAGER (object);
g_hash_table_destroy (manager->devices);
g_object_unref (manager->udev_client);
G_OBJECT_CLASS (gsd_udev_device_manager_parent_class)->finalize (object);
}
static GList *
gsd_udev_device_manager_list_devices (GsdDeviceManager *manager,
GsdDeviceType type)
{
GsdUdevDeviceManager *manager_udev = GSD_UDEV_DEVICE_MANAGER (manager);
GsdDeviceType device_type;
GList *devices = NULL;
GHashTableIter iter;
GsdDevice *device;
g_hash_table_iter_init (&iter, manager_udev->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_udev_device_manager_lookup_device (GsdDeviceManager *manager,
GdkDevice *gdk_device)
{
const gchar *node_path;
GHashTableIter iter;
GsdDevice *device;
node_path = gdk_wayland_device_get_node_path (gdk_device);
if (!node_path)
return NULL;
g_hash_table_iter_init (&iter, GSD_UDEV_DEVICE_MANAGER (manager)->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_udev_device_manager_class_init (GsdUdevDeviceManagerClass *klass)
{
GsdDeviceManagerClass *manager_class = GSD_DEVICE_MANAGER_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = gsd_udev_device_manager_finalize;
manager_class->list_devices = gsd_udev_device_manager_list_devices;
manager_class->lookup_device = gsd_udev_device_manager_lookup_device;
}