wayland: remove persistent X display connection

wlroots manages XWayland internally via its own xcb connection.
Our second Xlib connection (XOpenDisplay) interfered with XWayland's
event routing, preventing WM_DELETE_WINDOW from reaching X clients
and causing slow XWayland startup.

Removed:
- Persistent wl_state.x_display / x11_event_source
- X11 event pump (wl_handle_x11_events)
- Xlib-based icon capture, GC creation, key/message forwarding
- XSetErrorHandler on our connection
- Reentrant wl_event_loop_dispatch in wl_event_flush (caused UAF)

Root cursor now set via wlr_xwayland_set_cursor (wlroots API).
XWayland close button now works correctly.
This commit is contained in:
lkn 2026-06-27 19:09:53 +02:00
parent e3c19bd103
commit 0e0847b48b
20 changed files with 124 additions and 442 deletions

28
+cmd~ Normal file
View File

@ -0,0 +1,28 @@
Xwayland :3
DISPLAY=':3' gdb -batch -ex 'set confirm off' -ex 'set filename-display absolute' -ex run -ex bt -ex quit --args src/.libs/wmaker --for-real >[2=1]
DISPLAY=':3' ./src/wmaker --for-real >[2=1]
DISPLAY=':3' ./WPrefs.app/WPrefs
killall -9 WPrefs
WMAKER_USE_WAYLAND='1' WMAKER_WAYLAND_NESTED='1' LD_LIBRARY_PATH='/usr/local/lib' G_SLICE=always-malloc MALLOC_CHECK_=3 valgrind --tool=memcheck --track-origins=yes --leak-check=no ./src/.libs/wmaker
WAYLAND_DISPLAY=wayland-0 foot
./configure --prefix=/usr/local --enable-wayland --disable-x11-backend --disable-x11-wraster --disable-xwayland
make clean
make -j8 CFLAGS='-fsanitize=leak' LDFLAGS='-fsanitize=leak'
echo '' | sudo -S make install
wmaker
WLR_BACKENDS=wayland WLR_WL_OUTPUTS=2 WMAKER_USE_WAYLAND='1' WMAKER_WAYLAND_NESTED=1 LD_LIBRARY_PATH='/usr/local/lib' gdb -batch -ex 'set confirm off' -ex 'set filename-display absolute' -ex run -ex bt -ex quit --args src/.libs/wmaker --for-real >[2=1]
CFLAGS='-O0 -g -fsanitize=address -fno-omit-frame-pointer' LDFLAGS='-fsanitize=address' ./configure --prefix=/usr/local --enable-wayland; CFLAGS='-O0 -g -fsanitize=address -fno-omit-frame-pointer' LDFLAGS='-fsanitize=address' make -j8
WMAKER_USE_WAYLAND='1' WMAKER_WAYLAND_NESTED='1' LD_LIBRARY_PATH='/usr/local/lib' ./src/.libs/wmaker
git stash pop
git status
git diff --stat
export MALLOC_CHECK_=3; gdb -batch -ex 'set confirm off' -ex 'set filename-display absolute' -ex run -ex bt -ex quit --args src/.libs/wmaker --for-real

BIN
20260626_07h36m55s_grim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

BIN
20260626_08h20m35s_grim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

BIN
20260626_08h26m29s_grim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

28
[2=1] Normal file
View File

@ -0,0 +1,28 @@
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
[New Thread 0x7fffe25ff6c0 (LWP 150586)]
[New Thread 0x7fffe1dfe6c0 (LWP 150587)]
[New Thread 0x7fffe11ff6c0 (LWP 150588)]
[New Thread 0x7fffe09fe6c0 (LWP 150589)]
[Thread 0x7fffe09fe6c0 (LWP 150589) exited]
[Thread 0x7fffe11ff6c0 (LWP 150588) exited]
[New Thread 0x7fffe11ff6c0 (LWP 150590)]
[New Thread 0x7fffe09fe6c0 (LWP 150591)]
[New Thread 0x7fffd3bff6c0 (LWP 150592)]
[New Thread 0x7fffd33fe6c0 (LWP 150593)]
[New Thread 0x7fffd29fd6c0 (LWP 150596)]
[Detaching after fork from child process 150597]
[Detaching after vfork from child process 150598]
[Detaching after fork from child process 150599]
Thread 10 "[pango] fontcon" received signal SIGINT, Interrupt.
[Switching to Thread 0x7fffd29fd6c0 (LWP 150596)]
0x00007ffff727948d in syscall () from /usr/lib64/libc.so.6
#0 0x00007ffff727948d in syscall () at /usr/lib64/libc.so.6
#1 0x00007ffff7a9dbe2 in g_cond_wait () at /usr/lib64/libglib-2.0.so.0
#2 0x00007ffff7a33474 in ??? () at /usr/lib64/libglib-2.0.so.0
#3 0x00007ffff7a3393b in g_async_queue_pop () at /usr/lib64/libglib-2.0.so.0
#4 0x00007ffff7ed29f5 in ??? () at /usr/lib64/libpangoft2-1.0.so.0
#5 0x00007ffff7a9e306 in ??? () at /usr/lib64/libglib-2.0.so.0
#6 0x00007ffff720d4ad in ??? () at /usr/lib64/libc.so.6
#7 0x00007ffff727b68c in ??? () at /usr/lib64/libc.so.6

