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
256 lines
6.7 KiB
C
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;
|
|
}
|