From b4484e05bc34d1db684af7b9a591a961e50099b3 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Wed, 13 May 2026 13:51:27 +0200 Subject: [PATCH] Fix Wayland startup: compositor window appears and event loop runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three root causes fixed: 1. RCreateContext crash: wScreenInit called RCreateContext(dpy=NULL). Fix: add RCreateContextWayland() to wrlib — creates a minimal 32-bit ARGB context without X11. Wayland path in screen.c now always taken when is_wayland (removed incorrect XWayland fallthrough). 2. Color parsing: parse_color and get_color_for_colormap were no-ops. Fix: implement real color parser in wl_backend supporting #RGB, #RRGGBB, #RRRRGGGGBBBB, rgb:RR/GG/BB, and common named colors. Texture creation (solid, gradient) now works in Wayland mode. 3. RConvertImage crash: wrlib tried XCreateImage with NULL display. Fix: RConvertImage/RConvertImageMask return success with pixmap=0 when context->dpy is NULL. Callers don't use pixmaps in Wayland mode (rendering goes through vtable slots to pixman buffers). 4. Workspace crash: Wayland path skipped wWorkspaceNew(), so workspace_count=0 when menu tried to access workspace entries. Fix: add wWorkspaceNew + netwm_init to Wayland screen init path. Result: compositor starts, event loop runs, window is visible. WMAKER_USE_WAYLAND=1 wmaker now shows a compositor window. --- src/backend/backend.h | 3 + src/backend/wayland/wl_backend.c | 119 +++++++++++++++++++++++++++++-- src/backend/x11/x11_backend.c | 6 ++ src/screen.c | 51 ++++++++++--- wrlib/context.c | 47 ++++++++++++ wrlib/convert.c | 12 ++++ 6 files changed, 222 insertions(+), 16 deletions(-) diff --git a/src/backend/backend.h b/src/backend/backend.h index 51c3bf0..26ef205 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -1764,6 +1764,9 @@ typedef struct WMBackend { void (*netwm_position_splash)(WWindow *wwin, int *x, int *y, int width, int height); void (*netwm_check_initial_frame_state)(WWindow *wwin); + /* wrlib context creation */ + RContext *(*create_rcontext)(int screen_number, RContextAttributes *attribs); + } WMBackend; /* diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index d90e88c..32d77b3 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -6142,9 +6142,109 @@ static void wl_noop_appicon_paint(WAppIcon *aicon) { (void)aicon; } static void wl_noop_restore_desktop(WScreen *scr) { (void)scr; } static void wl_noop_wipe_desktop(WScreen *scr) { (void)scr; } -static int wl_noop_get_color_for_colormap(unsigned long colormap, +static int wl_get_color_for_colormap(unsigned long colormap, const char *color_name, XColor *color) -{ (void)colormap; (void)color_name; (void)color; return 0; } +{ + unsigned int r, g, b; + (void)colormap; + + if (!color_name || !color) + return 0; + + /* Parse #RGB, #RRGGBB, #RRRRGGGGBBBB */ + if (color_name[0] == '#') { + int len = strlen(color_name + 1); + if (len == 6) { + if (sscanf(color_name, "#%2x%2x%2x", &r, &g, &b) == 3) { + color->red = r << 8 | r; + color->green = g << 8 | g; + color->blue = b << 8 | b; + color->pixel = (0xFF << 24) | (r << 16) | (g << 8) | b; + return 1; + } + } else if (len == 12) { + if (sscanf(color_name, "#%4hx%4hx%4hx", + &color->red, &color->green, &color->blue) == 3) { + color->pixel = (0xFF << 24) | + ((color->red >> 8) << 16) | + ((color->green >> 8) << 8) | + (color->blue >> 8); + return 1; + } + } else if (len == 3) { + if (sscanf(color_name, "#%1x%1x%1x", &r, &g, &b) == 3) { + color->red = r << 12 | r << 8 | r << 4 | r; + color->green = g << 12 | g << 8 | g << 4 | g; + color->blue = b << 12 | b << 8 | b << 4 | b; + color->pixel = (0xFF << 24) | (r*17 << 16) | (g*17 << 8) | b*17; + return 1; + } + } + } + + /* X11 rgb:RR/GG/BB or rgb:RRRR/GGGG/BBBB format */ + if (strncasecmp(color_name, "rgb:", 4) == 0) { + unsigned int rv, gv, bv; + const char *p = color_name + 4; + int n = sscanf(p, "%x/%x/%x", &rv, &gv, &bv); + if (n == 3) { + /* Determine component width from first component */ + const char *slash = strchr(p, '/'); + int width = slash ? (int)(slash - p) : 2; + if (width == 1) { + color->red = rv << 12 | rv << 8 | rv << 4 | rv; + color->green = gv << 12 | gv << 8 | gv << 4 | gv; + color->blue = bv << 12 | bv << 8 | bv << 4 | bv; + } else if (width == 2) { + color->red = rv << 8 | rv; + color->green = gv << 8 | gv; + color->blue = bv << 8 | bv; + } else { + color->red = rv; + color->green = gv; + color->blue = bv; + } + color->pixel = (0xFF << 24) | + ((color->red >> 8) << 16) | + ((color->green >> 8) << 8) | + (color->blue >> 8); + return 1; + } + } + + /* Named colors — common ones */ + struct { const char *name; unsigned short r, g, b; } named[] = { + {"black", 0x0000, 0x0000, 0x0000}, + {"white", 0xFFFF, 0xFFFF, 0xFFFF}, + {"red", 0xFFFF, 0x0000, 0x0000}, + {"green", 0x0000, 0xFFFF, 0x0000}, + {"blue", 0x0000, 0x0000, 0xFFFF}, + {"gray", 0xBEBE, 0xBEBE, 0xBEBE}, + {"grey", 0xBEBE, 0xBEBE, 0xBEBE}, + {"gray50", 0x7F7F, 0x7F7F, 0x7F7F}, + {"gray80", 0xCCCC, 0xCCCC, 0xCCCC}, + {"darkgray",0xA9A9, 0xA9A9, 0xA9A9}, + {"darkgrey",0xA9A9, 0xA9A9, 0xA9A9}, + {"lightgray",0xD3D3, 0xD3D3, 0xD3D3}, + {"lightgrey",0xD3D3, 0xD3D3, 0xD3D3}, + {NULL, 0, 0, 0} + }; + for (int i = 0; named[i].name; i++) { + if (strcasecmp(color_name, named[i].name) == 0) { + color->red = named[i].r; + color->green = named[i].g; + color->blue = named[i].b; + color->pixel = (0xFF << 24) | + ((color->red >> 8) << 16) | + ((color->green >> 8) << 8) | + (color->blue >> 8); + return 1; + } + } + + wwarning(_("could not parse color \"%s\" (Wayland)"), color_name); + return 0; +} static void wl_noop_free_color(WScreen *scr, unsigned long pixel) { (void)scr; (void)pixel; } @@ -6234,8 +6334,8 @@ static void wl_noop_query_tree(WNativeWindow root, WNativeWindow **children_out, static WScreen *wl_noop_screen_for_window(WNativeWindow window) { (void)window; return wScreenWithNumber(0); } -static int wl_noop_parse_color(WScreen *scr, const char *name, XColor *color_out) -{ (void)scr; (void)name; (void)color_out; return 0; } +static int wl_parse_color(WScreen *scr, const char *name, XColor *color_out) +{ (void)scr; return wl_get_color_for_colormap(0, name, color_out); } static int wl_noop_cursor_create_from_files(WScreen *scr, const char *bitmap_path, const char *mask_path, Cursor *cursor_out) { (void)scr; (void)bitmap_path; (void)mask_path; (void)cursor_out; return 0; } @@ -6282,6 +6382,12 @@ static void wl_noop_netwm_check_client_hint_change(WWindow *w, void *e) { (void) static void wl_noop_netwm_position_splash(WWindow *w, int *x, int *y, int ww, int h) { (void)w;(void)x;(void)y;(void)ww;(void)h; } static void wl_noop_netwm_check_initial_frame_state(WWindow *w) { (void)w; } +static RContext *wl_create_rcontext(int screen_number, RContextAttributes *attribs) +{ + (void)screen_number; + return RCreateContextWayland(1920, 1080, attribs); +} + /* ------------------------------------------------------------------ */ /* vtable */ /* ------------------------------------------------------------------ */ @@ -6475,7 +6581,7 @@ const WMBackend wl_backend = { .appicon_paint = wl_noop_appicon_paint, .restore_desktop = wl_noop_restore_desktop, .wipe_desktop = wl_noop_wipe_desktop, - .get_color_for_colormap = wl_noop_get_color_for_colormap, + .get_color_for_colormap = wl_get_color_for_colormap, .free_color = wl_noop_free_color, .colormap_install = wl_noop_colormap_install, .xdnd_init = wl_noop_xdnd_init, @@ -6509,7 +6615,7 @@ const WMBackend wl_backend = { .context_init = wl_context_init, .query_tree = wl_noop_query_tree, .screen_for_window = wl_noop_screen_for_window, - .parse_color = wl_noop_parse_color, + .parse_color = wl_parse_color, .cursor_create_from_files = wl_noop_cursor_create_from_files, .cursor_free = wl_noop_cursor_free, .cursor_set = wl_noop_cursor_set, @@ -6544,4 +6650,5 @@ const WMBackend wl_backend = { .netwm_check_client_hint_change = wl_noop_netwm_check_client_hint_change, .netwm_position_splash = wl_noop_netwm_position_splash, .netwm_check_initial_frame_state = wl_noop_netwm_check_initial_frame_state, + .create_rcontext = wl_create_rcontext, }; diff --git a/src/backend/x11/x11_backend.c b/src/backend/x11/x11_backend.c index 93910a3..9d9e382 100644 --- a/src/backend/x11/x11_backend.c +++ b/src/backend/x11/x11_backend.c @@ -3381,6 +3381,11 @@ static void x11_netwm_check_initial_frame_state(WWindow *wwin) { wNETWMCheckInitialFrameState(wwin); } + +static RContext *x11_create_rcontext(int screen_number, RContextAttributes *attribs) +{ + return RCreateContext(dpy, screen_number, attribs); +} /* -------------------------------------------------------------------- */ /* Client operations (Phase 80) */ /* -------------------------------------------------------------------- */ @@ -4260,6 +4265,7 @@ const WMBackend x11_backend = { .netwm_check_client_hint_change = x11_netwm_check_client_hint_change, .netwm_position_splash = x11_netwm_position_splash, .netwm_check_initial_frame_state = x11_netwm_check_initial_frame_state, + .create_rcontext = x11_create_rcontext, }; /* diff --git a/src/screen.c b/src/screen.c index 8121e62..8ae2fc1 100644 --- a/src/screen.c +++ b/src/screen.c @@ -158,18 +158,49 @@ WScreen *wScreenInit(int screen_number) wm_backend->monitors_select_events(scr); wm_backend->screen_set_root_cursor(scr, WCUR_ROOT); - /* Pure Wayland path (no XWayland display): skip X11/wrlib context setup - * and allocate workspace 0 directly. When XWayland is available (dpy is - * set, Path 1), fall through to the full X11 init so WINGs, RContext, - * GCs, and menus work correctly on the XWayland display. */ - if (wm_backend->is_wayland && scr->root_win == 0) { - scr->w_depth = 24; - scr->rcontext = NULL; + /* Wayland path: WM UI is pure Wayland (pixman + wlr_scene). + * XWayland exists only for X11 client apps, not for the WM itself. */ + if (wm_backend->is_wayland) { + scr->w_depth = 32; + scr->rcontext = RCreateContextWayland(scr->scr_width, scr->scr_height, NULL); /* WINGs screen — pure Wayland, no X11. */ - scr->wmscreen = WMCreateScreenWithRContext(NULL, 0, NULL); + scr->wmscreen = WMCreateScreenWithRContext(NULL, 0, scr->rcontext); + + if (!scr->wmscreen) { + wfatal(_("could not initialize WINGs widget set")); + wfree(scr); + return NULL; + } + + scr->black = WMBlackColor(scr->wmscreen); + scr->white = WMWhiteColor(scr->wmscreen); + scr->gray = WMGrayColor(scr->wmscreen); + scr->darkGray = WMDarkGrayColor(scr->wmscreen); + + scr->black_pixel = scr->black ? WMColorPixel(scr->black) : 0xFF000000; + scr->white_pixel = scr->white ? WMColorPixel(scr->white) : 0xFFFFFFFF; + scr->light_pixel = scr->gray ? WMColorPixel(scr->gray) : 0xFFAAAAAA; + scr->dark_pixel = scr->darkGray ? WMColorPixel(scr->darkGray) : 0xFF555555; + + wm_backend->screen_alloc_gcs(scr); + wReadDefaults(scr, w_global.domain.wmaker->dictionary); + + if (scr->frame_border_color) + scr->frame_border_pixel = WMColorPixel(scr->frame_border_color); + if (scr->frame_focused_border_color) + scr->frame_focused_border_pixel = WMColorPixel(scr->frame_focused_border_color); + if (scr->frame_selected_border_color) + scr->frame_selected_border_pixel = WMColorPixel(scr->frame_selected_border_color); wm_backend->screen_create_internals(scr); + + scr->info_text_font = WMBoldSystemFontOfSize(scr->wmscreen, 12); + + wm_backend->netwm_init(scr); + wWorkspaceNew(scr); + wm_backend->screen_create_pixmaps(scr); + goto screen_done; } @@ -190,7 +221,7 @@ WScreen *wScreenInit(int screen_number) rattr.visualid = getWVisualID(screen_number); } - scr->rcontext = RCreateContext(dpy, screen_number, &rattr); + scr->rcontext = wm_backend->create_rcontext(screen_number, &rattr); if (!scr->rcontext && RErrorCode == RERR_STDCMAPFAIL) { wwarning("%s", RMessageForError(RErrorCode)); @@ -198,7 +229,7 @@ WScreen *wScreenInit(int screen_number) rattr.flags &= ~RC_StandardColormap; rattr.standard_colormap_mode = RUseStdColormap; - scr->rcontext = RCreateContext(dpy, screen_number, &rattr); + scr->rcontext = wm_backend->create_rcontext(screen_number, &rattr); } if (scr->rcontext == NULL) { wfatal(_("can't create Context on screen %d, %s"), diff --git a/wrlib/context.c b/wrlib/context.c index 7304450..80e6bb1 100644 --- a/wrlib/context.c +++ b/wrlib/context.c @@ -716,3 +716,50 @@ static Bool bestContext(Display * dpy, int screen_number, RContext * context) else return True; } + +/* + * Create a minimal RContext for Wayland mode. + * No X11 connection needed — just sets up 32-bit TrueColor parameters + * so that RImage operations (load, scale, combine) work. + * RConvertImage will not work (returns 0) since there's no X11 drawable. + */ +RContext *RCreateContextWayland(int width, int height, + const RContextAttributes *attribs) +{ + RContext *context; + RContextAttributes *attr; + + context = malloc(sizeof(RContext)); + if (!context) { + RErrorCode = RERR_NOMEMORY; + return NULL; + } + memset(context, 0, sizeof(RContext)); + + attr = malloc(sizeof(RContextAttributes)); + if (!attr) { + free(context); + RErrorCode = RERR_NOMEMORY; + return NULL; + } + if (attribs) + *attr = *attribs; + else + memset(attr, 0, sizeof(RContextAttributes)); + + context->attribs = attr; + + /* 32-bit ARGB TrueColor — matches pixman ARGB32 */ + context->depth = 32; + context->vclass = TrueColor; + context->red_offset = 16; + context->green_offset = 8; + context->blue_offset = 0; + context->black = 0xFF000000; + context->white = 0xFFFFFFFF; + + (void)width; + (void)height; + + return context; +} diff --git a/wrlib/convert.c b/wrlib/convert.c index 400234f..e7f608c 100644 --- a/wrlib/convert.c +++ b/wrlib/convert.c @@ -871,6 +871,12 @@ int RConvertImage(RContext * context, RImage * image, Pixmap * pixmap) assert(image != NULL); assert(pixmap != NULL); + /* Wayland mode: no X11 display, pixmaps are not used */ + if (!context->dpy) { + *pixmap = (Pixmap)0; + return 1; + } + switch (context->vclass) { case TrueColor: ximg = image2TrueColor(context, image); @@ -940,6 +946,12 @@ int RConvertImageMask(RContext * context, RImage * image, Pixmap * pixmap, Pixma assert(pixmap != NULL); assert(mask != NULL); + if (!context->dpy) { + *pixmap = (Pixmap)0; + *mask = (Pixmap)0; + return True; + } + if (!RConvertImage(context, image, pixmap)) return False;