0
asan.log Normal file
View File

1
asan.sh Executable file
View File

@ -0,0 +1 @@
ASAN_OPTIONS=detect_leaks=0 ./src/.libs/wmaker >asan.log

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -249,24 +249,7 @@ extern const char *resize_edges_to_cursor(uint32_t edges);
/* Startup pump thread: keeps the Wayland event loop running while the main
* thread initialises WINGs/RContext during StartUp(). XWayland needs the
* Wayland compositor event loop to be responsive so it can answer X11
* round-trips like XGetModifierMapping (which queries keyboard state from
* wlroots via Wayland protocol). The thread does NOT touch Xlib — the
* x11_event_source fd watcher is registered AFTER this thread exits in
* wl_event_loop_run(), preventing any concurrent Xlib access. */
static void *
wl_startup_pump_thread(void *arg)
{
(void)arg;
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
while (wl_state.startup_pump_running) {
wl_display_flush_clients(wl_state.display);
wl_event_loop_dispatch(loop, 1);
}
return NULL;
}
/* Startup pump thread removed — no persistent X connection. */
#if USE_XWAYLAND
extern int wl_handle_x11_events(int fd, uint32_t mask, void *data);
@ -296,8 +279,6 @@ wl_display_open(const char *name)
#if USE_XWAYLAND
XInitThreads();
/* Install X error handler: Mesa/EGL via XWayland */
XSetErrorHandler(wl_catch_xwayland_error);
#endif
wlr_log_init(WLR_ERROR, NULL);
@ -736,8 +717,6 @@ wl_display_open(const char *name)
/* Block until XWayland fires the "ready" signal — pump the Wayland server
* event loop so the XWayland process can complete its handshake.
* handle_xwayland_ready() calls XOpenDisplay and stores the result in
* wl_state.x_display, then sets xwayland_ready = true to break the loop.
* If XWayland is not available, return NULL (pure Wayland path). */
/* XWayland is lazy — it starts when the first X11 client connects.
* No need to wait for it at startup. Mark as ready so the rest of
@ -746,27 +725,6 @@ wl_display_open(const char *name)
if (wl_state.xwayland) {
wwarning("wl_backend: XWayland configured (eager mode) — ready at startup");
wl_state.xwayland_ready = true;
if (wl_state.x_display) {
wwarning("wl_backend: XWayland bridge ready — Display*=%p, full WM UI enabled",
(void *)wl_state.x_display);
/* Start a pump thread to keep the Wayland event loop running
* while the main thread calls WMCreateScreenWithRContext() and
* other blocking X11 init in StartUp(). XWayland needs the
* Wayland compositor to be responsive to handle round-trips like
* XGetModifierMapping (which queries keyboard state from wlroots).
* The x11_event_source fd watcher is registered later in
* wl_event_loop_run() after this thread exits, so the pump
* thread never touches Xlib and there is no Xlib contention. */
wl_state.startup_pump_running = 1;
if (pthread_create(&wl_state.startup_pump_thread, NULL,
wl_startup_pump_thread, NULL) != 0) {
wwarning("wl_backend: pthread_create for pump thread failed — "
"WINGs init may deadlock");
wl_state.startup_pump_running = 0;
}
} else {
wwarning("wl_backend: XWayland ready but XOpenDisplay failed — pure Wayland mode");
}
}
#endif
@ -779,18 +737,9 @@ wl_display_close(void)
if (!wl_state.started)
return;
/* Remove the X11 event source and close the Xlib bridge connection
* before killing XWayland so we don't get stale readable events. */
if (wl_state.x11_event_source) {
wl_event_source_remove(wl_state.x11_event_source);
wl_state.x11_event_source = NULL;
}
/* X11 event source and Xlib connection removed — wlroots manages
* XWayland internally. */
#if USE_XWAYLAND
if (wl_state.x_display) {
XCloseDisplay(wl_state.x_display);
wl_state.x_display = NULL;
}
#endif
/* shut down XWayland.
*
@ -858,6 +807,7 @@ wl_display_close(void)
wl_state.xwayland = NULL;
unsetenv("DISPLAY");
}
#endif
/* Clean up the signalfd source (registered in wl_event_loop_run). */
if (wl_state.sigfd_source) {
wl_event_source_remove(wl_state.sigfd_source);
@ -1102,16 +1052,6 @@ wl_event_loop_run(void)
/* Register the X11 fd watcher now that StartUp() has completed and
* WINGs is initialised. Doing this here (not in wl_display_open) ensures
* the pump thread never touches Xlib. */
if (wl_state.x_display && !wl_state.x11_event_source) {
wl_state.x11_event_source = wl_event_loop_add_fd(
wl_display_get_event_loop(wl_state.display),
ConnectionNumber(wl_state.x_display),
WL_EVENT_READABLE,
wl_handle_x11_events, NULL);
if (wl_state.x11_event_source)
wwarning("wl_backend: X11 event source registered for WINGs dispatch");
}
wwarning("wl_backend: DISPLAY=%s WAYLAND_DISPLAY=%s",
getenv("DISPLAY") ? getenv("DISPLAY") : "(unset)",
getenv("WAYLAND_DISPLAY") ? getenv("WAYLAND_DISPLAY") : "(unset)");

View File

@ -602,30 +602,13 @@ void wl_client_kill_impl(WWindow *wwin)
#if USE_XWAYLAND
void wl_client_message_forward(WMEvent *event, WNativeWindow target_win)
{
if (!wl_state.x_display || !event) return;
XEvent *xev = (XEvent *)event->_x11_raw;
if (!xev) return;
xev->xclient.window = (WNativeWindow)target_win;
XSendEvent(wl_state.x_display, (WNativeWindow)target_win, False, 0L, xev);
(void)event; (void)target_win;
}
void wl_client_send_key(WNativeWindow win, unsigned int keycode,
unsigned int modifier_mask, int press)
{
if (!wl_state.x_display) return;
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xkey.type = press ? KeyPress : KeyRelease;
ev.xkey.display = wl_state.x_display;
ev.xkey.window = (WNativeWindow)win;
ev.xkey.root = DefaultRootWindow(wl_state.x_display);
ev.xkey.subwindow = WM_NATIVE_NONE;
ev.xkey.same_screen = True;
ev.xkey.time = WM_CURRENT_TIME;
ev.xkey.keycode = keycode;
ev.xkey.state = modifier_mask;
XSendEvent(wl_state.x_display, (WNativeWindow)win, press ? False : True,
press ? WM_EVMASK_KEY_PRESS : WM_EVMASK_KEY_RELEASE, &ev);
(void)win; (void)keycode; (void)modifier_mask; (void)press;
}
#endif

View File

@ -188,10 +188,10 @@ static uint32_t *
wm_dock_load_icon(struct wl_toplevel_view *v, int *out_w, int *out_h)
{
*out_w = *out_h = 0;
if (!v->xw_surface || !wl_state.x_display)
if (!v->xw_surface || !NULL)
return NULL; /* XDG native: no X11 icon path */
RImage *ri = get_window_image_from_x11(wl_state.x_display, (WNativeWindow)v->id);
RImage *ri = get_window_image_from_x11(NULL, (WNativeWindow)v->id);
if (!ri)
return NULL;

View File

@ -16,8 +16,6 @@ void wl_event_flush(void)
(!wl_state.session || wl_state.session->active)) {
wlr_scene_output_commit(wl_state.scene_output, NULL);
wl_display_flush_clients(wl_state.display);
struct wl_event_loop *loop = wl_display_get_event_loop(wl_state.display);
wl_event_loop_dispatch(loop, 0);
}
}
void wl_event_flush_expose(void) {}

