From ec61c13aa688052359becafadc4d0c7f9fbccb7a Mon Sep 17 00:00:00 2001 From: Slendi Date: Sun, 10 Aug 2025 12:58:55 +0300 Subject: [PATCH] Add xwayland, fix naming Signed-off-by: Slendi --- src/LunarWM.c | 406 ++++++++++++++++++++++++++++++++++++++++++++------ src/LunarWM.h | 17 ++- 2 files changed, 376 insertions(+), 47 deletions(-) diff --git a/src/LunarWM.c b/src/LunarWM.c index cb8ca03..e1f06b8 100644 --- a/src/LunarWM.c +++ b/src/LunarWM.c @@ -43,9 +43,14 @@ void toplevel_commit_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), commit); - if (tl->xdg_toplevel->base->initial_commit) { - wlr_xdg_toplevel_set_size(tl->xdg_toplevel, 0, 0); + if (!tl || !tl->surface) return; + + if (!tl->is_xwayland) { + if (tl->u.xdg && tl->u.xdg->base->initial_commit) { + wlr_xdg_toplevel_set_size(tl->u.xdg, 0, 0); + return; + } } LunarWM_Toplevel_update(tl); @@ -65,12 +70,13 @@ void toplevel_destroy_notify(struct wl_listener *l, void *) free(tl); } -bool LunarWM_Toplevel_init( - LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg) +bool LunarWM_Toplevel_init_xdg( + LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xdg_toplevel *xdg) { tl->id = vector_size(wm->wayland.v_toplevels) + 1; tl->server = wm; - tl->xdg_toplevel = xdg; + tl->is_xwayland = false; + tl->u.xdg = xdg; tl->surface = xdg->base->surface; assert(tl->surface); @@ -84,6 +90,26 @@ bool LunarWM_Toplevel_init( return true; } +bool LunarWM_Toplevel_init_xwayland( + LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xwayland_surface *xwl) +{ + tl->id = vector_size(wm->wayland.v_toplevels) + 1; + tl->server = wm; + tl->is_xwayland = true; + tl->u.xwl = xwl; + tl->surface = xwl->surface; + + assert(tl->surface); + + tl->commit.notify = toplevel_commit_notify; + wl_signal_add(&tl->surface->events.commit, &tl->commit); + + tl->destroy.notify = toplevel_destroy_notify; + wl_signal_add(&tl->surface->events.destroy, &tl->destroy); + + return true; +} + bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) { wl_list_remove(&this->commit.link); @@ -131,6 +157,278 @@ bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) return true; } +static void clamp_to_hints( + const struct wlr_xwayland_surface *x, uint16_t *w, uint16_t *h) +{ + xcb_size_hints_t *hints = x->size_hints; + if (!hints) + return; + + if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) { + if (*w < hints->min_width) + *w = hints->min_width; + if (*h < hints->min_height) + *h = hints->min_height; + } + if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) { + if (hints->max_width > 0 && *w > hints->max_width) + *w = hints->max_width; + if (hints->max_height > 0 && *h > hints->max_height) + *h = hints->max_height; + } + if ((hints->flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) { + if (hints->width_inc > 0) + *w = (*w / hints->width_inc) * hints->width_inc; + if (hints->height_inc > 0) + *h = (*h / hints->height_inc) * hints->height_inc; + } + if ((hints->flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE)) { + if (*w < hints->base_width) + *w = hints->base_width; + if (*h < hints->base_height) + *h = hints->base_height; + } +} + +struct XwlHooks { + LunarWM *wm; + struct wlr_xwayland_surface *xwl; + struct wl_listener associate; + struct wl_listener dissociate; + struct wl_listener destroy; + + struct wl_listener req_configure; + struct wl_listener req_maximize; + struct wl_listener req_fullscreen; + struct wl_listener set_geometry; +}; + +static void xwayland_ready_notify(struct wl_listener *l, void *data) +{ + LunarWM *wm = wl_container_of(l, wm, wayland.xwayland_ready); + wlr_xwayland_set_seat(wm->wayland.xwayland, wm->wayland.seat); + setenv("DISPLAY", wm->wayland.xwayland->display_name, 1); +} + +static void handle_associate(struct wl_listener *ll, void *d) +{ + struct wlr_xwayland_surface *x = d; + LunarWM *wm2 = wl_container_of(ll, wm2, wayland.xwayland_associate_tmp); + if (!x->surface) + return; + + LunarWM_Toplevel *tl = calloc(1, sizeof *tl); + if (!tl) + return; + + if (LunarWM_Toplevel_init_xwayland(tl, wm2, x)) { + vector_add(&wm2->wayland.v_toplevels, tl); + } else { + free(tl); + } + wl_list_remove(&wm2->wayland.xwayland_associate_tmp.link); +} + +static void handle_dissociate(struct wl_listener *ll, void *d) +{ + struct wlr_xwayland_surface *x = d; + LunarWM *wm2 = wl_container_of(ll, wm2, wayland.xwayland_dissociate_tmp); + for (size_t i = 0; i < vector_size(wm2->wayland.v_toplevels); ++i) { + LunarWM_Toplevel *tl = wm2->wayland.v_toplevels[i]; + if (tl->is_xwayland && tl->u.xwl == x) { + vector_remove(wm2->wayland.v_toplevels, i); + LunarWM_Toplevel_destroy(tl); + free(tl); + break; + } + } + wl_list_remove(&wm2->wayland.xwayland_dissociate_tmp.link); +} + +static void xwl_hooks_destroy(struct XwlHooks *h) +{ + if (!h) + return; + if (h->associate.link.prev || h->associate.link.next) + wl_list_remove(&h->associate.link); + if (h->dissociate.link.prev || h->dissociate.link.next) + wl_list_remove(&h->dissociate.link); + if (h->destroy.link.prev || h->destroy.link.next) + wl_list_remove(&h->destroy.link); + free(h); +} + +static void xwl_on_associate(struct wl_listener *ll, void *data) +{ + struct XwlHooks *h = wl_container_of(ll, h, associate); + struct wlr_xwayland_surface *x = h->xwl; + if (!x || !x->surface) + return; + + LunarWM_Toplevel *tl = calloc(1, sizeof *tl); + if (!tl) + return; + if (LunarWM_Toplevel_init_xwayland(tl, h->wm, x)) { + vector_add(&h->wm->wayland.v_toplevels, tl); + } else { + free(tl); + } +} + +static void xwl_unmap_toplevel(LunarWM_Toplevel *tl) +{ + if (!tl) + return; + + if (tl->commit.link.prev || tl->commit.link.next) + wl_list_remove(&tl->commit.link); + if (tl->destroy.link.prev || tl->destroy.link.next) + wl_list_remove(&tl->destroy.link); + + if (tl->locked_buffer) { + wlr_buffer_unlock(tl->locked_buffer); + tl->locked_buffer = NULL; + } + + tl->texture = NULL; + tl->gles_texture = NULL; + tl->rl_texture = (Texture) { 0 }; + tl->surface = NULL; +} + +static void xwl_on_dissociate(struct wl_listener *ll, void *data) +{ + struct XwlHooks *h = wl_container_of(ll, h, dissociate); + LunarWM *wm = h->wm; + struct wlr_xwayland_surface *x = h->xwl; + + for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { + LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i]; + if (tl->is_xwayland && tl->u.xwl == x) { + xwl_unmap_toplevel(tl); + break; + } + } +} + +static void xwl_on_request_configure(struct wl_listener *ll, void *data) +{ + struct XwlHooks *xh = wl_container_of(ll, xh, req_configure); + struct wlr_xwayland_surface_configure_event *ev = data; + if (!xh->xwl) + return; + + int16_t x = xh->xwl->x, y = xh->xwl->y; + uint16_t w = xh->xwl->width, h = xh->xwl->height; + + if (ev->mask & XCB_CONFIG_WINDOW_X) + x = ev->x; + if (ev->mask & XCB_CONFIG_WINDOW_Y) + y = ev->y; + if (ev->mask & XCB_CONFIG_WINDOW_WIDTH) + w = ev->width; + if (ev->mask & XCB_CONFIG_WINDOW_HEIGHT) + h = ev->height; + + clamp_to_hints(xh->xwl, &w, &h); + + wlr_xwayland_surface_configure(xh->xwl, x, y, w, h); +} + +static void xwl_on_request_maximize(struct wl_listener *ll, void *data) +{ + struct XwlHooks *xh = wl_container_of(ll, xh, req_maximize); + wlr_xwayland_surface_set_maximized(xh->xwl, true, true); +} + +static void xwl_on_request_fullscreen(struct wl_listener *ll, void *data) +{ + struct XwlHooks *xh = wl_container_of(ll, xh, req_fullscreen); + wlr_xwayland_surface_set_fullscreen(xh->xwl, true); +} + +static void xwl_on_set_geometry(struct wl_listener *ll, void *data) +{ + (void)ll; + (void)data; +} + +static void xwl_on_destroy(struct wl_listener *ll, void *data) +{ + struct XwlHooks *xh = wl_container_of(ll, xh, destroy); + LunarWM *wm = xh->wm; + struct wlr_xwayland_surface *x = xh->xwl; + + if (xh->req_configure.link.prev) + wl_list_remove(&xh->req_configure.link); + if (xh->req_maximize.link.prev) + wl_list_remove(&xh->req_maximize.link); + if (xh->req_fullscreen.link.prev) + wl_list_remove(&xh->req_fullscreen.link); + if (xh->set_geometry.link.prev) + wl_list_remove(&xh->set_geometry.link); + if (xh->associate.link.prev) + wl_list_remove(&xh->associate.link); + if (xh->dissociate.link.prev) + wl_list_remove(&xh->dissociate.link); + if (xh->destroy.link.prev) + wl_list_remove(&xh->destroy.link); + + for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { + LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i]; + if (tl->is_xwayland && tl->u.xwl == x) { + vector_remove(wm->wayland.v_toplevels, i); + free(tl); + break; + } + } + + free(xh); +} + +static void xwayland_new_surface_notify(struct wl_listener *l, void *data) +{ + LunarWM *wm = wl_container_of(l, wm, wayland.xwayland_new_surface); + struct wlr_xwayland_surface *xwl = data; + + if (xwl->surface) { + LunarWM_Toplevel *tl = calloc(1, sizeof *tl); + if (tl && LunarWM_Toplevel_init_xwayland(tl, wm, xwl)) + vector_add(&wm->wayland.v_toplevels, tl); + else + free(tl); + } + + struct XwlHooks *h = calloc(1, sizeof *h); + if (!h) + return; + h->wm = wm; + h->xwl = xwl; + + h->associate.notify = xwl_on_associate; + wl_signal_add(&xwl->events.associate, &h->associate); + + h->dissociate.notify = xwl_on_dissociate; + wl_signal_add(&xwl->events.dissociate, &h->dissociate); + + h->req_configure.notify = xwl_on_request_configure; + wl_signal_add(&xwl->events.request_configure, &h->req_configure); + + h->req_maximize.notify = xwl_on_request_maximize; + wl_signal_add(&xwl->events.request_maximize, &h->req_maximize); + + h->req_fullscreen.notify = xwl_on_request_fullscreen; + wl_signal_add(&xwl->events.request_fullscreen, &h->req_fullscreen); + + h->set_geometry.notify = xwl_on_set_geometry; + wl_signal_add(&xwl->events.set_geometry, &h->set_geometry); + + h->destroy.notify = xwl_on_destroy; + wl_signal_add(&xwl->events.destroy, &h->destroy); + + xwl->data = h; +} + void LunarWM_Toplevel_focus(LunarWM_Toplevel *this) { if (!this) @@ -139,19 +437,29 @@ void LunarWM_Toplevel_focus(LunarWM_Toplevel *this) LunarWM *wm = this->server; struct wlr_seat *seat = wm->wayland.seat; struct wlr_surface *prev_surface = seat->keyboard_state.focused_surface; - struct wlr_surface *surface = this->xdg_toplevel->base->surface; + struct wlr_surface *surface = this->surface; if (prev_surface == surface) { return; } if (prev_surface) { - struct wlr_xdg_toplevel *prev_toplevel + struct wlr_xdg_toplevel *prev_tl = wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); - if (prev_toplevel != NULL) { - wlr_xdg_toplevel_set_activated(prev_toplevel, false); - } + if (prev_tl) + wlr_xdg_toplevel_set_activated(prev_tl, false); + struct wlr_xwayland_surface *prev_x + = wlr_xwayland_surface_try_from_wlr_surface(prev_surface); + if (prev_x) + wlr_xwayland_surface_activate(prev_x, false); } struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); - wlr_xdg_toplevel_set_activated(this->xdg_toplevel, true); + + if (this->is_xwayland) { + wlr_xwayland_surface_offer_focus(this->u.xwl); + wlr_xwayland_surface_activate(this->u.xwl, true); + } else { + wlr_xdg_toplevel_set_activated(this->u.xdg, true); + } + if (keyboard != NULL) { wlr_seat_keyboard_notify_enter(seat, surface, keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers); @@ -338,7 +646,7 @@ static void new_xdg_toplevel_listener_notify( return; } - if (LunarWM_Toplevel_init(tl, wm, xdg_tl)) { + if (LunarWM_Toplevel_init_xdg(tl, wm, xdg_tl)) { vector_add(&wm->wayland.v_toplevels, tl); } else { wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); @@ -454,7 +762,13 @@ static bool init_wayland(LunarWM *this) this->wayland.xwayland = wlr_xwayland_create(this->wayland.display, this->wayland.compositor, this->cman->cfg.xwayland.lazy); - setenv("DISPLAY", this->wayland.xwayland->display_name, 1); + this->wayland.xwayland_ready.notify = xwayland_ready_notify; + wl_signal_add(&this->wayland.xwayland->events.ready, + &this->wayland.xwayland_ready); + + this->wayland.xwayland_new_surface.notify = xwayland_new_surface_notify; + wl_signal_add(&this->wayland.xwayland->events.new_surface, + &this->wayland.xwayland_new_surface); } return true; @@ -1576,34 +1890,34 @@ static void DrawBillboardNoShear( // pass yFlip from tl->attribs.invert_y static void DrawTexture3D( - Texture2D tex, Vector3 position, Vector3 target, float scale, bool yFlip) + Texture2D tex, Vector3 position, Vector3 target, float scale, bool y_flip) { if (!tex.id) return; Vector3 fwd = Vector3Normalize(Vector3Subtract(target, position)); - Vector3 upRef = (fabsf(fwd.y) > 0.99f) ? (Vector3) { 0, 0, 1 } - : (Vector3) { 0, 1, 0 }; - Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, upRef)); + Vector3 up_ref = (fabsf(fwd.y) > 0.99f) ? (Vector3) { 0, 0, 1 } + : (Vector3) { 0, 1, 0 }; + Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, up_ref)); Vector3 up = Vector3CrossProduct(right, fwd); - float halfW = 0.5f * (float)tex.width * scale; - float halfH = 0.5f * (float)tex.height * scale; + float half_w = 0.5f * (float)tex.width * scale; + float half_h = 0.5f * (float)tex.height * scale; Vector3 tl - = Vector3Add(Vector3Subtract(position, Vector3Scale(right, halfW)), - Vector3Scale(up, halfH)); - Vector3 tr = Vector3Add(Vector3Add(position, Vector3Scale(right, halfW)), - Vector3Scale(up, halfH)); + = Vector3Add(Vector3Subtract(position, Vector3Scale(right, half_w)), + Vector3Scale(up, half_h)); + Vector3 tr = Vector3Add(Vector3Add(position, Vector3Scale(right, half_w)), + Vector3Scale(up, half_h)); Vector3 br - = Vector3Subtract(Vector3Add(position, Vector3Scale(right, halfW)), - Vector3Scale(up, halfH)); - Vector3 bl - = Vector3Subtract(Vector3Subtract(position, Vector3Scale(right, halfW)), - Vector3Scale(up, halfH)); + = Vector3Subtract(Vector3Add(position, Vector3Scale(right, half_w)), + Vector3Scale(up, half_h)); + Vector3 bl = Vector3Subtract( + Vector3Subtract(position, Vector3Scale(right, half_w)), + Vector3Scale(up, half_h)); - float vt = yFlip ? 1.0f : 0.0f; - float vb = yFlip ? 0.0f : 1.0f; + float vt = y_flip ? 1.0f : 0.0f; + float vb = y_flip ? 0.0f : 1.0f; rlDisableBackfaceCulling(); // ensure visible regardless of winding rlSetTexture(tex.id); @@ -1755,30 +2069,32 @@ static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) RL_ATTACHMENT_TEXTURE2D, 0); assert(rlFramebufferComplete(this->renderer.fbo)); - uint32_t const eyeW + uint32_t const eye_w = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; - uint32_t const eyeH + uint32_t const eye_h = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; this->renderer.tmp_rt = (RenderTexture2D) { .id = this->renderer.fbo, - .texture = { color_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, - .depth = { depth_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, + .texture = { color_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 }, + .depth = { depth_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 }, }; // head-space view matrix (matches rlOpenXR) XrSpaceLocation headLoc = { .type = XR_TYPE_SPACE_LOCATION }; xrLocateSpace(this->xr.view_space, this->xr.local_space, info->predicted_display_time, &headLoc); - auto const headView = MatrixInvert(xr_matrix(headLoc.pose)); + auto const head_view = MatrixInvert(xr_matrix(headLoc.pose)); // per-eye projection + view-offset - Matrix const projR = xr_projection_matrix(views[0].fov); - Matrix const projL = xr_projection_matrix(views[1].fov); - Matrix const viewOffL = MatrixMultiply(xr_matrix(views[0].pose), headView); - Matrix const viewOffR = MatrixMultiply(xr_matrix(views[1].pose), headView); + Matrix const proj_r = xr_projection_matrix(views[0].fov); + Matrix const proj_l = xr_projection_matrix(views[1].fov); + Matrix const view_off_l + = MatrixMultiply(xr_matrix(views[0].pose), head_view); + Matrix const view_off_r + = MatrixMultiply(xr_matrix(views[1].pose), head_view); - int const hud_size = eyeH * 0.3; + int const hud_size = eye_h * 0.3; if (!IsTextureValid(this->renderer.hud_rt.texture)) { this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size); } @@ -1795,10 +2111,10 @@ static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) BeginTextureMode(this->renderer.tmp_rt); rlEnableStereoRender(); - rlSetMatrixProjectionStereo(projR, projL); // right, left (yes) - rlSetMatrixViewOffsetStereo(viewOffR, viewOffL); + rlSetMatrixProjectionStereo(proj_r, proj_l); // right, left (yes) + rlSetMatrixViewOffsetStereo(view_off_r, view_off_l); - glViewport(0, 0, (GLsizei)eyeW * view_count, (GLsizei)eyeH); + glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h); ClearBackground((Color) { 0, 0, 10, 255 }); for (int i = 0; i < 1; i++) { @@ -1850,14 +2166,14 @@ static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) info->layer_projection_views_count = view_count; for (uint32_t i = 0; i < view_count; ++i) { - int32_t const xOff = i * eyeW; + int32_t const xOff = i * eye_w; auto *pv = &info->layer_projection_views[i]; pv->pose = views[i].pose; pv->fov = views[i].fov; pv->subImage.swapchain = color_sc->swapchain; pv->subImage.imageRect.offset = (XrOffset2Di) { .x = xOff, .y = 0 }; pv->subImage.imageRect.extent - = (XrExtent2Di) { .width = eyeW, .height = eyeH }; + = (XrExtent2Di) { .width = eye_w, .height = eye_h }; pv->subImage.imageArrayIndex = 0; } diff --git a/src/LunarWM.h b/src/LunarWM.h index 6be17d6..7022d22 100644 --- a/src/LunarWM.h +++ b/src/LunarWM.h @@ -50,12 +50,18 @@ typedef struct { typedef struct { uint32_t id; + bool is_xwayland; + struct LunarWM *server; struct wl_listener commit; struct wl_listener destroy; - struct wlr_xdg_toplevel *xdg_toplevel; + union { + struct wlr_xdg_toplevel *xdg; + struct wlr_xwayland_surface *xwl; + } u; + struct wlr_surface *surface; struct wlr_texture *texture; @@ -65,8 +71,10 @@ typedef struct { Texture2D rl_texture; } LunarWM_Toplevel; -bool LunarWM_Toplevel_init( +bool LunarWM_Toplevel_init_xdg( LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg); +bool LunarWM_Toplevel_init_xwayland( + LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xwayland_surface *xwl); bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this); bool LunarWM_Toplevel_update(LunarWM_Toplevel *this); @@ -130,6 +138,11 @@ typedef struct LunarWM { struct wlr_xwayland *xwayland; + struct wl_listener xwayland_ready; + struct wl_listener xwayland_new_surface; + struct wl_listener xwayland_associate_tmp; // per-surface temp + struct wl_listener xwayland_dissociate_tmp; // per-surface temp + LunarWM_Toplevel **v_toplevels; int current_focus; } wayland;