enumerator: Add UpEnumerator class and udev implementation

This commit is contained in:
Benjamin Berg 2022-05-12 15:59:01 +02:00
parent 10290e0e6d
commit 896c77dc7d
9 changed files with 554 additions and 0 deletions

View file

@ -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"' ],

View file

@ -0,0 +1,324 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
*
* 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 <string.h>
#include <gudev/gudev.h>
#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;
}

View file

@ -0,0 +1,31 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
*
* 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

View file

@ -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',

View file

@ -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 */

View file

@ -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)
{

View file

@ -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);

141
src/up-enumerator.c Normal file
View file

@ -0,0 +1,141 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
*
* 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);
}

40
src/up-enumerator.h Normal file
View file

@ -0,0 +1,40 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2022 Benjamin Berg <bberg@redhat.com>
*
* 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 <glib-object.h>
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