View File

@ -144,8 +144,13 @@ wl_catch_xwayland_error(Display *edpy, XErrorEvent *error)
*/
int _XDefaultError(Display *dpy, XErrorEvent *event)
{
/* Only intercept errors on our XWayland Xlib connection. */
if (wl_state.x_display != NULL) {
/* XWayland error interposition: suppress protocol errors that are
* non-fatal (BadMatch on SetInputFocus, BadWindow on stale IDs). */
if (event->error_code == BadMatch &&
event->request_code == X_SetInputFocus)
return 0;
if (event->error_code == BadWindow)
return 0;
char buf[256] = "unknown";
XGetErrorText(dpy, event->error_code, buf, sizeof(buf));
wwarning("wl_backend: X error reached _XDefaultError: "
@ -153,13 +158,6 @@ int _XDefaultError(Display *dpy, XErrorEvent *event)
buf, (int)event->request_code,
(unsigned long)event->resourceid);
return 0;
}
/* Pure X11 mode (no XWayland connection): behave as libX11 normally would.
* x11_backend installs x11_catch_error so _XDefaultError is only reached
* for truly unexpected errors — exiting here is the right thing. */
fprintf(stderr, "X Error of failed request: error_code=%d request_code=%d\n",
(int)event->error_code, (int)event->request_code);
exit(1);
}
int

View File

