From e8b81b3d4a7d3f2f2f71e9a5e2c837468cbb99cc Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 24 May 2026 13:43:09 +0200 Subject: [PATCH 1/3] backend/session: introduce wlr_device_manager --- backend/libinput/backend.c | 10 +- backend/session/meson.build | 2 +- backend/session/session.c | 303 ++++++------------------------ backend/session/udev.c | 259 +++++++++++++++++++++++++ include/backend/session/session.h | 18 ++ include/backend/session/udev.h | 17 ++ include/wlr/backend/session.h | 4 +- 7 files changed, 362 insertions(+), 251 deletions(-) create mode 100644 backend/session/udev.c create mode 100644 include/backend/session/udev.h diff --git a/backend/libinput/backend.c b/backend/libinput/backend.c index 909292864..ee605e7ee 100644 --- a/backend/libinput/backend.c +++ b/backend/libinput/backend.c @@ -6,6 +6,7 @@ #include #include #include "backend/libinput.h" +#include "backend/session/udev.h" #include "util/env.h" static struct wlr_libinput_backend *get_libinput_backend_from_backend( @@ -181,6 +182,13 @@ static void handle_session_destroy(struct wl_listener *listener, void *data) { } struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session) { + struct wlr_udev_device_manager *device_manager = + wlr_udev_device_manager_try_from_base(session->device_manager); + if (device_manager == NULL) { + wlr_log(WLR_ERROR, "libinput requires an udev device manager"); + return NULL; + } + struct wlr_libinput_backend *backend = calloc(1, sizeof(*backend)); if (!backend) { wlr_log(WLR_ERROR, "Allocation failed: %s", strerror(errno)); @@ -192,7 +200,7 @@ struct wlr_backend *wlr_libinput_backend_create(struct wlr_session *session) { backend->session = session; backend->libinput_context = libinput_udev_create_context(&libinput_impl, - backend, backend->session->udev); + backend, device_manager->udev); if (!backend->libinput_context) { wlr_log(WLR_ERROR, "Failed to create libinput context"); wlr_backend_finish(&backend->backend); diff --git a/backend/session/meson.build b/backend/session/meson.build index 4c20ee9df..050a7885c 100644 --- a/backend/session/meson.build +++ b/backend/session/meson.build @@ -12,6 +12,6 @@ if not (udev.found() and libseat.found()) subdir_done() endif -wlr_files += files('session.c') +wlr_files += files('session.c', 'udev.c') wlr_deps += [udev, libseat] features += { 'session': true } diff --git a/backend/session/session.c b/backend/session/session.c index 868774399..8a87f60a3 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -15,12 +15,43 @@ #include #include #include "backend/session/session.h" +#include "backend/session/udev.h" #include "util/time.h" -#include - #define WAIT_GPU_TIMEOUT 10000 // ms +struct wlr_device *session_find_device_by_devid(struct wlr_session *session, dev_t devid) { + struct wlr_device *dev; + wl_list_for_each(dev, &session->devices, link) { + if (dev->dev == devid) { + return dev; + } + } + return NULL; +} + +void wlr_device_manager_init(struct wlr_device_manager *manager, + const struct wlr_device_manager_impl *impl, struct wlr_session *session) { + assert(impl && impl->destroy && impl->find_drm_cards); + *manager = (struct wlr_device_manager){ + .session = session, + .impl = impl, + }; +} + +void wlr_device_manager_finish(struct wlr_device_manager *manager) { + // No-op +} + +static void device_manager_destroy(struct wlr_device_manager *manager) { + manager->impl->destroy(manager); +} + +static ssize_t device_manager_find_drm_cards(struct wlr_device_manager *manager, + size_t ret_cap, struct wlr_device **ret) { + return manager->impl->find_drm_cards(manager, ret_cap, ret); +} + static void handle_enable_seat(struct libseat *seat, void *data) { struct wlr_session *session = data; session->active = true; @@ -133,115 +164,6 @@ static void libseat_session_finish(struct wlr_session *session) { session->libseat_event = NULL; } -static bool is_drm_card(const char *sysname) { - const char prefix[] = DRM_PRIMARY_MINOR_NAME; - if (strncmp(sysname, prefix, strlen(prefix)) != 0) { - return false; - } - for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { - if (sysname[i] < '0' || sysname[i] > '9') { - return false; - } - } - return true; -} - -static void read_udev_change_event(struct wlr_device_change_event *event, - struct udev_device *udev_dev) { - const char *hotplug = udev_device_get_property_value(udev_dev, "HOTPLUG"); - if (hotplug != NULL && strcmp(hotplug, "1") == 0) { - event->type = WLR_DEVICE_HOTPLUG; - struct wlr_device_hotplug_event *hotplug = &event->hotplug; - - const char *connector = - udev_device_get_property_value(udev_dev, "CONNECTOR"); - if (connector != NULL) { - hotplug->connector_id = strtoul(connector, NULL, 10); - } - - const char *prop = - udev_device_get_property_value(udev_dev, "PROPERTY"); - if (prop != NULL) { - hotplug->prop_id = strtoul(prop, NULL, 10); - } - - return; - } - - const char *lease = udev_device_get_property_value(udev_dev, "LEASE"); - if (lease != NULL && strcmp(lease, "1") == 0) { - event->type = WLR_DEVICE_LEASE; - return; - } -} - -static int handle_udev_event(int fd, uint32_t mask, void *data) { - struct wlr_session *session = data; - - struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); - if (!udev_dev) { - return 1; - } - - const char *sysname = udev_device_get_sysname(udev_dev); - const char *devnode = udev_device_get_devnode(udev_dev); - const char *action = udev_device_get_action(udev_dev); - wlr_log(WLR_DEBUG, "udev event for %s (%s)", sysname, action); - - if (!is_drm_card(sysname) || !action || !devnode) { - goto out; - } - - const char *seat = udev_device_get_property_value(udev_dev, "ID_SEAT"); - if (!seat) { - seat = "seat0"; - } - if (session->seat[0] != '\0' && strcmp(session->seat, seat) != 0) { - goto out; - } - - dev_t devnum = udev_device_get_devnum(udev_dev); - if (strcmp(action, "add") == 0) { - struct wlr_device *dev; - wl_list_for_each(dev, &session->devices, link) { - if (dev->dev == devnum) { - wlr_log(WLR_DEBUG, "Skipping duplicate device %s", sysname); - goto out; - } - } - - wlr_log(WLR_DEBUG, "DRM device %s added", sysname); - struct wlr_session_add_event event = { - .path = devnode, - }; - wl_signal_emit_mutable(&session->events.add_drm_card, &event); - } else if (strcmp(action, "change") == 0) { - struct wlr_device *dev; - wl_list_for_each(dev, &session->devices, link) { - if (dev->dev == devnum) { - wlr_log(WLR_DEBUG, "DRM device %s changed", sysname); - struct wlr_device_change_event event = {0}; - read_udev_change_event(&event, udev_dev); - wl_signal_emit_mutable(&dev->events.change, &event); - break; - } - } - } else if (strcmp(action, "remove") == 0) { - struct wlr_device *dev; - wl_list_for_each(dev, &session->devices, link) { - if (dev->dev == devnum) { - wlr_log(WLR_DEBUG, "DRM device %s removed", sysname); - wl_signal_emit_mutable(&dev->events.remove, NULL); - break; - } - } - } - -out: - udev_device_unref(udev_dev); - return 1; -} - static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { struct wlr_session *session = wl_container_of(listener, session, event_loop_destroy); @@ -266,39 +188,17 @@ struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) { goto error_open; } - session->udev = udev_new(); - if (!session->udev) { - wlr_log_errno(WLR_ERROR, "Failed to create udev context"); + session->device_manager = wlr_udev_device_manager_create(session); + if (session->device_manager == NULL) { + wlr_log(WLR_ERROR, "Failed to create udev device manager"); goto error_session; } - session->mon = udev_monitor_new_from_netlink(session->udev, "udev"); - if (!session->mon) { - wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); - goto error_udev; - } - - udev_monitor_filter_add_match_subsystem_devtype(session->mon, "drm", NULL); - udev_monitor_enable_receiving(session->mon); - - int fd = udev_monitor_get_fd(session->mon); - - session->udev_event = wl_event_loop_add_fd(event_loop, fd, - WL_EVENT_READABLE, handle_udev_event, session); - if (!session->udev_event) { - wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); - goto error_mon; - } - session->event_loop_destroy.notify = handle_event_loop_destroy; wl_event_loop_add_destroy_listener(event_loop, &session->event_loop_destroy); return session; -error_mon: - udev_monitor_unref(session->mon); -error_udev: - udev_unref(session->udev); error_session: libseat_session_finish(session); error_open: @@ -319,9 +219,7 @@ void wlr_session_destroy(struct wlr_session *session) { wl_list_remove(&session->event_loop_destroy.link); - wl_event_source_remove(session->udev_event); - udev_monitor_unref(session->mon); - udev_unref(session->udev); + device_manager_destroy(session->device_manager); struct wlr_device *dev, *tmp_dev; wl_list_for_each_safe(dev, tmp_dev, &session->devices, link) { @@ -443,25 +341,6 @@ static ssize_t explicit_find_gpus(struct wlr_session *session, return i; } -static struct udev_enumerate *enumerate_drm_cards(struct udev *udev) { - struct udev_enumerate *en = udev_enumerate_new(udev); - if (!en) { - wlr_log(WLR_ERROR, "udev_enumerate_new failed"); - return NULL; - } - - udev_enumerate_add_match_subsystem(en, "drm"); - udev_enumerate_add_match_sysname(en, DRM_PRIMARY_MINOR_NAME "[0-9]*"); - - if (udev_enumerate_scan_devices(en) != 0) { - wlr_log(WLR_ERROR, "udev_enumerate_scan_devices failed"); - udev_enumerate_unref(en); - return NULL; - } - - return en; -} - struct find_gpus_add_handler { bool added; struct wl_listener listener; @@ -481,103 +360,35 @@ ssize_t wlr_session_find_gpus(struct wlr_session *session, return explicit_find_gpus(session, ret_len, ret, explicit); } - struct udev_enumerate *en = enumerate_drm_cards(session->udev); - if (!en) { - return -1; + ssize_t n = device_manager_find_drm_cards(session->device_manager, ret_len, ret); + if (n != 0) { + return n; } - if (udev_enumerate_get_list_entry(en) == NULL) { - udev_enumerate_unref(en); - en = NULL; - wlr_log(WLR_INFO, "Waiting for a KMS device"); + wlr_log(WLR_INFO, "Waiting for a KMS device"); - struct find_gpus_add_handler handler = {0}; - handler.listener.notify = find_gpus_handle_add; - wl_signal_add(&session->events.add_drm_card, &handler.listener); + struct find_gpus_add_handler handler = {0}; + handler.listener.notify = find_gpus_handle_add; + wl_signal_add(&session->events.add_drm_card, &handler.listener); - int64_t started_at = get_current_time_msec(); - int64_t timeout = WAIT_GPU_TIMEOUT; - while (!handler.added) { - int ret = wl_event_loop_dispatch(session->event_loop, (int)timeout); - if (ret < 0) { - wlr_log_errno(WLR_ERROR, "Failed to wait for KMS device: " - "wl_event_loop_dispatch failed"); - return -1; - } - - int64_t now = get_current_time_msec(); - if (now >= started_at + WAIT_GPU_TIMEOUT) { - break; - } - timeout = started_at + WAIT_GPU_TIMEOUT - now; - } - - wl_list_remove(&handler.listener.link); - - en = enumerate_drm_cards(session->udev); - if (!en) { + int64_t started_at = get_current_time_msec(); + int64_t timeout = WAIT_GPU_TIMEOUT; + while (!handler.added) { + int ret = wl_event_loop_dispatch(session->event_loop, (int)timeout); + if (ret < 0) { + wlr_log_errno(WLR_ERROR, "Failed to wait for KMS device: " + "wl_event_loop_dispatch failed"); return -1; } - } - struct udev_list_entry *entry; - size_t i = 0; - - udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { - if (i == ret_len) { + int64_t now = get_current_time_msec(); + if (now >= started_at + WAIT_GPU_TIMEOUT) { break; } - - const char *path = udev_list_entry_get_name(entry); - struct udev_device *dev = udev_device_new_from_syspath(session->udev, path); - if (!dev) { - continue; - } - - const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); - if (!seat) { - seat = "seat0"; - } - if (session->seat[0] && strcmp(session->seat, seat) != 0) { - udev_device_unref(dev); - continue; - } - - bool is_primary = false; - const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display"); - if (boot_display && strcmp(boot_display, "1") == 0) { - is_primary = true; - } else { - // This is owned by 'dev', so we don't need to free it - struct udev_device *pci = - udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); - - if (pci) { - const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); - if (id && strcmp(id, "1") == 0) { - is_primary = true; - } - } - } - - struct wlr_device *wlr_dev = - session_open_if_kms(session, udev_device_get_devnode(dev)); - udev_device_unref(dev); - if (!wlr_dev) { - continue; - } - - ret[i] = wlr_dev; - if (is_primary) { - struct wlr_device *tmp = ret[0]; - ret[0] = ret[i]; - ret[i] = tmp; - } - - ++i; + timeout = started_at + WAIT_GPU_TIMEOUT - now; } - udev_enumerate_unref(en); + wl_list_remove(&handler.listener.link); - return i; + return device_manager_find_drm_cards(session->device_manager, ret_len, ret); } diff --git a/backend/session/udev.c b/backend/session/udev.c new file mode 100644 index 000000000..201f22565 --- /dev/null +++ b/backend/session/udev.c @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include + +#include "backend/session/session.h" +#include "backend/session/udev.h" + +static struct udev_enumerate *enumerate_drm_cards(struct udev *udev) { + struct udev_enumerate *en = udev_enumerate_new(udev); + if (!en) { + wlr_log(WLR_ERROR, "udev_enumerate_new failed"); + return NULL; + } + + udev_enumerate_add_match_subsystem(en, "drm"); + udev_enumerate_add_match_sysname(en, DRM_PRIMARY_MINOR_NAME "[0-9]*"); + + if (udev_enumerate_scan_devices(en) != 0) { + wlr_log(WLR_ERROR, "udev_enumerate_scan_devices failed"); + udev_enumerate_unref(en); + return NULL; + } + + return en; +} + +static ssize_t manager_find_drm_cards(struct wlr_device_manager *base, + size_t ret_cap, struct wlr_device **ret) { + struct wlr_udev_device_manager *manager = wl_container_of(base, manager, base); + struct wlr_session *session = manager->base.session; + + struct udev_enumerate *en = enumerate_drm_cards(manager->udev); + if (!en) { + return -1; + } + + struct udev_list_entry *entry; + size_t i = 0; + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(en)) { + if (i == ret_cap) { + break; + } + + const char *path = udev_list_entry_get_name(entry); + struct udev_device *dev = udev_device_new_from_syspath(manager->udev, path); + if (!dev) { + continue; + } + + const char *seat = udev_device_get_property_value(dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] && strcmp(session->seat, seat) != 0) { + udev_device_unref(dev); + continue; + } + + bool is_primary = false; + const char *boot_display = udev_device_get_sysattr_value(dev, "boot_display"); + if (boot_display && strcmp(boot_display, "1") == 0) { + is_primary = true; + } else { + // This is owned by 'dev', so we don't need to free it + struct udev_device *pci = + udev_device_get_parent_with_subsystem_devtype(dev, "pci", NULL); + + if (pci) { + const char *id = udev_device_get_sysattr_value(pci, "boot_vga"); + if (id && strcmp(id, "1") == 0) { + is_primary = true; + } + } + } + + struct wlr_device *wlr_dev = + session_open_if_kms(session, udev_device_get_devnode(dev)); + udev_device_unref(dev); + if (!wlr_dev) { + continue; + } + + ret[i] = wlr_dev; + if (is_primary) { + struct wlr_device *tmp = ret[0]; + ret[0] = ret[i]; + ret[i] = tmp; + } + + ++i; + } + + udev_enumerate_unref(en); + + return i; +} + +static void manager_destroy(struct wlr_device_manager *base) { + struct wlr_udev_device_manager *manager = wl_container_of(base, manager, base); + + wl_event_source_remove(manager->udev_event); + udev_monitor_unref(manager->mon); + udev_unref(manager->udev); + free(manager); +} + +static const struct wlr_device_manager_impl manager_impl = { + .destroy = manager_destroy, + .find_drm_cards = manager_find_drm_cards, +}; + +static bool is_drm_card(const char *sysname) { + const char prefix[] = DRM_PRIMARY_MINOR_NAME; + if (strncmp(sysname, prefix, strlen(prefix)) != 0) { + return false; + } + for (size_t i = strlen(prefix); sysname[i] != '\0'; i++) { + if (sysname[i] < '0' || sysname[i] > '9') { + return false; + } + } + return true; +} + +static void read_udev_change_event(struct wlr_device_change_event *event, + struct udev_device *udev_dev) { + const char *hotplug = udev_device_get_property_value(udev_dev, "HOTPLUG"); + if (hotplug != NULL && strcmp(hotplug, "1") == 0) { + event->type = WLR_DEVICE_HOTPLUG; + struct wlr_device_hotplug_event *hotplug = &event->hotplug; + + const char *connector = + udev_device_get_property_value(udev_dev, "CONNECTOR"); + if (connector != NULL) { + hotplug->connector_id = strtoul(connector, NULL, 10); + } + + const char *prop = + udev_device_get_property_value(udev_dev, "PROPERTY"); + if (prop != NULL) { + hotplug->prop_id = strtoul(prop, NULL, 10); + } + + return; + } + + const char *lease = udev_device_get_property_value(udev_dev, "LEASE"); + if (lease != NULL && strcmp(lease, "1") == 0) { + event->type = WLR_DEVICE_LEASE; + return; + } +} + +static int handle_udev_event(int fd, uint32_t mask, void *data) { + struct wlr_udev_device_manager *manager = data; + struct wlr_session *session = manager->base.session; + + struct udev_device *udev_dev = udev_monitor_receive_device(manager->mon); + if (!udev_dev) { + return 1; + } + + const char *sysname = udev_device_get_sysname(udev_dev); + const char *devnode = udev_device_get_devnode(udev_dev); + const char *action = udev_device_get_action(udev_dev); + wlr_log(WLR_DEBUG, "udev event for %s (%s)", sysname, action); + + if (!is_drm_card(sysname) || !action || !devnode) { + goto out; + } + + const char *seat = udev_device_get_property_value(udev_dev, "ID_SEAT"); + if (!seat) { + seat = "seat0"; + } + if (session->seat[0] != '\0' && strcmp(session->seat, seat) != 0) { + goto out; + } + + dev_t devnum = udev_device_get_devnum(udev_dev); + struct wlr_device *dev = session_find_device_by_devid(session, devnum); + if (strcmp(action, "add") == 0) { + if (dev != NULL) { + wlr_log(WLR_DEBUG, "Skipping duplicate device %s", sysname); + goto out; + } + + wlr_log(WLR_DEBUG, "DRM device %s added", sysname); + struct wlr_session_add_event event = { + .path = devnode, + }; + wl_signal_emit_mutable(&session->events.add_drm_card, &event); + } else if (strcmp(action, "change") == 0 && dev != NULL) { + wlr_log(WLR_DEBUG, "DRM device %s changed", sysname); + struct wlr_device_change_event event = {0}; + read_udev_change_event(&event, udev_dev); + wl_signal_emit_mutable(&dev->events.change, &event); + } else if (strcmp(action, "remove") == 0 && dev != NULL) { + wlr_log(WLR_DEBUG, "DRM device %s removed", sysname); + wl_signal_emit_mutable(&dev->events.remove, NULL); + } + +out: + udev_device_unref(udev_dev); + return 1; +} + +struct wlr_device_manager *wlr_udev_device_manager_create(struct wlr_session *session) { + struct wlr_udev_device_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + wlr_device_manager_init(&manager->base, &manager_impl, session); + + manager->udev = udev_new(); + if (!manager->udev) { + wlr_log_errno(WLR_ERROR, "Failed to create udev context"); + goto error_manager; + } + + manager->mon = udev_monitor_new_from_netlink(manager->udev, "udev"); + if (!manager->mon) { + wlr_log_errno(WLR_ERROR, "Failed to create udev monitor"); + goto error_udev; + } + + udev_monitor_filter_add_match_subsystem_devtype(manager->mon, "drm", NULL); + udev_monitor_enable_receiving(manager->mon); + + int fd = udev_monitor_get_fd(manager->mon); + + manager->udev_event = wl_event_loop_add_fd(session->event_loop, fd, + WL_EVENT_READABLE, handle_udev_event, manager); + if (!manager->udev_event) { + wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); + goto error_mon; + } + + return &manager->base; + +error_mon: + udev_monitor_unref(manager->mon); +error_udev: + udev_unref(manager->udev); +error_manager: + free(manager); + return NULL; +} + +struct wlr_udev_device_manager *wlr_udev_device_manager_try_from_base(struct wlr_device_manager *base) { + if (base->impl != &manager_impl) { + return NULL; + } + struct wlr_udev_device_manager *manager = wl_container_of(base, manager, base); + return manager; +} diff --git a/include/backend/session/session.h b/include/backend/session/session.h index 0275f69fa..c95026ffe 100644 --- a/include/backend/session/session.h +++ b/include/backend/session/session.h @@ -2,6 +2,7 @@ #define BACKEND_SESSION_SESSION_H #include +#include struct wl_display; struct wlr_session; @@ -16,5 +17,22 @@ void session_init(struct wlr_session *session); struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, const char *restrict path); +struct wlr_device *session_find_device_by_devid(struct wlr_session *session, dev_t devid); + +struct wlr_device_manager { + struct wlr_session *session; + + const struct wlr_device_manager_impl *impl; +}; + +struct wlr_device_manager_impl { + void (*destroy)(struct wlr_device_manager *manager); + ssize_t (*find_drm_cards)(struct wlr_device_manager *manager, + size_t ret_cap, struct wlr_device **ret); +}; + +void wlr_device_manager_init(struct wlr_device_manager *manager, + const struct wlr_device_manager_impl *impl, struct wlr_session *session); +void wlr_device_manager_finish(struct wlr_device_manager *manager); #endif diff --git a/include/backend/session/udev.h b/include/backend/session/udev.h new file mode 100644 index 000000000..6f5732997 --- /dev/null +++ b/include/backend/session/udev.h @@ -0,0 +1,17 @@ +#ifndef BACKEND_SESSION_UDEV_H +#define BACKEND_SESSION_UDEV_H + +#include "backend/session/session.h" + +struct wlr_udev_device_manager { + struct wlr_device_manager base; + + struct udev *udev; + struct udev_monitor *mon; + struct wl_event_source *udev_event; +}; + +struct wlr_device_manager *wlr_udev_device_manager_create(struct wlr_session *session); +struct wlr_udev_device_manager *wlr_udev_device_manager_try_from_base(struct wlr_device_manager *base); + +#endif diff --git a/include/wlr/backend/session.h b/include/wlr/backend/session.h index a1ff0c317..4c27e9577 100644 --- a/include/wlr/backend/session.h +++ b/include/wlr/backend/session.h @@ -45,9 +45,7 @@ struct wlr_session { char seat[256]; - struct udev *udev; - struct udev_monitor *mon; - struct wl_event_source *udev_event; + struct wlr_device_manager *device_manager; struct libseat *seat_handle; struct wl_event_source *libseat_event; From 905ebaa89a52a20a37458308e76924942b2ec25b Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 24 May 2026 13:54:23 +0200 Subject: [PATCH 2/3] backend/session: make udev dependency optional udev is now optional at build-time but will fail at runtime because we don't have an alternative implementation yet. --- backend/libinput/meson.build | 11 ++++++++--- backend/session/meson.build | 17 +++++++++++------ backend/session/session.c | 11 ++++++++++- meson.build | 1 + meson.options | 1 + 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/backend/libinput/meson.build b/backend/libinput/meson.build index 091b0e0eb..e3e6b5ed1 100644 --- a/backend/libinput/meson.build +++ b/backend/libinput/meson.build @@ -1,16 +1,21 @@ msg = ['Required for libinput backend support.'] if 'libinput' in backends - msg += 'Install "libinput" or disable the libinput backend.' + msg += 'Install "@0@" or disable the libinput backend.' endif libinput = dependency( 'libinput', version: '>=1.19.0', required: 'libinput' in backends, - not_found_message: '\n'.join(msg), + not_found_message: '\n'.join(msg).format('libinput'), +) +udev = dependency( + 'libudev', + required: 'libinput' in backends, + not_found_message: '\n'.join(msg).format('libudev'), ) -if not (libinput.found() and features['session']) +if not (libinput.found() and udev.found() and features['session']) subdir_done() endif diff --git a/backend/session/meson.build b/backend/session/meson.build index 050a7885c..becc08232 100644 --- a/backend/session/meson.build +++ b/backend/session/meson.build @@ -1,17 +1,22 @@ -msg = 'Required for session support.' -udev = dependency('libudev', required: session_required, not_found_message: msg) libseat = dependency( 'libseat', version: '>=0.2.0', fallback: 'seatd', default_options: ['server=disabled', 'man-pages=disabled', 'examples=disabled'], required: session_required, - not_found_message: msg, + not_found_message: 'Required for session support.', ) -if not (udev.found() and libseat.found()) +if not libseat.found() subdir_done() endif -wlr_files += files('session.c', 'udev.c') -wlr_deps += [udev, libseat] +wlr_files += files('session.c') +wlr_deps += [libseat] features += { 'session': true } + +udev = dependency('libudev', required: get_option('udev')) +if udev.found() + wlr_files += files('udev.c') + wlr_deps += [udev] + internal_features += { 'udev': true } +endif diff --git a/backend/session/session.c b/backend/session/session.c index 8a87f60a3..155dd770a 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -15,9 +15,13 @@ #include #include #include "backend/session/session.h" -#include "backend/session/udev.h" +#include "config.h" #include "util/time.h" +#if HAVE_UDEV +#include "backend/session/udev.h" +#endif + #define WAIT_GPU_TIMEOUT 10000 // ms struct wlr_device *session_find_device_by_devid(struct wlr_session *session, dev_t devid) { @@ -188,11 +192,16 @@ struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) { goto error_open; } +#if HAVE_UDEV session->device_manager = wlr_udev_device_manager_create(session); if (session->device_manager == NULL) { wlr_log(WLR_ERROR, "Failed to create udev device manager"); goto error_session; } +#else + wlr_log(WLR_ERROR, "Session requires udev"); + goto error_session; +#endif session->event_loop_destroy.notify = handle_event_loop_destroy; wl_event_loop_add_destroy_listener(event_loop, &session->event_loop_destroy); diff --git a/meson.build b/meson.build index 6d31bdc41..7755da0d4 100644 --- a/meson.build +++ b/meson.build @@ -82,6 +82,7 @@ internal_features = { 'xcb-errors': false, 'egl': false, 'libliftoff': false, + 'udev': false, } internal_config = configuration_data() diff --git a/meson.options b/meson.options index d8a8ca940..08cd3f2e8 100644 --- a/meson.options +++ b/meson.options @@ -7,6 +7,7 @@ option('backends', type: 'array', choices: ['auto', 'drm', 'libinput', 'x11'], v option('allocators', type: 'array', choices: ['auto', 'gbm', 'udmabuf'], value: ['auto'], description: 'Select built-in allocators') option('session', type: 'feature', value: 'auto', description: 'Enable session support') +option('udev', type: 'feature', value: 'auto', description: 'Use udev for device management') option('tests', type: 'boolean', value: true, description: 'Build tests and benchmarks') option('color-management', type: 'feature', value: 'auto', description: 'Enable support for color management') option('libliftoff', type: 'feature', value: 'auto', description: 'Enable support for libliftoff') From baf2e1852f7dc2eff3d26ab0ac18c9d2d2baa894 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Sun, 24 May 2026 16:18:35 +0200 Subject: [PATCH 3/3] backend/session: add linux device manager --- backend/session/linux.c | 291 ++++++++++++++++++++++++++++++++ backend/session/meson.build | 13 +- backend/session/session.c | 11 +- include/backend/session/linux.h | 15 ++ 4 files changed, 323 insertions(+), 7 deletions(-) create mode 100644 backend/session/linux.c create mode 100644 include/backend/session/linux.h diff --git a/backend/session/linux.c b/backend/session/linux.c new file mode 100644 index 000000000..962a77fea --- /dev/null +++ b/backend/session/linux.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "backend/session/linux.h" + +// TODO: make configurable +#define NETLINK_GROUPS 0x1 + +static ssize_t manager_find_drm_cards(struct wlr_device_manager *base, + size_t ret_cap, struct wlr_device **ret) { + struct wlr_linux_device_manager *manager = wl_container_of(base, manager, base); + struct wlr_session *session = manager->base.session; + + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, NULL, 0); + if (devices_len < 0) { + wlr_log(WLR_ERROR, "drmGetDevices2() failed"); + return -1; + } + drmDevice **devices = calloc(devices_len, sizeof(devices[0])); + if (devices == NULL) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return -1; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + wlr_log(WLR_ERROR, "drmGetDevices2() failed"); + return -1; + } + + // TODO: prioritize boot_display/boot_vga + ssize_t n = 0; + for (int i = 0; i < devices_len; i++) { + drmDevice *dev = devices[i]; + if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) { + continue; + } + + struct wlr_device *wlr_dev = session_open_if_kms(session, dev->nodes[DRM_NODE_PRIMARY]); + if (wlr_dev == NULL) { + continue; + } + + ret[n] = wlr_dev; + n++; + } + + drmFreeDevices(devices, devices_len); + free(devices); + return n; +} + +static void manager_destroy(struct wlr_device_manager *base) { + struct wlr_linux_device_manager *manager = wl_container_of(base, manager, base); + + wl_event_source_remove(manager->netlink_source); + close(manager->netlink_fd); + free(manager); +} + +static const struct wlr_device_manager_impl manager_impl = { + .destroy = manager_destroy, + .find_drm_cards = manager_find_drm_cards, +}; + +static bool parse_ul(unsigned long *out, const char *str) { + char *end = NULL; + errno = 0; + unsigned long v = strtoul(str, &end, 10); + if (errno != 0 || end == str || end[0] != '\0') { + return false; + } + *out = v; + return true; +} + +static bool parse_devid(dev_t *out, const char *major_str, const char *minor_str) { + unsigned long major, minor; + if (!parse_ul(&major, major_str) || !parse_ul(&minor, minor_str)) { + return false; + } + *out = makedev(major, minor); + return true; +} + +static int handle_netlink_event(int fd, uint32_t mask, void *data) { + struct wlr_linux_device_manager *manager = data; + struct wlr_session *session = manager->base.session; + + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + if (mask & WL_EVENT_ERROR) { + wlr_log(WLR_ERROR, "Failed to wait for netlink event"); + } else { + wlr_log(WLR_INFO, "Disconnected from netlink socket"); + } + return 0; + } + + char buf[8192]; + struct sockaddr_nl addr = {0}; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr hdr = { + .msg_name = &addr, + .msg_namelen = sizeof(addr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + ssize_t n = recvmsg(fd, &hdr, 0); + if (n <= 0) { + wlr_log(WLR_ERROR, "recvmsg() failed"); + return 0; + } + + if (hdr.msg_flags & MSG_TRUNC) { + wlr_log(WLR_ERROR, "Received truncated netlink message"); + return 0; + } + + if (addr.nl_groups == 0x0 || (addr.nl_groups == 0x1 && addr.nl_pid != 0)) { + wlr_log(WLR_ERROR, "Invalid netlink address"); + return 0; + } + + assert(buf[n - 1] == '\0'); + + size_t offset = 0; + const char *action = NULL, *subsystem = NULL, *devpath = NULL, *major = NULL, *minor = NULL, + *hotplug = NULL, *connector = NULL, *property = NULL, *lease = NULL; + while (offset < (size_t)n) { + char *line = &buf[offset]; + offset += strlen(line) + 1; + if (offset == 0) { // summary + wlr_log(WLR_DEBUG, "Received netlink event: %s", line); + continue; + } + + char *eq = strchr(line, '='); + if (eq == NULL) { + wlr_log(WLR_ERROR, "Malformed netlink event"); + return 0; + } + eq[0] = '\0'; + const char *key = line, *value = &eq[1]; + + if (strcmp(key, "ACTION") == 0) { + action = value; + } else if (strcmp(key, "SUBSYSTEM") == 0) { + subsystem = value; + } else if (strcmp(key, "DEVPATH") == 0) { + subsystem = value; + } else if (strcmp(key, "MAJOR") == 0) { + major = value; + } else if (strcmp(key, "MINOR") == 0) { + minor = value; + } else if (strcmp(key, "HOTPLUG") == 0) { + hotplug = value; + } else if (strcmp(key, "CONNECTOR") == 0) { + connector = value; + } else if (strcmp(key, "PROPERTY") == 0) { + property = value; + } else if (strcmp(key, "LEASE") == 0) { + lease = value; + } + } + if (subsystem == NULL || strcmp(subsystem, "drm") != 0) { + return 0; + } + if (action == NULL || devpath == NULL || major == NULL || minor == NULL) { + wlr_log(WLR_ERROR, "Missing required properties in netlink event"); + return 0; + } + + // TODO: filter card devices + + dev_t devid; + if (!parse_devid(&devid, major, minor)) { + return 0; + } + + char path[1024]; + snprintf(path, sizeof(path), "/dev/char/%s:%s", major, minor); + + struct wlr_device *dev = session_find_device_by_devid(session, devid); + if (strcmp(action, "add") == 0) { + if (dev != NULL) { + wlr_log(WLR_DEBUG, "Skipping duplicate device %s", devpath); + return 0; + } + + wlr_log(WLR_DEBUG, "DRM device %s added", devpath); + struct wlr_session_add_event event = { + .path = path, + }; + wl_signal_emit_mutable(&session->events.add_drm_card, &event); + } else if (strcmp(action, "change") == 0 && dev != NULL) { + wlr_log(WLR_DEBUG, "DRM device %s changed", devpath); + struct wlr_device_change_event event = {0}; + if (hotplug != NULL && strcmp(hotplug, "1") == 0) { + event.type = WLR_DEVICE_HOTPLUG; + if (connector != NULL) { + unsigned long id; + if (!parse_ul(&id, connector)) { + wlr_log(WLR_ERROR, "Invalid CONNECTOR attribute"); + return 0; + } + event.hotplug.connector_id = id; + } + if (property != NULL) { + unsigned long id; + if (!parse_ul(&id, property)) { + wlr_log(WLR_ERROR, "Invalid PROPERTY attribute"); + return 0; + } + event.hotplug.prop_id = id; + } + } else if (lease != NULL && strcmp(lease, "1") == 0) { + event.type = WLR_DEVICE_LEASE; + } else { + return 0; + } + wl_signal_emit_mutable(&dev->events.change, &event); + } else if (strcmp(action, "remove") == 0 && dev != NULL) { + wlr_log(WLR_DEBUG, "DRM device %s removed", devpath); + wl_signal_emit_mutable(&dev->events.remove, NULL); + } + + return 1; +} + +struct wlr_device_manager *wlr_linux_device_manager_create(struct wlr_session *session) { + struct wlr_linux_device_manager *manager = calloc(1, sizeof(*manager)); + if (manager == NULL) { + return NULL; + } + wlr_device_manager_init(&manager->base, &manager_impl, session); + + int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (fd < 0) { + wlr_log_errno(WLR_ERROR, "socket() failed"); + goto error_manager; + } + + int fd_flags = fcntl(fd, F_GETFL, 0); + if (fd_flags == -1) { + wlr_log_errno(WLR_ERROR, "Failed to get FD flags"); + goto error_fd; + } + if (fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK | O_CLOEXEC) == -1) { + wlr_log_errno(WLR_ERROR, "Failed to set FD flags"); + goto error_fd; + } + + struct sockaddr_nl addr = { .nl_family = AF_NETLINK, .nl_groups = NETLINK_GROUPS }; + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) { + wlr_log_errno(WLR_ERROR, "bind() failed"); + goto error_fd; + } + + // TODO: SO_ATTACH_FILTER for "drm" subsystem matching + // https://github.com/systemd/systemd/blob/788ef4c43eb2e74a67b90c3059bf407e7e301c81/src/libsystemd/sd-device/device-monitor.c#L807 + + manager->netlink_fd = fd; + manager->netlink_source = wl_event_loop_add_fd(session->event_loop, fd, + WL_EVENT_READABLE, handle_netlink_event, manager); + if (manager->netlink_source == NULL) { + wlr_log(WLR_ERROR, "wl_event_loop_add_fd() failed"); + goto error_fd; + } + + return &manager->base; + +error_fd: + close(fd); +error_manager: + free(manager); + return NULL; +} diff --git a/backend/session/meson.build b/backend/session/meson.build index becc08232..bd390b423 100644 --- a/backend/session/meson.build +++ b/backend/session/meson.build @@ -6,7 +6,13 @@ libseat = dependency( required: session_required, not_found_message: 'Required for session support.', ) -if not libseat.found() +udev = dependency('libudev', required: get_option('udev')) +has_netlink = cc.has_header('linux/netlink.h') +has_device_manager = udev.found() or has_netlink +if session_required and not has_device_manager + error('Session requires either udev or ') +endif +if not (libseat.found() and has_device_manager) subdir_done() endif @@ -14,9 +20,12 @@ wlr_files += files('session.c') wlr_deps += [libseat] features += { 'session': true } -udev = dependency('libudev', required: get_option('udev')) if udev.found() wlr_files += files('udev.c') wlr_deps += [udev] internal_features += { 'udev': true } endif + +if has_netlink + wlr_files += files('linux.c') +endif diff --git a/backend/session/session.c b/backend/session/session.c index 155dd770a..8c8e9eaca 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -20,6 +20,8 @@ #if HAVE_UDEV #include "backend/session/udev.h" +#else +#include "backend/session/linux.h" #endif #define WAIT_GPU_TIMEOUT 10000 // ms @@ -194,14 +196,13 @@ struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) { #if HAVE_UDEV session->device_manager = wlr_udev_device_manager_create(session); +#else + session->device_manager = wlr_linux_device_manager_create(session); +#endif if (session->device_manager == NULL) { - wlr_log(WLR_ERROR, "Failed to create udev device manager"); + wlr_log(WLR_ERROR, "Failed to create device manager"); goto error_session; } -#else - wlr_log(WLR_ERROR, "Session requires udev"); - goto error_session; -#endif session->event_loop_destroy.notify = handle_event_loop_destroy; wl_event_loop_add_destroy_listener(event_loop, &session->event_loop_destroy); diff --git a/include/backend/session/linux.h b/include/backend/session/linux.h new file mode 100644 index 000000000..d4bc57cc9 --- /dev/null +++ b/include/backend/session/linux.h @@ -0,0 +1,15 @@ +#ifndef BACKEND_SESSION_LINUX_H +#define BACKEND_SESSION_LINUX_H + +#include "backend/session/session.h" + +struct wlr_linux_device_manager { + struct wlr_device_manager base; + + int netlink_fd; + struct wl_event_source *netlink_source; +}; + +struct wlr_device_manager *wlr_linux_device_manager_create(struct wlr_session *session); + +#endif