From 8b38546049ea51c81dc7667f4a8abb4704ff8fc9 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 21 Aug 2026 05:43:11 +1000 Subject: [PATCH] [client] displayserver: support external event sources Allow display servers to dispatch a caller-owned event source on their native event thread. This lets background input readers marshal events to the thread that owns application and display-server state. Poll X11 fairly when native events remain continuously available, and use bounded polling if the external descriptor cannot be registered. --- client/displayservers/Wayland/wayland.c | 18 ++++++++ client/displayservers/Wayland/wayland.h | 15 +++--- client/displayservers/X11/x11.c | 59 ++++++++++++++++++------ client/displayservers/X11/x11.h | 9 ++-- client/include/interface/displayserver.h | 14 ++++++ 5 files changed, 89 insertions(+), 26 deletions(-) diff --git a/client/displayservers/Wayland/wayland.c b/client/displayservers/Wayland/wayland.c index b947feb4..43b337aa 100644 --- a/client/displayservers/Wayland/wayland.c +++ b/client/displayservers/Wayland/wayland.c @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -105,6 +106,14 @@ static bool getCompositor(char * dst, size_t size) return true; } +static void eventSourceCallback(uint32_t events, void * opaque) +{ + (void)events; + + const LG_DSEventSource * source = (const LG_DSEventSource *)opaque; + source->callback(source->opaque); +} + static bool waylandInit(const LG_DSInitParams params) { memset(&wlWm, 0, sizeof(wlWm)); @@ -121,6 +130,7 @@ static bool waylandInit(const LG_DSInitParams params) atomic_init(&wlWm.hdrScRGBWhiteLevel, 80); atomic_init(&wlWm.pendingResize, 0); wlWm.resizeEventFd = -1; + wlWm.eventSource = params.eventSource; wlWm.display = wl_display_connect(NULL); if (!wlWm.display) @@ -152,6 +162,14 @@ static bool waylandInit(const LG_DSInitParams params) if (!waylandPollInit()) return false; + if (wlWm.eventSource.fd >= 0 && wlWm.eventSource.callback && + !waylandPollRegister(wlWm.eventSource.fd, eventSourceCallback, + &wlWm.eventSource, EPOLLIN)) + { + DEBUG_ERROR("Failed to register the external event source"); + return false; + } + if (!waylandOutputInit()) return false; diff --git a/client/displayservers/Wayland/wayland.h b/client/displayservers/Wayland/wayland.h index ac1f3260..e7187163 100644 --- a/client/displayservers/Wayland/wayland.h +++ b/client/displayservers/Wayland/wayland.h @@ -276,13 +276,14 @@ struct WaylandDSState _Atomic(unsigned) frameEventFlags; LGEvent * frameEvent; - struct wl_list poll; // WaylandPoll::link - struct wl_list pollFree; // WaylandPoll::link - LG_Lock pollLock; - LG_Lock pollFreeLock; - unsigned int pollWaiters; - int epollFd; - int displayFd; + struct wl_list poll; // WaylandPoll::link + struct wl_list pollFree; // WaylandPoll::link + LG_Lock pollLock; + LG_Lock pollFreeLock; + unsigned int pollWaiters; + int epollFd; + int displayFd; + LG_DSEventSource eventSource; }; struct WCBTransfer diff --git a/client/displayservers/X11/x11.c b/client/displayservers/X11/x11.c index 2da62203..017d9f6d 100644 --- a/client/displayservers/X11/x11.c +++ b/client/displayservers/X11/x11.c @@ -443,6 +443,7 @@ static bool x11Init(const LG_DSInitParams params) x11InputInit(&x11.input, &inputSink, NULL); LG_LOCK_INIT(x11.pointerLock); atomic_init(&x11.captureActive, false); + x11.eventSource = params.eventSource; x11.xValuator = -1; x11.yValuator = -1; x11.numLockIndicator = -1; @@ -1063,6 +1064,12 @@ static bool x11GetProp(LG_DSProperty prop, void *ret) static int x11EventThread(void * unused) { + enum + { + EPOLL_SOURCE_X11, + EPOLL_SOURCE_EXTERNAL, + }; + int epollfd = epoll_create1(0); if (epollfd == -1) { @@ -1070,7 +1077,11 @@ static int x11EventThread(void * unused) return 0; } - struct epoll_event ev = { .events = EPOLLIN }; + struct epoll_event ev = + { + .events = EPOLLIN, + .data.u32 = EPOLL_SOURCE_X11, + }; const int fd = ConnectionNumber(x11.display); if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) { @@ -1079,6 +1090,18 @@ static int x11EventThread(void * unused) return 0; } + bool pollEventSource = false; + if (x11.eventSource.fd >= 0 && x11.eventSource.callback) + { + ev.data.u32 = EPOLL_SOURCE_EXTERNAL; + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, + x11.eventSource.fd, &ev) == -1) + { + DEBUG_WARN("Failed to register the external event source; polling"); + pollEventSource = true; + } + } + while(app_isRunning()) { const uint64_t lastWMEvent = atomic_load(&x11.lastWMEvent); @@ -1088,24 +1111,30 @@ static int x11EventThread(void * unused) app_invalidateWindow(true); } - if (!XPending(x11.display)) + const bool xPending = XPending(x11.display) != 0; + struct epoll_event events[2]; + const int nfds = epoll_wait(epollfd, events, ARRAY_LENGTH(events), + xPending ? 0 : pollEventSource ? 10 : 100); + if (nfds == -1) { - struct epoll_event events[1]; - int nfds = epoll_wait(epollfd, events, 1, 100); - if (nfds == -1) - { - if (errno == EINTR) - continue; - - close(epollfd); - DEBUG_ERROR("epoll_wait failure"); - return 0; - } - - if (nfds == 0 || !XPending(x11.display)) + if (errno == EINTR) continue; + + close(epollfd); + DEBUG_ERROR("epoll_wait failure"); + return 0; } + for (int i = 0; i < nfds; ++i) + if (events[i].data.u32 == EPOLL_SOURCE_EXTERNAL) + x11.eventSource.callback(x11.eventSource.opaque); + + if (pollEventSource) + x11.eventSource.callback(x11.eventSource.opaque); + + if (!XPending(x11.display)) + continue; + XEvent xe; XNextEvent(x11.display, &xe); diff --git a/client/displayservers/X11/x11.h b/client/displayservers/X11/x11.h index 269e450e..246ade7e 100644 --- a/client/displayservers/X11/x11.h +++ b/client/displayservers/X11/x11.h @@ -101,10 +101,11 @@ struct X11DSState int xValuator; int yValuator; - X11Input input; - LG_Lock pointerLock; - _Atomic(bool) captureActive; - bool fullscreen; + X11Input input; + LG_DSEventSource eventSource; + LG_Lock pointerLock; + _Atomic(bool) captureActive; + bool fullscreen; struct Rect rect; struct Border border; diff --git a/client/include/interface/displayserver.h b/client/include/interface/displayserver.h index 9be6d71b..7890ea5b 100644 --- a/client/include/interface/displayserver.h +++ b/client/include/interface/displayserver.h @@ -103,6 +103,18 @@ LG_DSWaitFrameResult; #define LG_POINTER_COUNT (LG_POINTER_NOT_ALLOWED + 1) +typedef void (*LG_DSEventCallback)(void * opaque); + +typedef struct LG_DSEventSource +{ + /* The callback must be nonblocking and tolerate spurious calls. The caller + * retains ownership of fd and opaque until display-server shutdown. */ + int fd; + LG_DSEventCallback callback; + void * opaque; +} +LG_DSEventSource; + typedef struct LG_DSInitParams { const char * title; @@ -122,6 +134,8 @@ typedef struct LG_DSInitParams // x11 needs to know if this is in use so we can decide to setup for // presentation times bool jitRender; + + LG_DSEventSource eventSource; } LG_DSInitParams;