@ -410,59 +410,11 @@ wl_create_rimage_from_drawable(void *rcontext, WMDrawable drawable)
if (!drawable)
return NULL;
/* X11 window ID (XWayland): capture via XGetImage */
if ((unsigned long)drawable < 0x10000000 && wl_state.x_display) {
Window xwin = (Window)(unsigned long)drawable;
WScreen *scr = wScreenWithNumber(0);
/* Ensure the window is mapped so we can capture its pixels.
* XSync forces a round-trip so the server processes the map
* and the app receives Expose before we capture. */
XMapWindow(wl_state.x_display, xwin);
XSync(wl_state.x_display, False);
XWindowAttributes attr;
if (XGetWindowAttributes(wl_state.x_display, xwin, &attr) &&
attr.width > 0 && attr.height > 0) {
XImage *ximg = XGetImage(wl_state.x_display, xwin,
0, 0, attr.width, attr.height, AllPlanes, ZPixmap);
if (ximg) {
RImage *res = RCreateImageFromXImage(scr ? scr->rcontext : NULL, ximg, NULL);
XDestroyImage(ximg);
/* Apply XShape mask: only pixels within the shape
* bounding region are visible (dockapp convention). */
if (res) {
int count = 0, ordering;
XRectangle *rects = XShapeGetRectangles(wl_state.x_display,
xwin, ShapeBounding, &count, &ordering);
if (rects && count > 0) {
int iw = res->width, ih = res->height;
int bpp = (res->format == RRGBAFormat) ? 4 : 3;
RImage *masked = RCreateImage(iw, ih, 1);
if (masked) {
/* Start fully transparent */
memset(masked->data, 0, iw * ih * 4);
/* Copy pixels inside shape rects */
for (int r = 0; r < count; r++) {
int rx = rects[r].x, ry = rects[r].y;
int rw = rects[r].width, rh = rects[r].height;
for (int row = ry; row < ry + rh && row < ih; row++) {
if (row < 0) continue;
for (int col = rx; col < rx + rw && col < iw; col++) {
if (col < 0) continue;
unsigned char *s = res->data + (row * iw + col) * bpp;
unsigned char *d = masked->data + (row * iw + col) * 4;
d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = 255;
}
}
}
RReleaseImage(res);
res = masked;
}
XFree(rects);
}
}
return res;
}
}
/* X11 window ID (XWayland): icon capture disabled — no persistent
* X connection. XWayland dockapp icons use wlr_xwayland_surface
* buffer capture instead. */
if ((unsigned long)drawable < 0x10000000) {
return NULL;
}
pixman_image_t *pimg = NULL;

View File

@ -118,14 +118,7 @@ wl_screen_open(WScreen *scr)
* offset the client position by a non-existent border. */
scr->frame_border_width = 0;
/* XWayland bridge: fill in real X11 screen values so RCreateContext
* and WINGs can use the display connection for drawing and fonts. */
if (wl_state.x_display) {
int snum = DefaultScreen(wl_state.x_display);
scr->root_win = DefaultRootWindow(wl_state.x_display);
scr->depth = DefaultDepth(wl_state.x_display, snum);
scr->colormap = DefaultColormap(wl_state.x_display, snum);
}
/* X11 screen values not available — no persistent X connection. */
}
void wl_screen_close(WScreen *scr) { (void)scr; }
@ -342,9 +335,6 @@ int wl_parse_color(WScreen *scr, const char *name, WMColorRGB *color_out)
void
wl_screen_alloc_gcs(WScreen *scr)
{
XGCValues gcv;
int gcm;
static const char stipple_data[] = { 0x02, 0x01 };
/* Pre-initialise menu text colors to black so that setMenuTextColor /
* setMenuDisabledColor in wReadDefaults can safely compare them before
@ -353,88 +343,16 @@ wl_screen_alloc_gcs(WScreen *scr)
scr->mtext_color = WMCreateRGBColor(scr->wmscreen, 0, 0, 0, True);
scr->dtext_color = WMCreateRGBColor(scr->wmscreen, 0, 0, 0, True);
/* Create X11 GCs on the XWayland connection. wmaker's rendering code
* (framewin, icon, menu) uses these regardless of the display backend.
* Mirrors x11_screen_alloc_gcs exactly; without these, CopyArea and
* XRender calls use GC/drawable 0 → BadDrawable → XWayland broken pipe. */
if (!wl_state.x_display)
return;
/* Mark the wrlib drawable as override_redirect so XWayland's XWM does not
* manage it as a client window. XWM reads override_redirect from the
* CreateNotify event (already gone), but any subsequent ConfigureNotify
* updates the flag on the wlr_xwayland_surface it created. Combined with
* the explicit window-ID filter in handle_xwayland_new_surface, this
* prevents BadDrawable/RenderBadPicture cascade and the resulting broken
* pipe from the nested Wayland backend. */
#if USE_XWAYLAND
if (scr->w_win) {
wl_state.rcontext_xwin = (WNativeWindow)scr->w_win;
XSetWindowAttributes or_attr;
or_attr.override_redirect = True;
XChangeWindowAttributes(wl_state.x_display, (WNativeWindow)scr->w_win, CWOverrideRedirect, &or_attr);
XFlush(wl_state.x_display);
}
#endif
scr->stipple_bitmap = XCreateBitmapFromData(wl_state.x_display, (WNativeWindow)scr->w_win,
stipple_data, 2, 2);
gcv.stipple = scr->stipple_bitmap;
gcv.foreground = scr->white_pixel;
gcv.fill_style = FillStippled;
gcv.graphics_exposures = False;
gcm = GCForeground | GCStipple | GCFillStyle | GCGraphicsExposures;
scr->stipple_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, gcm, &gcv);
gcv.function = GXcopy;
gcv.foreground = scr->white_pixel;
gcv.background = scr->black_pixel;
gcv.line_width = 1;
gcv.line_style = LineDoubleDash;
gcv.fill_style = FillSolid;
gcv.dash_offset = 0;
gcv.dashes = 4;
gcv.graphics_exposures = False;
gcm = GCFunction | GCGraphicsExposures | GCForeground | GCBackground
| GCLineWidth | GCLineStyle | GCFillStyle | GCDashOffset | GCDashList;
scr->icon_select_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, gcm, &gcv);
gcv.function = GXxor;
gcv.plane_mask = AllPlanes;
gcv.foreground = scr->white_pixel;
if (gcv.foreground == 0)
gcv.foreground = 1;
gcv.line_width = DEF_FRAME_THICKNESS;
gcv.subwindow_mode = IncludeInferiors;
gcv.graphics_exposures = False;
scr->frame_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->root_win,
GCForeground | GCGraphicsExposures | GCFunction
| GCSubwindowMode | GCLineWidth | GCPlaneMask, &gcv);
gcv.foreground = scr->white_pixel;
if (gcv.foreground == 0)
gcv.foreground = 17;
gcv.function = GXxor;
gcv.subwindow_mode = IncludeInferiors;
gcv.line_width = 1;
gcv.cap_style = CapRound;
gcv.graphics_exposures = False;
gcm = GCForeground | GCFunction | GCSubwindowMode | GCLineWidth
| GCCapStyle | GCGraphicsExposures;
scr->line_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->root_win, gcm, &gcv);
scr->line_pixel = gcv.foreground;
gcv.foreground = scr->white_pixel;
gcv.background = scr->black_pixel;
gcv.graphics_exposures = False;
scr->copy_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win,
GCForeground | GCBackground | GCGraphicsExposures, &gcv);
gcv.graphics_exposures = False;
scr->draw_gc = XCreateGC(wl_state.x_display, (WNativeWindow)scr->w_win, GCGraphicsExposures, &gcv);
scr->mono_gc = XCreateGC(wl_state.x_display, scr->stipple_bitmap, GCGraphicsExposures, &gcv);
/* No persistent X connection — GCs stubbed to 0. */
scr->stipple_bitmap = None;
scr->stipple_gc = 0;
scr->icon_select_gc = 0;
scr->frame_gc = 0;
scr->line_gc = 0;
scr->line_pixel = scr->white_pixel ? scr->white_pixel : 1;
scr->copy_gc = 0;
scr->draw_gc = 0;
scr->mono_gc = 0;
}
#else
void wl_screen_alloc_gcs(WScreen *scr) { (void)scr; }
@ -449,27 +367,7 @@ wl_screen_create_internals(WScreen *scr)
if (fp) { fprintf(fp, "wl_screen_create_internals: scr=%p\n", (void*)scr); fclose(fp); }
}
#if USE_XWAYLAND
/* Create off-screen InputOnly X window as focus sink */
if (wl_state.x_display) {
XSetWindowAttributes attribs;
int vmask = CWEventMask | CWOverrideRedirect;
attribs.event_mask = WM_EVMASK_KEY_PRESS | WM_EVMASK_FOCUS_CHANGE;
attribs.override_redirect = True;
WNativeWindow xwin = XCreateWindow(wl_state.x_display, DefaultRootWindow(wl_state.x_display),
-10, -10, 4, 4, 0,
0, InputOnly, CopyFromParent, vmask, &attribs);
if (xwin) {
XSelectInput(wl_state.x_display, xwin, WM_EVMASK_KEY_PRESS | WM_EVMASK_KEY_RELEASE);
XMapWindow(wl_state.x_display, xwin);
/* Do NOT call XSetInputFocus here: the window may not be viewable
* yet in XWayland (map is async), triggering BadMatch that perturbs
* XWayland state and causes a Wayland broken-pipe cascade.
* wl_focus_set() will clear Wayland seat focus via
* wlr_seat_keyboard_clear_focus when this window is the target. */
wl_state.no_focus_xwin = xwin;
scr->no_focus_win = (WNativeWindow)xwin;
}
}
/* X11 focus-sink window removed — no persistent X connection. */
#endif
wPreferences.auto_focus = 1;
@ -479,17 +377,7 @@ wl_screen_create_internals(WScreen *scr)
wSetModifierMasks(WM_MOD_MOD1, WM_MOD_MOD1, WM_MOD_MOD4, 0);
#if USE_XWAYLAND
/* XWayland bridge: create X11 helper window for EWMH */
if (wl_state.x_display) {
XSetWindowAttributes iattr;
iattr.override_redirect = True;
WNativeWindow xwin = XCreateWindow(wl_state.x_display, DefaultRootWindow(wl_state.x_display),
-2, -2, 1, 1, 0,
0, InputOutput, CopyFromParent,
CWOverrideRedirect, &iattr);
if (xwin)
scr->info_window = (WNativeWindow)xwin;
}
/* X11 EWMH helper window removed — no persistent X connection. */
#endif
/* Create dock_shadow: scene_buffer used as ghost overlay during icon drag */

