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.
This commit is contained in:
Chris P 2024-04-03 05:28:04 -04:00
parent f0ce906b73
commit 2aa8cf10d6
2 changed files with 78 additions and 27 deletions

View file

@ -16,6 +16,7 @@
#include <xf86drmMode.h>
#include "backend/session/session.h"
#include "util/time.h"
#include "util/env.h"
#include <libseat.h>
@ -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;

View file

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