diff --git a/src/backend/backend.h b/src/backend/backend.h index b61b29b..8da8ccb 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -72,6 +72,29 @@ typedef struct WMBackend { */ int (*display_screen_count)(void); + /* + * Return the default screen index for the display connection. + * X11: DefaultScreen(dpy). Wayland: 0. + * Called from StartUp() to pass to wScreenInit(). + */ + int (*display_default_screen)(void); + + /* + * Intern an atom by name. + * X11: XInternAtom(dpy, name, False) -- returns Atom (unsigned long). + * Wayland: returns 0 (atoms have no meaning in native Wayland). + * Called from StartUp() to populate w_global.atom.*. + */ + unsigned long (*atom_intern)(const char *name); + + /* + * Run the backend's main event loop until shutdown. + * X11: no-op; EventLoop() drives the WINGs/X11 event pump. + * Wayland: wl_display_run(display) -- blocks until wl_display_terminate(). + * Called from EventLoop() when dpy == NULL (Wayland path). + */ + void (*event_loop_run)(void); + /* ------------------------------------------------------------------ */ /* Screen lifecycle (Phase 2) */ /* ------------------------------------------------------------------ */ diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index 90b118a..eb6e4f3 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "../../WindowMaker.h" @@ -36,6 +37,7 @@ struct wl_compositor_state { struct wl_display *display; struct wlr_backend *backend; struct wlr_renderer *renderer; + struct wlr_output *output; /* headless output (screen 0) */ int started; /* non-zero after successful display_open */ }; @@ -87,6 +89,9 @@ wl_display_open(const char *name) return NULL; } + /* Add a single 1920×1080 headless output (screen 0). */ + wl_state.output = wlr_headless_add_output(wl_state.backend, 1920, 1080); + wl_state.started = 1; return NULL; /* no X11 Display* in the Wayland path */ } @@ -114,11 +119,43 @@ wl_display_screen_count(void) return wl_state.started ? 1 : 0; } +static int +wl_display_default_screen(void) +{ + return 0; +} + +static unsigned long +wl_atom_intern(const char *name) +{ + (void)name; + return 0; /* atoms have no meaning in native Wayland */ +} + +static void +wl_event_loop_run(void) +{ + if (wl_state.display) + wl_display_run(wl_state.display); +} + /* ------------------------------------------------------------------ */ /* Screen open/close */ /* ------------------------------------------------------------------ */ -static void wl_screen_open(WScreen *scr) { (void)scr; } +static void +wl_screen_open(WScreen *scr) +{ + /* Set geometry from the headless output created in display_open(). */ + scr->scr_width = wl_state.output ? + (int)wl_state.output->width : 1920; + scr->scr_height = wl_state.output ? + (int)wl_state.output->height : 1080; + scr->depth = 24; + scr->root_win = WNATIVE_NONE; + scr->colormap = 0; +} + static void wl_screen_close(WScreen *scr) { (void)scr; } /* ------------------------------------------------------------------ */ @@ -468,8 +505,10 @@ static void wl_fake_leader_destroy(WNativeWindow win) { (void)win; } /* Screen internals */ /* ------------------------------------------------------------------ */ -static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 0; } -static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 0; } +/* Wayland compositor is always the sole WM — succeed unconditionally. */ +static int wl_screen_replace_wm(WScreen *scr) { (void)scr; return 1; } +/* No X11 event mask selection needed; succeed. */ +static int wl_screen_select_input(WScreen *scr, long event_mask) { (void)scr; (void)event_mask; return 1; } static void wl_screen_set_root_cursor(WScreen *scr, int cursor_id) { (void)scr; (void)cursor_id; } static void wl_screen_set_icon_sizes(WScreen *scr, int min_size, int max_size) { (void)scr; (void)min_size; (void)max_size; } static void wl_screen_setup_noticeboard(WScreen *scr) { (void)scr; } @@ -530,9 +569,12 @@ static void wl_register_error_handlers(void) {} /* ------------------------------------------------------------------ */ const WMBackend wl_backend = { - .display_open = wl_display_open, - .display_close = wl_display_close, - .display_screen_count = wl_display_screen_count, + .display_open = wl_display_open, + .display_close = wl_display_close, + .display_screen_count = wl_display_screen_count, + .display_default_screen = wl_display_default_screen, + .atom_intern = wl_atom_intern, + .event_loop_run = wl_event_loop_run, .screen_open = wl_screen_open, .screen_close = wl_screen_close, diff --git a/src/backend/x11/x11_backend.c b/src/backend/x11/x11_backend.c index 870cf68..dd6c846 100644 --- a/src/backend/x11/x11_backend.c +++ b/src/backend/x11/x11_backend.c @@ -102,6 +102,24 @@ x11_display_screen_count(void) return dpy ? ScreenCount(dpy) : 0; } +static int +x11_display_default_screen(void) +{ + return dpy ? DefaultScreen(dpy) : 0; +} + +static unsigned long +x11_atom_intern(const char *name) +{ + return (unsigned long)XInternAtom(dpy, name, False); +} + +static void +x11_event_loop_run(void) +{ + /* no-op: EventLoop() in event.c drives the X11/WINGs event pump */ +} + /* -------------------------------------------------------------------- */ /* Screen lifecycle */ /* -------------------------------------------------------------------- */ @@ -2358,9 +2376,12 @@ x11_register_error_handlers(void) /* -------------------------------------------------------------------- */ const WMBackend x11_backend = { - .display_open = x11_display_open, - .display_close = x11_display_close, - .display_screen_count = x11_display_screen_count, + .display_open = x11_display_open, + .display_close = x11_display_close, + .display_screen_count = x11_display_screen_count, + .display_default_screen = x11_display_default_screen, + .atom_intern = x11_atom_intern, + .event_loop_run = x11_event_loop_run, .screen_open = x11_screen_open, .screen_close = x11_screen_close, .frame_create_toplevel = x11_frame_create_toplevel, diff --git a/src/event.c b/src/event.c index e297d2c..cca5044 100644 --- a/src/event.c +++ b/src/event.c @@ -366,7 +366,15 @@ noreturn void EventLoop(void) struct timeval time; fd_set rfds; int retVal = 0; +#endif + /* Wayland path: delegate to the backend's event loop (wl_display_run). */ + if (!dpy) { + wm_backend->event_loop_run(); + Exit(0); + } + +#ifdef HAVE_INOTIFY if (w_global.inotify.fd_event_queue < 0 || w_global.inotify.wd_defaults < 0) retVal = -1; #endif diff --git a/src/main.c b/src/main.c index d6fae38..559948f 100644 --- a/src/main.c +++ b/src/main.c @@ -198,6 +198,9 @@ static int initWVisualID(const char *user_str) noreturn void Exit(int status) { + if (wm_backend) + wm_backend->display_close(); + if (dpy) XCloseDisplay(dpy); diff --git a/src/screen.c b/src/screen.c index f0009c9..f62ed97 100644 --- a/src/screen.c +++ b/src/screen.c @@ -158,6 +158,10 @@ WScreen *wScreenInit(int screen_number) wm_backend->monitors_select_events(scr); wm_backend->screen_set_root_cursor(scr, WCUR_ROOT); + /* Wayland path: screen_open() sets geometry; X11/wrlib context not needed. */ + if (!dpy) + goto screen_done; + /* screen descriptor for raster graphic library */ rattr.flags = RC_RenderMode | RC_ColorsPerChannel | RC_StandardColormap; rattr.render_mode = wPreferences.no_dithering ? RBestMatchRendering : RDitheredRendering; @@ -265,6 +269,7 @@ WScreen *wScreenInit(int screen_number) wScreenUpdateUsableArea(scr); +screen_done: return scr; } diff --git a/src/shutdown.c b/src/shutdown.c index 05072f6..4847f5c 100644 --- a/src/shutdown.c +++ b/src/shutdown.c @@ -73,7 +73,8 @@ void Shutdown(WShutdownMode mode) if (scr->helper_pid) kill(scr->helper_pid, SIGKILL); - wScreenSaveState(scr); + if (dpy) /* Wayland: skip X11 session state save */ + wScreenSaveState(scr); if (mode == WSKillMode) wipeDesktop(scr); @@ -99,7 +100,8 @@ void Shutdown(WShutdownMode mode) if (scr) { if (scr->helper_pid) kill(scr->helper_pid, SIGKILL); - wScreenSaveState(scr); + if (dpy) /* Wayland: skip X11 session state save */ + wScreenSaveState(scr); RestoreDesktop(scr); } } @@ -157,6 +159,9 @@ static void restoreWindows(WMBag * bag, WMBagIterator iter) */ void RestoreDesktop(WScreen * scr) { + if (!dpy) + return; /* Wayland: no X11 clients to unmanage */ + if (scr->helper_pid > 0) { kill(scr->helper_pid, SIGTERM); scr->helper_pid = 0; @@ -192,6 +197,9 @@ static void wipeDesktop(WScreen * scr) { WWindow *wwin; + if (!dpy) + return; /* Wayland: no X11 clients to wipe */ + wwin = scr->focused_window; while (wwin) { if (wwin->protocols.DELETE_WINDOW) diff --git a/src/startup.c b/src/startup.c index 753d327..1305934 100644 --- a/src/startup.c +++ b/src/startup.c @@ -177,6 +177,9 @@ static void getOffendingModifiers(void) Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask }; + if (!dpy) + return; /* Wayland: no modifier mapping to query */ + nlock = wm_backend->keysym_to_keycode(WM_KEY_NUM_LOCK); slock = wm_backend->keysym_to_keycode(WM_KEY_SCROLL_LOCK); @@ -376,18 +379,11 @@ void StartUp(Bool defaultScreenOnly) w_global.context.app_win = XUniqueContext(); w_global.context.stack = XUniqueContext(); - /* _XA_VERSION = XInternAtom(dpy, "VERSION", False); */ - -#ifdef HAVE_XINTERNATOMS - XInternAtoms(dpy, atomNames, wlengthof(atomNames), False, atom); -#else - { - int i; - for (i = 0; i < wlengthof(atomNames); i++) - atom[i] = XInternAtom(dpy, atomNames[i], False); + int ai; + for (ai = 0; ai < (int)wlengthof(atomNames); ai++) + atom[ai] = (Atom)wm_backend->atom_intern(atomNames[ai]); } -#endif w_global.atom.wm.state = atom[0]; w_global.atom.wm.change_state = atom[1]; @@ -478,8 +474,9 @@ void StartUp(Bool defaultScreenOnly) /* handle X shutdowns and errors via backend */ wm_backend->register_error_handlers(); - /* set hook for out event dispatcher in WINGs event dispatcher */ - WMHookEventHandler(DispatchEvent); + /* set hook for our event dispatcher in WINGs event dispatcher (X11 only) */ + if (dpy) + WMHookEventHandler(DispatchEvent); /* initialize defaults stuff */ w_global.domain.wmaker = wDefaultsInitDomain("WindowMaker", True); @@ -535,7 +532,7 @@ void StartUp(Bool defaultScreenOnly) /* manage the screens */ for (j = 0; j < max; j++) { if (defaultScreenOnly || max == 1) { - wScreen[w_global.screen_count] = wScreenInit(DefaultScreen(dpy)); + wScreen[w_global.screen_count] = wScreenInit(wm_backend->display_default_screen()); if (!wScreen[w_global.screen_count]) { wfatal(_("it seems that there is already a window manager running")); Exit(1); @@ -550,9 +547,11 @@ void StartUp(Bool defaultScreenOnly) w_global.screen_count++; } - InitializeSwitchMenu(); + if (dpy) + InitializeSwitchMenu(); - /* initialize/restore state for the screens */ + /* initialize/restore state for the screens (X11 only for now) */ + if (dpy) { for (j = 0; j < w_global.screen_count; j++) { int lastDesktop; @@ -605,6 +604,7 @@ void StartUp(Bool defaultScreenOnly) else wSessionRestoreLastWorkspace(wScreen[j]); } + } /* end if (dpy) */ if (w_global.screen_count == 0) { wfatal(_("could not manage any screen")); @@ -612,8 +612,8 @@ void StartUp(Bool defaultScreenOnly) } #ifndef HAVE_INOTIFY - /* setup defaults file polling */ - if (!wPreferences.flags.noupdates) + /* setup defaults file polling (X11/WINGs timer only) */ + if (dpy && !wPreferences.flags.noupdates) WMAddTimerHandler(3000, wDefaultsCheckDomains, NULL); #endif @@ -647,7 +647,10 @@ static void manageAllWindows(WScreen * scr, int crashRecovery) unsigned int i, j; WWindow *wwin; - XGrabServer(dpy); + if (!dpy) + return; /* Wayland: no pre-existing X11 clients to adopt */ + + wm_backend->server_grab(); XQueryTree(dpy, scr->root_win, &root, &parent, &children, &nchildren); scr->flags.startup = 1; @@ -709,7 +712,7 @@ static void manageAllWindows(WScreen * scr, int crashRecovery) } } } - XUngrabServer(dpy); + wm_backend->server_ungrab(); /* hide apps */ wwin = scr->focused_window; diff --git a/src/xdnd.c b/src/xdnd.c index 9bc0a14..75df81b 100644 --- a/src/xdnd.c +++ b/src/xdnd.c @@ -49,6 +49,9 @@ static Atom selected_typelist; void wXDNDInitializeAtoms(void) { + if (!dpy) + return; /* Wayland: XDND is X11-only */ + _XA_XdndAware = XInternAtom(dpy, "XdndAware", False); _XA_XdndEnter = XInternAtom(dpy, "XdndEnter", False); _XA_XdndLeave = XInternAtom(dpy, "XdndLeave", False); diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 1fb0fee..203aa60 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -7,11 +7,10 @@ # Currently verifies: # 1. wmaker was built with Wayland backend support (symbol present) # 2. wmaker links against wlroots +# 3. wmaker starts with WMAKER_USE_WAYLAND=1 and reaches the event loop +# without crashing (verified by sending SIGTERM and checking exit status) # # This test grows as wl_backend vtable slots are implemented. -# When the compositor can open a headless display, tests for window -# management, EWMH properties, etc. will be added here in the same -# style as tests/x11-smoke-test.sh. # # Exit 0 = PASS, non-zero = FAIL. @@ -21,6 +20,13 @@ set -e WMAKER=src/.libs/wmaker [ -x "$WMAKER" ] || WMAKER=src/wmaker +WM_PID="" + +cleanup() { + [ -n "$WM_PID" ] && kill "$WM_PID" 2>/dev/null || true +} +trap cleanup EXIT INT TERM + die() { echo "FAIL: $*" >&2 exit 1 @@ -36,6 +42,44 @@ nm "$WMAKER" 2>/dev/null | grep -q 'wl_backend' \ ldd "$WMAKER" 2>/dev/null | grep -qi 'wlroots\|libwlr' \ || die "wmaker does not link against wlroots" -# -- Done (headless compositor tests to be added as vtable is implemented) -- -echo "PASS (wl_backend symbol + wlroots link verified; full headless tests pending)" +# -- 3. Start wmaker with Wayland backend; verify it reaches event loop --- +# +# We use --no-dock --no-clip --no-autolaunch --static to avoid config +# file writes and autolaunch; --dont-restore skips session state reads. +# WMAKER_USE_WAYLAND=1 selects wl_backend. +# +# The test waits for the process to be alive (in the event loop) then +# sends SIGTERM and checks for clean exit. + +WMAKER_USE_WAYLAND=1 src/wmaker \ + --no-dock --no-clip --no-autolaunch \ + --dont-restore --static \ + --for-real & +WM_PID=$! + +# Wait up to 5s for wmaker to reach the event loop (stay alive) +i=0 +while [ $i -lt 50 ]; do + kill -0 "$WM_PID" 2>/dev/null || break + sleep 0.1 + i=$(( i + 1 )) +done + +kill -0 "$WM_PID" 2>/dev/null \ + || die "wmaker exited prematurely before SIGTERM (crashed during startup)" + +# Send SIGTERM and wait for clean exit +kill -TERM "$WM_PID" 2>/dev/null +WM_EXIT=0 +wait "$WM_PID" 2>/dev/null || WM_EXIT=$? +WM_PID="" + +# SIGTERM causes exit(0) via the Shutdown(WSExitMode) path; +# shell reports signal-terminated processes as 128+signal, but +# wmaker catches SIGTERM and calls Exit(0) so we expect 0. +[ "$WM_EXIT" -eq 0 ] \ + || die "wmaker exited with status $WM_EXIT after SIGTERM (expected 0)" + +# -- Done ----------------------------------------------------------------- +echo "PASS (wl_backend compositor starts and exits cleanly)" exit 0