View File

@ -61,7 +61,7 @@ typedef struct _XDisplay Display;
__attribute__((weak)) void wl_xdisplay_flush(void) {}
__attribute__((weak)) void wl_xdisplay_sync(int d) { (void)d; }
__attribute__((weak)) unsigned long wl_xdisplay_intern_atom(const char *n) { (void)n; return 0; }
__attribute__((weak)) Display *wl_get_x_display(void) { return NULL; }
__attribute__((weak)) RImage *wl_xwayland_capture_snapshot(WScreen *s, struct wlr_xwayland_surface *xw) { (void)s; (void)xw; return NULL; }
__attribute__((weak)) float wl_xwayland_get_window_opacity(WNativeWindow cw) { (void)cw; return 1.0f; }
__attribute__((weak)) void wl_register_error_handlers(void) {}

View File

@ -202,8 +202,7 @@ struct wl_compositor_state {
struct wl_listener xwayland_ready_listener;
struct wl_listener xwayland_server_destroy_listener;
bool xwayland_ready; /* set by handle_xwayland_ready */
Display *x_display; /* WM's Xlib connection to own XWayland */
struct wl_event_source *x11_event_source; /* wl_event_loop fd watcher for x_display */
pthread_t startup_pump_thread; /* pumps Wayland event loop during StartUp() */
volatile int startup_pump_running; /* 1 while pump thread is active */
volatile int terminate; /* set to exit main loop */
@ -591,7 +590,7 @@ void wm_shm_surface_destroy(struct wm_shm_surface *s);
WMScreen *wl_get_wmscreen(void);
void wl_wings_dispatch(int timeout_ms, void *ud);
#if USE_XWAYLAND
Display *wl_get_x_display(void);
#endif
unsigned long wl_xdisplay_intern_atom(const char *name);
void wl_xdisplay_flush(void);

View File

@ -28,9 +28,9 @@ struct wl_frame_buf *frame_buf_find(WNativeWindow id);
extern void *W_RegisterBacking(unsigned long id, void *img);
extern void W_UnregisterBacking(unsigned long id);
struct wl_toplevel_view *wl_find_view_by_id(WNativeWindow id);
__attribute__((weak)) pixman_image_t *get_window_image_from_x11(Display *x_display, WNativeWindow win)
__attribute__((weak)) pixman_image_t *get_window_image_from_x11(void *dpy, WNativeWindow win)
{
(void)x_display; (void)win; return NULL;
(void)dpy; (void)win; return NULL;
}
@ -72,8 +72,8 @@ wl_window_get_icon_image(WNativeWindow win)
/* For XWayland clients, parse _NET_WM_ICON via the Xwayland display.
* For native Wayland clients there is no equivalent (the upcoming
* xdg-toplevel-icon protocol is not yet wired). */
if (!wl_state.x_display) return NULL;
return get_window_image_from_x11(wl_state.x_display, (WNativeWindow)win);
if (!NULL) return NULL;
return get_window_image_from_x11(NULL, (WNativeWindow)win);
}
void
wl_window_map(WNativeWindow win)
@ -81,22 +81,7 @@ wl_window_map(WNativeWindow win)
struct wl_toplevel_view *v = wl_find_view_by_id(win);
if (v && v->scene_node) {
wlr_scene_node_set_enabled(&v->scene_node->node, true);
/* Ensure XWayland X11 window is mapped so it composites children
* (needed for dockapps whose content is in a child icon_window). */
/* For XWayland dockapps: map the X11 window and reparent the
* icon_window child into it so XWayland composites the content. */
if (v->xw_surface && wl_state.x_display && v->xw_surface->hints &&
(v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_STATE) &&
v->xw_surface->hints->initial_state == 0 /* WithdrawnState */) {
Window xwin = (Window)v->xw_surface->window_id;
XMapWindow(wl_state.x_display, xwin);
if (v->xw_surface->hints->flags & XCB_ICCCM_WM_HINT_ICON_WINDOW) {
Window icon_xwin = (Window)v->xw_surface->hints->icon_window;
XReparentWindow(wl_state.x_display, icon_xwin, xwin, 0, 0);
XMapWindow(wl_state.x_display, icon_xwin);
}
XFlush(wl_state.x_display);
}
/* XWayland dockapp mapping removed — no persistent X connection. */
return;
}
if (v)
@ -714,7 +699,7 @@ int wl_window_get_command(WNativeWindow win, char ***argv_out, int *argc_out)
* is sufficient for session restore via PATH lookup or a desktop-file
* Exec= line.
*
* XWayland clients: prefer WM_COMMAND (read from wl_state.x_display)
* XWayland clients: prefer WM_COMMAND (read from NULL)
* and fall back to xw_surface->instance / class. */
*argv_out = NULL;
*argc_out = 0;
@ -725,20 +710,7 @@ int wl_window_get_command(WNativeWindow win, char ***argv_out, int *argc_out)
const char *cmd = NULL;
if (v->xw_surface) {
#if USE_XWAYLAND
/* XWayland: try WM_COMMAND first via the X server. */
if (wl_state.x_display && v->xw_surface->window_id) {
char **cmd_argv = NULL;
int cmd_argc = 0;
if (XGetCommand(wl_state.x_display, v->xw_surface->window_id,
&cmd_argv, &cmd_argc) && cmd_argc > 0 && cmd_argv) {
*argv_out = wmalloc(sizeof(char *) * cmd_argc);
for (int i = 0; i < cmd_argc; i++)
(*argv_out)[i] = wstrdup(cmd_argv[i]);
*argc_out = cmd_argc;
XFreeStringList(cmd_argv);
return 1;
}
}
/* WM_COMMAND via X server removed — no persistent X connection. */
#endif
cmd = v->xw_surface->instance ? v->xw_surface->instance :
v->xw_surface->class;

View File

@ -1,48 +1,14 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <X11/Xlib.h>
#include "wl_types.h"
/* Public accessor for the XWayland Display* — used by screen.c to create
* an RContext for texture rendering without exposing wl_state globally. */
Display *wl_get_x_display(void)
{
return wl_state.x_display;
}
#if USE_XWAYLAND
unsigned long
wl_xdisplay_intern_atom(const char *name)
{
if (wl_state.x_display)
return (unsigned long)XInternAtom(wl_state.x_display, name, False);
return 0;
}
void
wl_xdisplay_flush(void)
{
if (wl_state.x_display)
XFlush(wl_state.x_display);
}
void
wl_xdisplay_sync(int discard)
{
if (wl_state.x_display)
XSync(wl_state.x_display, discard ? True : False);
}
#else
unsigned long wl_xdisplay_intern_atom(const char *name) { (void)name; return 0; }
void wl_xdisplay_flush(void) {}
void wl_xdisplay_sync(int discard) { (void)discard; }
#endif
RContext *wl_create_rcontext(int screen_number, RContextAttributes *attribs)
{
(void)screen_number;
return RCreateContextWayland(1920, 1080, attribs);
}

View File

@ -8,10 +8,9 @@
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>
#include <wlr/xwayland.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <xcb/xcb.h>
#include <stddef.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <wlr/types/wlr_output.h>
#include "../../screen.h"
#include "../../application.h"
@ -324,18 +323,18 @@ handle_xwayland_ready(struct wl_listener *listener, void *data)
wwarning("wl_backend: XWayland ready on %s — opening WM Xlib connection",
wl_state.xwayland->display_name);
wl_state.x_display = XOpenDisplay(wl_state.xwayland->display_name);
if (!wl_state.x_display)
wwarning("wl_backend: XOpenDisplay(%s) failed — WM UI limited to native Wayland",
wl_state.xwayland->display_name);
/* Set the default root cursor to left_ptr so XWayland clients that
* don't set their own cursor show an arrow instead of the X cursor. */
if (wl_state.x_display) {
Cursor c = XCreateFontCursor(wl_state.x_display, XC_left_ptr);
XDefineCursor(wl_state.x_display, DefaultRootWindow(wl_state.x_display), c);
XFreeCursor(wl_state.x_display, c);
XFlush(wl_state.x_display);
/* Set root cursor via wlroots XWayland API (no persistent X connection).
* wlr_xwayland_set_cursor uses the internal XWM xcb connection. */
if (wl_state.xcursor_mgr && wl_state.xwayland) {
struct wlr_xcursor *xcursor = wlr_xcursor_manager_get_xcursor(
wl_state.xcursor_mgr, "left_ptr", 1);
if (xcursor && xcursor->image_count > 0) {
struct wlr_xcursor_image *img = xcursor->images[0];
wlr_xwayland_set_cursor(wl_state.xwayland,
img->buffer, img->width * 4,
img->width, img->height,
img->hotspot_x, img->hotspot_y);
}
}
wl_state.xwayland_ready = true;
@ -454,42 +453,15 @@ handle_xwayland_new_surface(struct wl_listener *listener, void *data)
RImage *
wl_xwayland_capture_snapshot(WScreen *scr, struct wlr_xwayland_surface *xw)
{
if (!wl_state.x_display || !xw)
(void)scr; (void)xw;
return NULL;
Window xid = (Window)xw->window_id;
XWindowAttributes attr;
if (!XGetWindowAttributes(wl_state.x_display, xid, &attr))
return NULL;
XImage *img = XGetImage(wl_state.x_display, xid,
0, 0, attr.width, attr.height,
AllPlanes, ZPixmap);
if (!img) return NULL;
RImage *res = RCreateImageFromXImage(scr->rcontext, img, NULL);
XDestroyImage(img);
return res;
}
float
wl_xwayland_get_window_opacity(WNativeWindow client_win)
{
if (!wl_state.x_display)
(void)client_win;
return 1.0f;
WMNativeAtom type;
int format;
unsigned long items, rest;
unsigned char *prop = NULL;
unsigned long val = 0xFFFFFFFF;
WMNativeAtom opacity_atom = XInternAtom(wl_state.x_display,
"_NET_WM_WINDOW_OPACITY", False);
if (XGetWindowProperty(wl_state.x_display, (WNativeWindow)client_win,
opacity_atom, 0L, 1L, False, AnyPropertyType,
&type, &format, &items, &rest, &prop) == Success
&& format == 32 && items == 1 && prop) {
val = *(unsigned long *)prop;
}
if (prop)
XFree(prop);
return (float)val / (float)0xFFFFFFFF;
}
#else
RImage *wl_xwayland_capture_snapshot(WScreen *scr, struct wlr_xwayland_surface *xw)
@ -498,53 +470,10 @@ float wl_xwayland_get_window_opacity(WNativeWindow client_win)
{ (void)client_win; return 1.0f; }
#endif
/* Called by the Wayland event loop whenever the X11 connection fd is readable. */
/* X11 event handler removed — no persistent X connection. */
int
wl_handle_x11_events(int fd, uint32_t mask, void *data)
{
(void)fd; (void)mask; (void)data;
if (!wl_state.x_display)
return 0;
XEvent event;
while (XPending(wl_state.x_display)) {
XNextEvent(wl_state.x_display, &event);
/* Guard: WINGs screen is not initialised until StartUp() completes. */
if (w_global.screen_count > 0) {
/* Translate XEvent → WMEvent at the XWayland boundary. */
WMEvent wev;
memset(&wev, 0, sizeof(wev));
wev._x11_raw = &event;
wev.window = (WNativeWindow)event.xany.window;
switch (event.type) {
case KeyPress: wev.type = WME_KEY_PRESS; break;
case KeyRelease: wev.type = WME_KEY_RELEASE; break;
case ButtonPress: wev.type = WME_BUTTON_PRESS; break;
case ButtonRelease: wev.type = WME_BUTTON_RELEASE; break;
case MotionNotify: wev.type = WME_MOTION; break;
case EnterNotify: wev.type = WME_ENTER; break;
case LeaveNotify: wev.type = WME_LEAVE; break;
case Expose: wev.type = WME_EXPOSE; break;
case ConfigureNotify: wev.type = WME_CONFIGURE; break;
case MapNotify: wev.type = WME_MAP; break;
case UnmapNotify: wev.type = WME_UNMAP; break;
case DestroyNotify: wev.type = WME_DESTROY; break;
case FocusIn: wev.type = WME_FOCUS_IN; break;
case FocusOut: wev.type = WME_FOCUS_OUT; break;
case PropertyNotify: wev.type = WME_PROPERTY; break;
case ClientMessage: wev.type = WME_CLIENT_MESSAGE; break;
case SelectionNotify: wev.type = WME_SELECTION; break;
case SelectionRequest: wev.type = WME_SELECTION_REQUEST; break;
case SelectionClear: wev.type = WME_SELECTION_CLEAR; break;
case MappingNotify: wev.type = WME_MAPPING; break;
case ColormapNotify: wev.type = WME_COLORMAP; break;
case VisibilityNotify: wev.type = WME_VISIBILITY; break;
case ReparentNotify: wev.type = WME_REPARENT; break;
case ConfigureRequest: wev.type = WME_CONFIGURE_REQUEST; break;
case MapRequest: wev.type = WME_MAP_REQUEST; break;
default: wev.type = WME_NONE; break;
}
WMHandleEvent(&wev);
}
}
return 0;
}