#include "LunarWM_wayland.h" #include "common.h" #include "vec.h" #include #include #include #include #include #include #include #include #include #include static inline SphericalCoord get_forward_spherical_with_nearest( Vector3 fwd, float r) { if (fabs(fwd.y) < 0.2f) { fwd.y = 0; } auto vec = Vector3Scale(Vector3Normalize(fwd), r); return Vector3ToSpherical(vec); } static void remove_windows_for_tl(LunarWM *wm, LunarWM_Toplevel *tl) { if (!wm || !tl) return; for (size_t ws = 0; ws < ARRAY_SZ(wm->wm.workspaces); ++ws) { auto *vec = &wm->wm.workspaces[ws].v_windows; for (size_t i = 0; i < vector_size(*vec);) { if ((*vec)[i].tl == tl) { vector_remove(*vec, i); } else { i++; } } } } static void focus_fallback(LunarWM *wm); static void toplevel_commit_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), commit); 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); } static void toplevel_destroy_notify(struct wl_listener *l, void *) { auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), destroy); for (size_t i = 0; i < vector_size(tl->server->wayland.v_toplevels); i++) { if (tl == tl->server->wayland.v_toplevels[i]) { vector_remove(tl->server->wayland.v_toplevels, i); break; } } remove_windows_for_tl(tl->server, tl); focus_fallback(tl->server); LunarWM_Toplevel_destroy(tl); free(tl); } static void toplevel_map_notify(struct wl_listener *l, void *data) { (void)data; LunarWM_Toplevel *tl = wl_container_of(l, (LunarWM_Toplevel *)0, map); if (!tl || !tl->surface) return; if (tl->is_xwayland && tl->u.xwl) { if (tl->u.xwl->override_redirect && !wlr_xwayland_surface_override_redirect_wants_focus(tl->u.xwl)) { return; } } LunarWM_Toplevel_focus(tl); LunarWM *wm = tl->server; for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); ++i) { if (wm->wayland.v_toplevels[i] == tl) { wm->wayland.current_focus = (int)i; break; } } } static void toplevel_unmap_notify(struct wl_listener *l, void *data) { (void)data; LunarWM_Toplevel *tl = wl_container_of(l, tl, unmap); if (!tl || !tl->surface) return; if (tl->map.link.prev || tl->map.link.next) wl_list_remove(&tl->map.link); if (tl->unmap.link.prev || tl->unmap.link.next) wl_list_remove(&tl->unmap.link); if (tl->commit.link.prev || tl->commit.link.next) wl_list_remove(&tl->commit.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; focus_fallback(tl->server); } bool LunarWM_Toplevel_init_xdg( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xdg_toplevel *xdg) { tl->id = LunarWM_get_new_id(wm); tl->server = wm; tl->is_xwayland = false; tl->u.xdg = xdg; tl->surface = xdg->base->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); tl->map.notify = toplevel_map_notify; wl_signal_add(&tl->u.xdg->base->surface->events.map, &tl->map); tl->unmap.notify = toplevel_unmap_notify; wl_signal_add(&tl->u.xdg->base->surface->events.unmap, &tl->unmap); return true; } bool LunarWM_Toplevel_init_xwayland( LunarWM_Toplevel *tl, LunarWM *wm, struct wlr_xwayland_surface *xwl) { tl->id = LunarWM_get_new_id(wm); 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); tl->map.notify = toplevel_map_notify; wl_signal_add(&xwl->surface->events.map, &tl->map); tl->unmap.notify = toplevel_unmap_notify; wl_signal_add(&xwl->surface->events.unmap, &tl->unmap); return true; } bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) { if (!this) return false; if (this->map.link.prev || this->map.link.next) wl_list_remove(&this->map.link); if (this->unmap.link.prev || this->unmap.link.next) wl_list_remove(&this->unmap.link); if (this->commit.link.prev || this->commit.link.next) wl_list_remove(&this->commit.link); if (this->destroy.link.prev || this->destroy.link.next) wl_list_remove(&this->destroy.link); if (this->locked_buffer) wlr_buffer_unlock(this->locked_buffer); return true; } bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) { if (!this || !this->surface) return false; this->texture = wlr_surface_get_texture(this->surface); struct wlr_client_buffer *cl_buf = this->surface->buffer; if ((this->texture == nullptr) || (cl_buf == nullptr)) { return false; } if ((this->locked_buffer != nullptr) && this->locked_buffer != &cl_buf->base) { wlr_buffer_unlock(this->locked_buffer); this->locked_buffer = nullptr; } if (this->locked_buffer == nullptr) { this->locked_buffer = wlr_buffer_lock(&cl_buf->base); } wlr_gles2_texture_get_attribs(this->texture, &this->attribs); this->gles_texture = gles2_get_texture(this->texture); if (this->gles_texture == nullptr) { return false; } this->rl_texture.id = (unsigned int)this->attribs.tex; this->rl_texture.width = (int)this->texture->width; this->rl_texture.height = (int)this->texture->height; this->rl_texture.mipmaps = 1; this->rl_texture.format = this->gles_texture->has_alpha ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 : PIXELFORMAT_UNCOMPRESSED_R8G8B8; SetTextureFilter(this->rl_texture, TEXTURE_FILTER_BILINEAR); SetTextureWrap(this->rl_texture, TEXTURE_WRAP_CLAMP); 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 req_activate; struct wl_listener set_geometry; }; static void xwayland_ready_notify(struct wl_listener *l, void *data) { (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); LunarWM_Window window = { .tl = tl, .coord = get_forward_spherical_with_nearest( wm2->renderer.camera.target, wm2->cman->cfg.space.radius), }; vector_add( &wm2->wm.workspaces[wm2->wm.active_workspace].v_windows, window); } 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); remove_windows_for_tl(wm2, tl); 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->map.link.prev || tl->map.link.next) wl_list_remove(&tl->map.link); if (tl->unmap.link.prev || tl->unmap.link.next) wl_list_remove(&tl->unmap.link); 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; } } focus_fallback(wm); } 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) { (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) { (void)data; struct XwlHooks *xh = wl_container_of(ll, xh, req_fullscreen); wlr_xwayland_surface_set_fullscreen(xh->xwl, true); } static void xwl_on_request_activate(struct wl_listener *ll, void *data) { (void)data; struct XwlHooks *h = wl_container_of(ll, h, req_activate); for (size_t i = 0; i < vector_size(h->wm->wayland.v_toplevels); ++i) { LunarWM_Toplevel *tl = h->wm->wayland.v_toplevels[i]; if (tl->is_xwayland && tl->u.xwl == h->xwl) { LunarWM_Toplevel_focus(tl); break; } } } 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) { (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->req_activate.link.prev) wl_list_remove(&xh->req_activate.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); remove_windows_for_tl(wm, tl); free(tl); break; } } focus_fallback(wm); free(xh); } static bool xwl_wants_focus(struct wlr_xwayland_surface *x) { if (!x) return false; if (!x->surface) return false; if (x->override_redirect && !wlr_xwayland_surface_override_redirect_wants_focus(x)) { return false; } if (x->withdrawn || x->minimized) return false; return true; } static void focus_fallback(LunarWM *wm) { if (!wm || !wm->wayland.seat) return; for (ssize_t i = (ssize_t)vector_size(wm->wayland.v_toplevels) - 1; i >= 0; --i) { LunarWM_Toplevel *cand = wm->wayland.v_toplevels[i]; if (!cand || !cand->surface) continue; if (cand->is_xwayland) { if (!xwl_wants_focus(cand->u.xwl)) continue; } LunarWM_Toplevel_focus(cand); wm->wayland.current_focus = (int)i; return; } struct wlr_seat *seat = wm->wayland.seat; struct wlr_surface *prev = seat->keyboard_state.focused_surface; if (prev) { struct wlr_xdg_toplevel *pt; struct wlr_xwayland_surface *px; if ((pt = wlr_xdg_toplevel_try_from_wlr_surface(prev))) wlr_xdg_toplevel_set_activated(pt, false); if ((px = wlr_xwayland_surface_try_from_wlr_surface(prev))) wlr_xwayland_surface_activate(px, false); } wlr_seat_keyboard_clear_focus(seat); wm->wayland.current_focus = -1; } 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->req_activate.notify = xwl_on_request_activate; wl_signal_add(&xwl->events.request_activate, &h->req_activate); 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) return; 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->surface; if (prev_surface == surface) { return; } if (prev_surface) { struct wlr_xdg_toplevel *prev_tl = wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); 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); 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); } } static void Keyboard_modifiers_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), modifiers); if (!kbd->server || !kbd->server->wayland.seat) { return; } wlr_seat_set_keyboard(kbd->server->wayland.seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_modifiers( kbd->server->wayland.seat, &kbd->wlr_keyboard->modifiers); } static void Keyboard_key_notify(struct wl_listener *listener, void *data) { auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), key); if (!kbd->server || !kbd->server->wayland.seat) { return; } auto *server = kbd->server; auto *event = (struct wlr_keyboard_key_event *)data; struct wlr_seat *seat = server->wayland.seat; uint32_t const keycode = event->keycode + 8; xkb_keysym_t const *syms = nullptr; int const nsyms = xkb_state_key_get_syms(kbd->wlr_keyboard->xkb_state, keycode, &syms); xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); bool handled = false; uint32_t const modifiers = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { if (server->wayland.session && keysym >= XKB_KEY_XF86Switch_VT_1 && keysym <= XKB_KEY_XF86Switch_VT_12) { unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; wlr_session_change_vt(server->wayland.session, vt); return; } for (int i = 0; i < server->cman->cfg.keybindings.count; i++) { BindingRef ref = server->cman->cfg.keybindings.items[i]; if (ref.mods_mask == 0 || ref.sym == XKB_KEY_NoSymbol) continue; bool sym_match = false; if (syms && nsyms > 0) { xkb_keysym_t want = xkb_keysym_to_lower(ref.sym); for (int s = 0; s < nsyms; ++s) { if (syms[s] == want) { sym_match = true; break; } if (xkb_keysym_to_lower(syms[s]) == want) { sym_match = true; break; } } } if (((modifiers & ref.mods_mask) == ref.mods_mask) && sym_match) { config_trigger_ref( server->cman->L, &server->cman->cfg, ref.action_ref); handled = true; break; } } } if (!handled) { if (!seat) return; if (!kbd->wlr_keyboard) return; wlr_seat_set_keyboard(seat, kbd->wlr_keyboard); wlr_seat_keyboard_notify_key( seat, event->time_msec, event->keycode, event->state); } } static void Keyboard_destroy_notify(struct wl_listener *listener, void *) { auto *p = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), destroy); wl_list_remove(&p->modifiers.link); wl_list_remove(&p->key.link); wl_list_remove(&p->destroy.link); wl_list_remove(&p->link); free(p); } static inline float wrap_pi(float a) { a = fmodf(a + PI, 2.0f * PI); if (a < 0.0f) a += 2.0f * PI; return a - PI; } static void Pointer_motion_notify(struct wl_listener *listener, void *data) { LunarWM_Pointer *p = wl_container_of(listener, (LunarWM_Pointer *)NULL, motion); if (!p->server || !p->server->wayland.seat) return; struct wlr_pointer_motion_event *ev = data; float dx = (float)ev->delta_x; float dy = (float)ev->delta_y; if (p->server->cman->cfg.input.mouse.invert_x) { dx *= -1; } if (p->server->cman->cfg.input.mouse.invert_y) { dy *= -1; } float const R = p->server->cman->cfg.space.radius; float const g = 0.0005f; float const POLE_GUARD = 6.0f * (float)M_PI / 180.0f; float const MIN_SIN = sinf(POLE_GUARD); SphericalCoord *coord = &p->server->wm.pointer; float theta = coord->theta + dx * g; float phi = coord->phi + dy * g; theta = wrap_pi(theta); float sin_phi = sinf(phi); if (fabsf(sin_phi) < MIN_SIN) { float sign = sin_phi < 0.0f ? -1.0f : 1.0f; phi = asinf(sign * MIN_SIN); } coord->theta = theta; coord->phi = phi; coord->r = R; } static void Pointer_destroy_notify(struct wl_listener *listener, void *) { auto *kbd = wl_container_of(listener, (LunarWM_Pointer *)(NULL), destroy); wl_list_remove(&kbd->motion.link); wl_list_remove(&kbd->destroy.link); wl_list_remove(&kbd->link); free(kbd); } static void new_input_listener_notify(struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_input_listener); auto *dev = (struct wlr_input_device *)data; if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(dev); LunarWM_Keyboard *keyboard = calloc(1, sizeof(*keyboard)); keyboard->server = wm; keyboard->wlr_keyboard = wlr_keyboard; struct xkb_rule_names const rule_names = { .options = wm->cman->cfg.input.keyboard.xkb_options, }; wlr_log(LOG_INFO, "xkb_options=%s", wm->cman->cfg.input.keyboard.xkb_options); struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_keymap_new_from_names( context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(wlr_keyboard, keymap); xkb_keymap_unref(keymap); xkb_context_unref(context); wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); keyboard->modifiers.notify = Keyboard_modifiers_notify; wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); keyboard->key.notify = Keyboard_key_notify; wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); keyboard->destroy.notify = Keyboard_destroy_notify; wl_signal_add(&dev->events.destroy, &keyboard->destroy); wlr_seat_set_keyboard(wm->wayland.seat, keyboard->wlr_keyboard); wl_list_insert(&wm->wayland.keyboards, &keyboard->link); } else if (dev->type == WLR_INPUT_DEVICE_POINTER) { struct wlr_pointer *wlr_pointer = wlr_pointer_from_input_device(dev); LunarWM_Pointer *pointer = calloc(1, sizeof(*pointer)); pointer->server = wm; pointer->wlr_pointer = wlr_pointer; pointer->destroy.notify = Pointer_destroy_notify; wl_signal_add(&dev->events.destroy, &pointer->destroy); pointer->motion.notify = Pointer_motion_notify; wl_signal_add(&wlr_pointer->events.motion, &pointer->motion); wl_list_insert(&wm->wayland.pointers, &pointer->link); } uint32_t caps = WL_SEAT_CAPABILITY_POINTER; if (!wl_list_empty(&wm->wayland.keyboards)) { caps |= WL_SEAT_CAPABILITY_KEYBOARD; } assert(wm->wayland.seat); wlr_seat_set_capabilities(wm->wayland.seat, caps); } static void new_xdg_popup_listener_notify(struct wl_listener *, void *) { } static void new_xdg_toplevel_listener_notify( struct wl_listener *listener, void *data) { LunarWM *wm = wl_container_of(listener, wm, wayland.new_xdg_toplevel_listener); auto *xdg_tl = (struct wlr_xdg_toplevel *)data; LunarWM_Toplevel *tl = (LunarWM_Toplevel *)calloc(1, sizeof(*tl)); if (!tl) { wlr_log(WLR_ERROR, "oom"); return; } if (LunarWM_Toplevel_init_xdg(tl, wm, xdg_tl)) { vector_add(&wm->wayland.v_toplevels, tl); LunarWM_Window window = { .tl = tl, .coord = get_forward_spherical_with_nearest( wm->renderer.camera.target, wm->cman->cfg.space.radius), }; vector_add( &wm->wm.workspaces[wm->wm.active_workspace].v_windows, window); } else { wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); free(tl); } } struct vo_client_res { struct wl_resource *res; struct wl_list link; }; static void vo_send_initial(struct virtual_output *vo, struct wl_resource *res) { wl_output_send_geometry(res, vo->x, vo->y, vo->phys_w_mm, vo->phys_h_mm, vo->subpixel, vo->make, vo->model, vo->transform); uint32_t flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; wl_output_send_mode(res, flags, vo->width, vo->height, vo->refresh_mhz); if (wl_resource_get_version(res) >= 2) wl_output_send_scale(res, vo->scale); if (wl_resource_get_version(res) >= 4) { wl_output_send_name(res, vo->name); wl_output_send_description(res, vo->desc); } if (wl_resource_get_version(res) >= 2) wl_output_send_done(res); } static void vo_resource_destroy(struct wl_resource *res) { struct vo_client_res *cr = wl_resource_get_user_data(res); if (!cr) return; wl_list_remove(&cr->link); free(cr); } static void output_release(struct wl_client *client, struct wl_resource *res) { (void)client; wl_resource_destroy(res); } static const struct wl_output_interface output_impl = { .release = output_release, }; static void vo_bind( struct wl_client *client, void *data, uint32_t version, uint32_t id) { struct virtual_output *vo = data; struct wl_resource *res = wl_resource_create(client, &wl_output_interface, version, id); if (!res) return; struct vo_client_res *cr = calloc(1, sizeof(*cr)); if (!cr) { wl_resource_destroy(res); return; } cr->res = res; wl_resource_set_implementation(res, &output_impl, cr, vo_resource_destroy); wl_list_insert(&vo->clients, &cr->link); vo_send_initial(vo, res); } static void vo_broadcast_mode(struct virtual_output *vo) { struct vo_client_res *cr; wl_list_for_each(cr, &vo->clients, link) { struct wl_resource *res = cr->res; uint32_t flags = WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; wl_output_send_mode(res, flags, vo->width, vo->height, vo->refresh_mhz); if (wl_resource_get_version(res) >= 2) wl_output_send_done(res); } } static void vo_broadcast_scale(struct virtual_output *vo) { struct vo_client_res *cr; wl_list_for_each(cr, &vo->clients, link) { struct wl_resource *res = cr->res; if (wl_resource_get_version(res) >= 2) { wl_output_send_scale(res, vo->scale); wl_output_send_done(res); } } } static void vo_destroy(struct virtual_output *vo) { struct vo_client_res *cr, *tmp; wl_list_for_each_safe(cr, tmp, &vo->clients, link) { wl_resource_destroy(cr->res); } if (vo->global) { wl_global_destroy(vo->global); vo->global = NULL; } free(vo); } static struct virtual_output *vo_create(struct wl_display *display, char const *name, char const *desc, int32_t w, int32_t h, int32_t refresh_mhz, int32_t scale, char const *make, char const *model) { struct virtual_output *vo = calloc(1, sizeof *vo); if (!vo) return NULL; vo->display = display; wl_list_init(&vo->clients); vo->x = 0; vo->y = 0; vo->phys_w_mm = 0; vo->phys_h_mm = 0; vo->width = w; vo->height = h; vo->refresh_mhz = refresh_mhz; vo->scale = scale > 0 ? scale : 1; vo->subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; vo->transform = WL_OUTPUT_TRANSFORM_NORMAL; vo->make = make ? make : "Lunar"; vo->model = model ? model : "Virtual"; vo->name = name; vo->desc = desc ? desc : name; vo->global = wl_global_create(display, &wl_output_interface, 4, vo, vo_bind); if (!vo->global) { free(vo); return NULL; } return vo; } static void setup_xwayland(LunarWM *this) { 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); } bool LunarWM_wayland_init(LunarWM *this) { wlr_log_init(WLR_DEBUG, nullptr); this->wayland.v_toplevels = vector_create(); this->wayland.display = wl_display_create(); if (this->wayland.display == nullptr) { return false; } this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); if (this->wayland.event_loop == nullptr) { wlr_log(WLR_ERROR, "Failed to get wayland event loop"); return false; } this->wayland.backend = wlr_backend_autocreate(this->wayland.event_loop, nullptr); if (this->wayland.backend == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots backend"); return false; } setenv("WLR_RENDERER", "gles2", 1); this->wayland.renderer = wlr_renderer_autocreate(this->wayland.backend); if (this->wayland.renderer == nullptr) { wlr_log(WLR_ERROR, "Failed to create wlroots renderer"); return false; } this->wayland.session = wlr_session_create(this->wayland.event_loop); if (this->wayland.session == nullptr) { wlr_log(WLR_ERROR, "Failed to create session"); return false; } this->wayland.egl = wlr_gles2_renderer_get_egl(this->wayland.renderer); if (this->wayland.egl == nullptr) { wlr_log(WLR_ERROR, "Failed to get egl information from renderer"); return false; } this->wayland.egl_display = wlr_egl_get_display(this->wayland.egl); this->wayland.egl_context = wlr_egl_get_context(this->wayland.egl); this->wayland.egl_config = EGL_NO_CONFIG_KHR; if (!wlr_renderer_init_wl_display( this->wayland.renderer, this->wayland.display)) { wlr_log( WLR_ERROR, "Failed to initialize renderer with wayland display"); return false; } this->wayland.allocator = wlr_allocator_autocreate( this->wayland.backend, this->wayland.renderer); if (!this->wayland.allocator) { wlr_log(WLR_ERROR, "Failed to create allocator"); return false; } this->wayland.compositor = wlr_compositor_create( this->wayland.display, 5, this->wayland.renderer); if (!this->wayland.compositor) { wlr_log(WLR_ERROR, "Failed to create compositor"); return false; } this->wayland.subcompositor = wlr_subcompositor_create(this->wayland.display); if (!this->wayland.subcompositor) { wlr_log(WLR_ERROR, "Failed to create subcompositor"); return false; } this->wayland.data_device_manager = wlr_data_device_manager_create(this->wayland.display); if (!this->wayland.data_device_manager) { wlr_log(WLR_ERROR, "Failed to create data device manager"); return false; } this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); if (!this->wayland.xdg_shell) { wlr_log(WLR_ERROR, "Failed to create xdg shell"); return false; } this->wayland.new_xdg_toplevel_listener.notify = new_xdg_toplevel_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_toplevel, &this->wayland.new_xdg_toplevel_listener); this->wayland.new_xdg_popup_listener.notify = new_xdg_popup_listener_notify; wl_signal_add(&this->wayland.xdg_shell->events.new_popup, &this->wayland.new_xdg_popup_listener); this->wayland.seat = wlr_seat_create(this->wayland.display, "seat0"); if (!this->wayland.seat) { wlr_log(WLR_ERROR, "Failed to create seat"); return false; } wl_list_init(&this->wayland.keyboards); wl_list_init(&this->wayland.pointers); this->wayland.new_input_listener.notify = new_input_listener_notify; wl_signal_add(&this->wayland.backend->events.new_input, &this->wayland.new_input_listener); this->wayland.xwayland = wlr_xwayland_create( this->wayland.display, this->wayland.compositor, false); if (!this->wayland.xwayland) { wlr_log(WLR_ERROR, "Failed to start XWayland"); return false; } setup_xwayland(this); this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); return true; } void LunarWM_wayland_cleanup(LunarWM *this) { if (this->wayland.custom_out_virtual) { vo_destroy(this->wayland.custom_out_virtual); this->wayland.custom_out_virtual = NULL; } if (this->wayland.custom_out_hud) { vo_destroy(this->wayland.custom_out_hud); this->wayland.custom_out_hud = NULL; } if (this->wayland.xwayland) { if (this->wayland.xwayland_new_surface.link.prev || this->wayland.xwayland_new_surface.link.next) { wl_list_remove(&this->wayland.xwayland_new_surface.link); this->wayland.xwayland_new_surface.notify = NULL; } if (this->wayland.xwayland_ready.link.prev || this->wayland.xwayland_ready.link.next) { wl_list_remove(&this->wayland.xwayland_ready.link); this->wayland.xwayland_ready.notify = NULL; } wlr_xwayland_destroy(this->wayland.xwayland); this->wayland.xwayland = NULL; } if (this->wayland.new_xdg_toplevel_listener.link.prev || this->wayland.new_xdg_toplevel_listener.link.next) { wl_list_remove(&this->wayland.new_xdg_toplevel_listener.link); this->wayland.new_xdg_toplevel_listener.notify = NULL; } if (this->wayland.new_xdg_popup_listener.link.prev || this->wayland.new_xdg_popup_listener.link.next) { wl_list_remove(&this->wayland.new_xdg_popup_listener.link); this->wayland.new_xdg_popup_listener.notify = NULL; } if (this->wayland.new_input_listener.link.prev || this->wayland.new_input_listener.link.next) { wl_list_remove(&this->wayland.new_input_listener.link); this->wayland.new_input_listener.notify = NULL; } if (this->wayland.v_toplevels) { for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); ++i) { if (this->wayland.v_toplevels[i]) { LunarWM_Toplevel_destroy(this->wayland.v_toplevels[i]); free(this->wayland.v_toplevels[i]); } } vector_free(this->wayland.v_toplevels); this->wayland.v_toplevels = NULL; } if (this->wayland.backend) { wlr_backend_destroy(this->wayland.backend); this->wayland.backend = NULL; } if (this->wayland.seat) { wlr_seat_destroy(this->wayland.seat); this->wayland.seat = NULL; } if (this->wayland.allocator) { wlr_allocator_destroy(this->wayland.allocator); this->wayland.allocator = NULL; } if (this->wayland.renderer) { wlr_renderer_destroy(this->wayland.renderer); this->wayland.renderer = NULL; } if (this->wayland.display) { wl_display_destroy(this->wayland.display); this->wayland.display = NULL; } } void LunarWM_wayland_update_virtual_outputs( LunarWM *wm, int virtual_width, int virtual_height, int hud_size) { if (wm->wayland.custom_out_virtual) { vo_destroy(wm->wayland.custom_out_virtual); wm->wayland.custom_out_virtual = NULL; } if (wm->wayland.custom_out_hud) { vo_destroy(wm->wayland.custom_out_hud); wm->wayland.custom_out_hud = NULL; } wm->wayland.custom_out_virtual = vo_create(wm->wayland.display, "Virtual", "Virtual output", virtual_width, virtual_height, 60000, 1, "LunarWM", "Virtual"); wm->wayland.custom_out_hud = vo_create(wm->wayland.display, "HUD", "HUD output", hud_size, hud_size, 60000, 1, "LunarWM", "HUD"); }