diff --git a/src/linux/meson.build b/src/linux/meson.build index b77cd29..ad49f0e 100644 --- a/src/linux/meson.build +++ b/src/linux/meson.build @@ -20,6 +20,7 @@ upshared += { 'linux': static_library('upshared', 'up-input.h', 'up-backend.c', 'up-native.c', + 'up-enumerator-udev.c', idevice_sources ], c_args: [ '-DG_LOG_DOMAIN="UPower-Linux"' ], diff --git a/src/linux/up-enumerator-udev.c b/src/linux/up-enumerator-udev.c new file mode 100644 index 0000000..6e3cf39 --- /dev/null +++ b/src/linux/up-enumerator-udev.c @@ -0,0 +1,324 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Benjamin Berg + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include +#include "up-device.h" +#include "up-config.h" +#include "up-enumerator-udev.h" + +#include "up-device-supply.h" +#include "up-device-hid.h" +#include "up-device-wup.h" + +struct _UpEnumeratorUdev { + UpEnumerator parent; + + GUdevClient *udev; + + /* Contains either a GUdevDevice or a UpDevice wrapping it. */ + GHashTable *known; + GHashTable *siblings; +}; + +G_DEFINE_TYPE (UpEnumeratorUdev, up_enumerator_udev, UP_TYPE_ENUMERATOR) + +static char* +device_parent_id (GUdevDevice *dev) +{ + g_autoptr(GUdevDevice) parent = NULL; + const char *subsystem; + + parent = g_udev_device_get_parent (dev); + if (!parent) + return NULL; + + subsystem = g_udev_device_get_subsystem (parent); + + /* Refusing using certain subsystems as parents. */ + if (g_strcmp0 (subsystem, "platform") == 0) + return NULL; + + /* Continue walk if the parent is a "hid" or "input" device */ + if (g_strcmp0 (subsystem, "hid") == 0 || + g_strcmp0 (subsystem, "input") == 0) + return device_parent_id (parent); + + /* Also skip over USB interfaces, we care about full devices */ + if (g_strcmp0 (subsystem, "usb") == 0 && + g_strcmp0 (g_udev_device_get_devtype (parent), "usb_interface") == 0) + return device_parent_id (parent); + + return g_strdup (g_udev_device_get_sysfs_path (parent)); +} + +static gpointer +is_macbook (gpointer data) +{ + g_autofree char *product = NULL; + + if (!g_file_get_contents ("/sys/devices/virtual/dmi/id/product_name", &product, NULL, NULL) || + product == NULL) + return GINT_TO_POINTER(FALSE); + return GINT_TO_POINTER(g_str_has_prefix (product, "MacBook")); +} + +static UpDevice * +device_new (UpEnumeratorUdev *self, GUdevDevice *native) +{ + UpDaemon *daemon; + const gchar *subsys; + const gchar *native_path; + + daemon = up_enumerator_get_daemon (UP_ENUMERATOR (self)); + + subsys = g_udev_device_get_subsystem (native); + if (g_strcmp0 (subsys, "power_supply") == 0) { + return g_initable_new (UP_TYPE_DEVICE_SUPPLY, NULL, NULL, + "daemon", daemon, + "native", native, + "ignore-system-percentage", GPOINTER_TO_INT (is_macbook (NULL)), + NULL); + + } else if (g_strcmp0 (subsys, "tty") == 0) { + return g_initable_new (UP_TYPE_DEVICE_WUP, NULL, NULL, + "daemon", daemon, + "native", native, + NULL); + + } else if (g_strcmp0 (subsys, "usbmisc") == 0) { +#ifdef HAVE_IDEVICE + UpDevice *device; + + device = g_initable_new (UP_TYPE_DEVICE_IDEVICE, NULL, NULL, + "daemon", daemon, + "native", native, + NULL); + if (device) + return device; +#endif /* HAVE_IDEVICE */ + + return g_initable_new (UP_TYPE_DEVICE_HID, NULL, NULL, + "daemon", daemon, + "native", native, + NULL); + + } else if (g_strcmp0 (subsys, "input") == 0) { + /* Ignore, we only resolve them to see siblings. */ + return NULL; + } else { + native_path = g_udev_device_get_sysfs_path (native); + g_warning ("native path %s (%s) ignoring", native_path, subsys); + return NULL; + } +} + +static void +uevent_signal_handler_cb (UpEnumeratorUdev *self, + const gchar *action, + GUdevDevice *device, + GUdevClient *client) +{ + const char *device_key = g_udev_device_get_sysfs_path (device); + + /* Work around the fact that we don't get a REMOVE event in some cases. */ + if (g_strcmp0 (g_udev_device_get_subsystem (device), "power_supply") == 0) + device_key = g_udev_device_get_name (device); + + /* It appears that we may not always receive an "add" event. As such, + * treat "add"/"change" in the same way, by first checking if we have + * seen the device. + * Even worse, we may not get a "remove" event in some odd cases, so + * if there is an "add" but we find the device (as the power_supply + * node has the same name), then remove it first before adding the + * new one. + */ + if (g_strcmp0 (action, "change") == 0 || g_strcmp0 (action, "add") == 0) { + GObject *obj; + + obj = g_hash_table_lookup (self->known, device_key); + if (UP_IS_DEVICE (obj) && g_strcmp0 (action, "add") == 0 && + g_strcmp0 (g_udev_device_get_sysfs_path (device), + g_udev_device_get_sysfs_path (G_UDEV_DEVICE (up_device_get_native (UP_DEVICE (obj))))) != 0) { + uevent_signal_handler_cb (self, "remove", device, client); + obj = NULL; + } + + if (!obj) { + g_autoptr(UpDevice) up_dev = NULL; + g_autofree char *parent_id = NULL; + + up_dev = device_new (self, device); + + /* We work with `obj` further down, which is the UpDevice + * if we have it, or the GUdevDevice if not. */ + if (up_dev) + obj = G_OBJECT (up_dev); + else + obj = G_OBJECT (device); + g_hash_table_insert (self->known, (char*) device_key, g_object_ref (obj)); + + /* Fire relevant sibling events and insert into lookup table */ + parent_id = device_parent_id (device); + g_debug ("device %s has parent id: %s", device_key, parent_id); + if (parent_id) { + gboolean insert = FALSE; + GPtrArray *devices; + int i; + + devices = g_hash_table_lookup (self->siblings, parent_id); + if (!devices) { + insert = TRUE; + devices = g_ptr_array_new_with_free_func (g_object_unref); + } + + for (i = 0; i < devices->len; i++) { + GObject *sibling = g_ptr_array_index (devices, i); + + if (up_dev) + up_device_sibling_discovered (up_dev, sibling); + if (UP_IS_DEVICE (sibling)) + up_device_sibling_discovered (UP_DEVICE (sibling), obj); + } + + g_ptr_array_add (devices, g_object_ref (obj)); + if (insert) + g_hash_table_insert (self->siblings, g_strdup (parent_id), devices); + } + + if (up_dev) + g_signal_emit_by_name (self, "device-added", up_dev); + + } else { + if (!UP_IS_DEVICE (obj)) + return; + + g_debug ("refreshing device for path %s", g_udev_device_get_sysfs_path (device)); + if (!up_device_refresh_internal (UP_DEVICE (obj), UP_REFRESH_EVENT)) + g_debug ("no changes on %s", up_device_get_object_path (UP_DEVICE (obj))); + + } + } else if (g_strcmp0 (action, "remove") == 0) { + g_autoptr(GObject) obj = NULL; + const char *key = NULL; + + g_debug ("removing device for path %s", g_udev_device_get_sysfs_path (device)); + g_hash_table_steal_extended (self->known, device_key, + (gpointer*) &key, (gpointer*) &obj); + + if (obj) { + g_autofree char *parent_id = device_parent_id (device); + + /* Remove from siblings table */ + if (parent_id) { + GPtrArray *devices; + + devices = g_hash_table_lookup (self->siblings, parent_id); + + g_ptr_array_remove_fast (devices, obj); + } + } + + if (obj && UP_IS_DEVICE (obj)) { + g_signal_emit_by_name (self, "device-removed", obj); + } else + g_debug ("ignoring remove event on %s", g_udev_device_get_sysfs_path (device)); + } +} + +static void +up_enumerator_udev_init (UpEnumeratorUdev *self) +{ + self->known = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, g_object_unref); + self->siblings = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_ptr_array_unref); +} + +static void +up_enumerator_udev_initable_init (UpEnumerator *enumerator) +{ + g_autoptr(UpConfig) config = NULL; + UpEnumeratorUdev *self = UP_ENUMERATOR_UDEV (enumerator); + GUdevDevice *native; + guint i; + const gchar **subsystems; + /* List "input" first just to avoid some sibling hotplugging later */ + const gchar *subsystems_no_wup[] = {"input", "power_supply", "usbmisc", NULL}; + const gchar *subsystems_wup[] = {"input", "power_supply", "usbmisc", "tty", NULL}; + + config = up_config_new (); + if (up_config_get_boolean (config, "EnableWattsUpPro")) + subsystems = subsystems_wup; + else + subsystems = subsystems_no_wup; + + self->udev = g_udev_client_new (subsystems); + g_signal_connect_swapped (self->udev, "uevent", + G_CALLBACK (uevent_signal_handler_cb), self); + + /* Emulate hotplug for existing devices */ + for (i = 0; subsystems[i] != NULL; i++) { + g_autolist(GUdevDevice) devices = NULL; + GList *l; + + g_debug ("registering subsystem : %s", subsystems[i]); + devices = g_udev_client_query_by_subsystem (self->udev, subsystems[i]); + for (l = devices; l != NULL; l = l->next) { + native = l->data; + uevent_signal_handler_cb (self, "add", native, self->udev); + } + } +} + +static void +up_enumerator_udev_dispose (GObject *obj) +{ + UpEnumeratorUdev *self = UP_ENUMERATOR_UDEV (obj); + + g_clear_object (&self->udev); + g_hash_table_remove_all (self->known); + g_hash_table_remove_all (self->siblings); + + G_OBJECT_CLASS (up_enumerator_udev_parent_class)->dispose (obj); +} + +static void +up_enumerator_udev_finalize (GObject *obj) +{ + UpEnumeratorUdev *self = UP_ENUMERATOR_UDEV (obj); + + g_clear_pointer (&self->known, g_hash_table_unref); + g_clear_pointer (&self->siblings, g_hash_table_unref); + + G_OBJECT_CLASS (up_enumerator_udev_parent_class)->finalize (obj); +} + +static void +up_enumerator_udev_class_init (UpEnumeratorUdevClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = up_enumerator_udev_dispose; + object_class->finalize = up_enumerator_udev_finalize; + + UP_ENUMERATOR_CLASS (klass)->initable_init = up_enumerator_udev_initable_init; +} diff --git a/src/linux/up-enumerator-udev.h b/src/linux/up-enumerator-udev.h new file mode 100644 index 0000000..877eefc --- /dev/null +++ b/src/linux/up-enumerator-udev.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Benjamin Berg + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include "up-enumerator.h" + +G_BEGIN_DECLS + +#define UP_TYPE_ENUMERATOR_UDEV (up_enumerator_udev_get_type ()) + +G_DECLARE_FINAL_TYPE (UpEnumeratorUdev, up_enumerator_udev, UP, ENUMERATOR_UDEV, UpEnumerator) + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index 3ee7073..b49d5f0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -33,6 +33,8 @@ upowerd_private = static_library('upowerd-private', 'up-device.c', 'up-device-list.h', 'up-device-list.c', + 'up-enumerator.c', + 'up-enumerator.h', 'up-kbd-backlight.h', 'up-kbd-backlight.c', 'up-history.h', diff --git a/src/up-config.h b/src/up-config.h index f02d37a..bfe80eb 100644 --- a/src/up-config.h +++ b/src/up-config.h @@ -55,6 +55,8 @@ guint up_config_get_uint (UpConfig *config, gchar *up_config_get_string (UpConfig *config, const gchar *key); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(UpConfig, g_object_unref) + G_END_DECLS #endif /* __UP_CONFIG_H */ diff --git a/src/up-device.c b/src/up-device.c index 575f530..9ba1317 100644 --- a/src/up-device.c +++ b/src/up-device.c @@ -608,6 +608,15 @@ out: return TRUE; } +void +up_device_sibling_discovered (UpDevice *device, GObject *sibling) +{ + UpDeviceClass *klass = UP_DEVICE_GET_CLASS (device); + + if (klass->sibling_discovered) + klass->sibling_discovered (device, sibling); +} + gboolean up_device_refresh_internal (UpDevice *device, UpRefreshReason reason) { diff --git a/src/up-device.h b/src/up-device.h index 8f0c0c6..4f7f6bd 100644 --- a/src/up-device.h +++ b/src/up-device.h @@ -45,6 +45,8 @@ struct _UpDeviceClass /* vtable */ gboolean (*coldplug) (UpDevice *device); + void (*sibling_discovered) (UpDevice *device, + GObject *sibling); gboolean (*refresh) (UpDevice *device, UpRefreshReason reason); const gchar *(*get_id) (UpDevice *device); @@ -65,6 +67,8 @@ gboolean up_device_get_on_battery (UpDevice *device, gboolean *on_battery); gboolean up_device_get_online (UpDevice *device, gboolean *online); +void up_device_sibling_discovered (UpDevice *device, + GObject *sibling); gboolean up_device_refresh_internal (UpDevice *device, UpRefreshReason reason); diff --git a/src/up-enumerator.c b/src/up-enumerator.c new file mode 100644 index 0000000..db6c80d --- /dev/null +++ b/src/up-enumerator.c @@ -0,0 +1,141 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Benjamin Berg + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "up-daemon.h" +#include "up-enumerator.h" +#include "up-device.h" + +typedef struct { + UpDaemon *daemon; +} UpEnumeratorPrivate; + +static void up_enumerator_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_EXTENDED (UpEnumerator, up_enumerator, G_TYPE_OBJECT, + G_TYPE_FLAG_ABSTRACT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + up_enumerator_initable_iface_init) + G_ADD_PRIVATE (UpEnumerator)) + +enum { + PROP_0, + PROP_DAEMON, + N_PROPS +}; + +static GParamSpec *properties[N_PROPS]; + +enum { + SIGNAL_DEVICE_ADDED, + SIGNAL_DEVICE_REMOVED, + SIGNAL_LAST +}; + +static guint signals [SIGNAL_LAST] = { 0 }; + +UpDaemon* +up_enumerator_get_daemon (UpEnumerator *self) +{ + UpEnumeratorPrivate *priv = up_enumerator_get_instance_private (self); + + return priv->daemon; +} + +static void +up_enumerator_init (UpEnumerator *self) +{ +} + +static gboolean +up_enumerator_initable_init (GInitable *obj, + GCancellable *cancellable, + GError **error) +{ + UP_ENUMERATOR_GET_CLASS (obj)->initable_init (UP_ENUMERATOR (obj)); + + return TRUE; +} + +static void +up_enumerator_initable_iface_init (GInitableIface *iface) +{ + iface->init = up_enumerator_initable_init; +} + +static void +up_enumerator_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + UpEnumerator *self = UP_ENUMERATOR (object); + UpEnumeratorPrivate *priv = up_enumerator_get_instance_private (self); + + switch (prop_id) + { + case PROP_DAEMON: + priv->daemon = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +up_enumerator_dispose (GObject *object) +{ + UpEnumerator *self = UP_ENUMERATOR (object); + UpEnumeratorPrivate *priv = up_enumerator_get_instance_private (self); + + g_clear_object (&priv->daemon); +} + +static void +up_enumerator_class_init (UpEnumeratorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = up_enumerator_dispose; + + object_class->set_property = up_enumerator_set_property; + + properties[PROP_DAEMON] = + g_param_spec_object ("daemon", + "UpDaemon", + "UpDaemon reference", + UP_TYPE_DAEMON, + G_PARAM_STATIC_STRINGS | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); + + signals [SIGNAL_DEVICE_ADDED] = + g_signal_new ("device-added", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, UP_TYPE_DEVICE); + signals [SIGNAL_DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, UP_TYPE_DEVICE); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} diff --git a/src/up-enumerator.h b/src/up-enumerator.h new file mode 100644 index 0000000..07fb4d7 --- /dev/null +++ b/src/up-enumerator.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2022 Benjamin Berg + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define UP_TYPE_ENUMERATOR (up_enumerator_get_type ()) + +G_DECLARE_DERIVABLE_TYPE (UpEnumerator, up_enumerator, UP, ENUMERATOR, GObject) + +struct _UpEnumeratorClass +{ + GObjectClass parent_class; + + void (*initable_init) (UpEnumerator *self); +}; + +UpDaemon *up_enumerator_get_daemon (UpEnumerator *self); + +G_END_DECLS