From 2aa8cf10d6f21bc739eac0dbebf3522901623247 Mon Sep 17 00:00:00 2001 From: Chris P Date: Wed, 3 Apr 2024 05:28:04 -0400 Subject: [PATCH 1/2] backend/session: Add rapid hotplug detection When applied, this patch adds the option to wait after a DRM device change uevent for another identical one in order to avoid monitor rearrangement due to a monitor disconnecting and reconnecting from the implementation of DisplayPort's deep sleep "feature". This is locked behind a new environment variable due to the 1 second delay that occurs to wait for a second event, but can be enable by setting WLR_RAPID_HOTPLUG_PREVENT to 1. --- backend/session/session.c | 102 ++++++++++++++++++++++-------- include/backend/session/session.h | 3 + 2 files changed, 78 insertions(+), 27 deletions(-) diff --git a/backend/session/session.c b/backend/session/session.c index 5fb20c226..261834e61 100644 --- a/backend/session/session.c +++ b/backend/session/session.c @@ -16,6 +16,7 @@ #include #include "backend/session/session.h" #include "util/time.h" +#include "util/env.h" #include @@ -166,31 +167,11 @@ static void read_udev_change_event(struct wlr_device_change_event *event, } } -static int handle_udev_event(int fd, uint32_t mask, void *data) { - struct wlr_session *session = data; +static int handle_udev_event(const char *sysname, const char *devnode, const char *action, + struct wlr_session *session, struct udev_device *udev_dev, void *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; - } - if (strcmp(action, "add") == 0) { wlr_log(WLR_DEBUG, "DRM device %s added", sysname); struct wlr_session_add_event event = { @@ -206,10 +187,19 @@ static int handle_udev_event(int fd, uint32_t mask, void *data) { } if (strcmp(action, "change") == 0) { - 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); + bool double_uevent_occured; + if (env_parse_bool("WLR_RAPID_HOTPLUG_PREVENT")) { + double_uevent_occured = check_double_uevent(sysname, devnode, action, data); + } else { + double_uevent_occured = false; + } + if (!double_uevent_occured) { + wlr_log(WLR_DEBUG, "Resuming change event for DRM device %s", sysname); + 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) { wlr_log(WLR_DEBUG, "DRM device %s removed", sysname); wl_signal_emit_mutable(&dev->events.remove, NULL); @@ -220,11 +210,69 @@ static int handle_udev_event(int fd, uint32_t mask, void *data) { } } + return 1; +} + +static int handle_udev_event_buffer(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); + + 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; + } + + handle_udev_event(sysname, devnode, action, session, udev_dev, data); + out: udev_device_unref(udev_dev); return 1; } +bool check_double_uevent(const char *stored_sysname, const char *stored_devnode, + const char *stored_action, void *data) { + bool double_uevent_occured = false; + + wlr_log(WLR_DEBUG, "Sleeping to wait for potential hot-plugs"); + sleep(1); + + struct wlr_session *session = data; + struct udev_device *udev_dev = udev_monitor_receive_device(session->mon); + + 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); + + if(sysname != NULL && devnode != NULL && action != NULL) { + if (strcmp(stored_sysname, sysname) == 0 && strcmp(stored_devnode, devnode) == 0 + && strcmp(stored_action, action) == 0) { + wlr_log(WLR_DEBUG, "DRM device %s double uevent ignored", sysname); + double_uevent_occured = true; + } else { + handle_udev_event(sysname, devnode, action, session, udev_dev, data); + } + } + + udev_device_unref(udev_dev); + + return double_uevent_occured; +} + static void handle_event_loop_destroy(struct wl_listener *listener, void *data) { struct wlr_session *session = wl_container_of(listener, session, event_loop_destroy); @@ -267,7 +315,7 @@ struct wlr_session *wlr_session_create(struct wl_event_loop *event_loop) { 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); + WL_EVENT_READABLE, handle_udev_event_buffer, session); if (!session->udev_event) { wlr_log_errno(WLR_ERROR, "Failed to create udev event source"); goto error_mon; diff --git a/include/backend/session/session.h b/include/backend/session/session.h index 0275f69fa..64115e9e1 100644 --- a/include/backend/session/session.h +++ b/include/backend/session/session.h @@ -12,6 +12,9 @@ int libseat_session_open_device(struct wlr_session *base, const char *path); void libseat_session_close_device(struct wlr_session *base, int fd); bool libseat_change_vt(struct wlr_session *base, unsigned vt); +bool check_double_uevent(const char *stored_sysname, const char *stored_devnode, + const char *stored_action, void *data); + void session_init(struct wlr_session *session); struct wlr_device *session_open_if_kms(struct wlr_session *restrict session, From 444f5eb4c8f54323c9f98db79844906ba8bc9cdc Mon Sep 17 00:00:00 2001 From: Chris P Date: Wed, 3 Apr 2024 05:34:36 -0400 Subject: [PATCH 2/2] doc: Add information about rapid hotplug detection This commit appends information about the WLR_RAPID_HOTPLUG_PREVENT enviroment variable from the previous commit to the documentation page on envrionment variables, namely what its purpose is and that the user should be aware of a delay in the event that a rapid hotplug occurs. --- docs/env_vars.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/env_vars.md b/docs/env_vars.md index e36cdc781..c371ebd64 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -14,6 +14,10 @@ wlroots reads these environment variables hardware-accelerated renderers. * *WLR_EGL_NO_MODIFIERS*: set to 1 to disable format modifiers in EGL, this can be used to understand and work around driver bugs. +* *WLR_RAPID_HOTPLUG_PREVENT*: set to 1 to wait after an output device hotplug + in case another occurs immediately after, and if so, ignore both. This is to + work around monitors that implement DisplayPort's "deep sleep" feature, but + causes a second delay for each change event due to waiting. ## DRM backend