From 28a9f5422780549c550f29b074cbb8f95657cab7 Mon Sep 17 00:00:00 2001 From: Slendi Date: Mon, 29 Sep 2025 18:51:43 +0300 Subject: [PATCH] Organization, render cursor above all else Signed-off-by: Slendi --- .direnv/flake-profile | 1 + .direnv/flake-profile-1-link | 1 + .envrc | 2 + CMakeLists.txt | 6 +- lunarwm/init.lua | 3 + src/Config.c | 16 + src/Config.h | 4 + src/LunarWM.c | 3127 ---------------------------------- src/LunarWM.h | 282 +-- src/LunarWM_core.c | 408 +++++ src/LunarWM_core.h | 14 + src/LunarWM_render.c | 662 +++++++ src/LunarWM_render.h | 11 + src/LunarWM_types.h | 272 +++ src/LunarWM_wayland.c | 1250 ++++++++++++++ src/LunarWM_wayland.h | 11 + src/LunarWM_xr.c | 824 +++++++++ src/LunarWM_xr.h | 13 + 18 files changed, 3499 insertions(+), 3408 deletions(-) create mode 120000 .direnv/flake-profile create mode 120000 .direnv/flake-profile-1-link create mode 100644 .envrc delete mode 100644 src/LunarWM.c create mode 100644 src/LunarWM_core.c create mode 100644 src/LunarWM_core.h create mode 100644 src/LunarWM_render.c create mode 100644 src/LunarWM_render.h create mode 100644 src/LunarWM_types.h create mode 100644 src/LunarWM_wayland.c create mode 100644 src/LunarWM_wayland.h create mode 100644 src/LunarWM_xr.c create mode 100644 src/LunarWM_xr.h diff --git a/.direnv/flake-profile b/.direnv/flake-profile new file mode 120000 index 0000000..0c05709 --- /dev/null +++ b/.direnv/flake-profile @@ -0,0 +1 @@ +flake-profile-1-link \ No newline at end of file diff --git a/.direnv/flake-profile-1-link b/.direnv/flake-profile-1-link new file mode 120000 index 0000000..aba1e2a --- /dev/null +++ b/.direnv/flake-profile-1-link @@ -0,0 +1 @@ +/nix/store/czy1f5v0c5i86rm2msxlbq9n8s5lhiv0-nix-shell-env \ No newline at end of file diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..e3fecb3 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +use flake + diff --git a/CMakeLists.txt b/CMakeLists.txt index 75c8dd5..8722bcb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,10 @@ target_sources(${PROJECT_NAME} PUBLIC src/RayExt.c src/Config.c - src/LunarWM.c + src/LunarWM_core.c + src/LunarWM_wayland.c + src/LunarWM_xr.c + src/LunarWM_render.c src/main.c ) target_link_libraries(${PROJECT_NAME} PUBLIC @@ -120,4 +123,3 @@ target_link_libraries(${PROJECT_NAME} PRIVATE xdg_shell) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR} ) - diff --git a/lunarwm/init.lua b/lunarwm/init.lua index 970fe56..c0043c0 100644 --- a/lunarwm/init.lua +++ b/lunarwm/init.lua @@ -7,6 +7,9 @@ return { keyboard = { xkb_options = { "altwin:swap_lalt_lwin" }, }, + mouse = { + invert_y = false, + }, }, keybindings = { { bind = main("Shift-Q"), action = lunar.quit_compositor }, diff --git a/src/Config.c b/src/Config.c index 6e72f2c..351ecd9 100644 --- a/src/Config.c +++ b/src/Config.c @@ -166,6 +166,8 @@ int config_load_ref(lua_State *L, int idx, Config *out) out->displays.virtual.resolution = (Vector2) { 2560, 1440 }; out->xwayland.enabled = true; out->xwayland.lazy = false; + out->input.mouse.invert_x = false; + out->input.mouse.invert_y = false; // ====== END DEFAULTS ====== int cfg_abs = lua_absindex(L, idx); @@ -250,6 +252,20 @@ int config_load_ref(lua_State *L, int idx, Config *out) lua_pop(L, 1); } lua_pop(L, 1); + + lua_getfield(L, -1, "mouse"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "invert_x"); + if (lua_isboolean(L, -1)) + out->input.mouse.invert_x = lua_toboolean(L, -1); + lua_pop(L, 1); + + lua_getfield(L, -1, "invert_y"); + if (lua_isboolean(L, -1)) + out->input.mouse.invert_y = lua_toboolean(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); } lua_pop(L, 1); diff --git a/src/Config.h b/src/Config.h index 6f7a34a..6bcad21 100644 --- a/src/Config.h +++ b/src/Config.h @@ -19,6 +19,10 @@ typedef struct { struct { char *xkb_options; } keyboard; + struct { + bool invert_x; + bool invert_y; + } mouse; } input; struct { diff --git a/src/LunarWM.c b/src/LunarWM.c deleted file mode 100644 index aec7e77..0000000 --- a/src/LunarWM.c +++ /dev/null @@ -1,3127 +0,0 @@ -#include "LunarWM.h" - -#include "RayExt.h" -#include "common.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include "vec.h" - -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); -} - -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 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); -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; - - // detach per-surface listeners/resources - 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; - - // If the thing that unmapped was focused, pick a fallback - 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) -{ - 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) -{ - 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_request_activate(struct wl_listener *ll, 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) -{ - 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 keysym_name(xkb_keysym_t sym, char *buf, size_t bufsz) -{ - if (bufsz == 0) - return; - buf[0] = 0; - - char tmp[128] = { 0 }; - int n = xkb_keysym_get_name(sym, tmp, sizeof tmp); - if (n <= 0) { - snprintf(buf, bufsz, "Unknown"); - return; - } - - if (strlen(tmp) == 1) { - buf[0] = (char)toupper((unsigned char)tmp[0]); - buf[1] = 0; - } else { - snprintf(buf, bufsz, "%s", tmp); - } -} - -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 const dx = (float)ev->delta_x; - float const dy = -(float)ev->delta_y; - float const R = p->server->cman->cfg.space.radius; - float const g = 0.0005f; // meters per pixel - - float const POLE_GUARD = 6.0f * (float)M_PI / 180.0f; - float const MIN_SIN = sinf(POLE_GUARD); - - float phi = p->server->wm.pointer.phi; - float theta = p->server->wm.pointer.theta; - - float dphi = (-g * dy) / R; - - float s = fmaxf(sinf(phi), MIN_SIN); - float dtheta = (g * dx) / (R * s); - - phi = Clamp(phi + dphi, POLE_GUARD, (float)M_PI - POLE_GUARD); - theta = wrap_pi(theta + dtheta); - - p->server->wm.pointer.phi = phi; - p->server->wm.pointer.theta = theta; - p->server->wm.pointer.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 *) -{ - // FIXME: Add support for popups -} - -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; // vo_client_res.link -}; - -static void vo_send_initial(struct virtual_output *vo, struct wl_resource *res) -{ - // wl_output v1..v4 ordering: geometry, mode, scale (v2+), name/desc (v4), - // done (v2+) - 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); -} - -// wl_output requests (only release exists in v3+) -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; - uint32_t ver = version; - if (ver > 4) - ver = 4; // we implement up to v4 - - struct wl_resource *res - = wl_resource_create(client, &wl_output_interface, ver, id); - if (!res) { - wl_client_post_no_memory(client); - return; - } - - struct vo_client_res *cr = calloc(1, sizeof *cr); - if (!cr) { - wl_client_post_no_memory(client); - 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) -{ - // destroy client resources - struct vo_client_res *cr, *tmp; - wl_list_for_each_safe(cr, tmp, &vo->clients, link) - { - wl_resource_destroy(cr->res); // calls vo_resource_destroy - } - 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; // unknown; set if you care - 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 bool init_wayland(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; - wlr_log(WLR_ERROR, "Failed to create wayland display"); - } - - 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 == nullptr) { - wlr_log(WLR_ERROR, "Failed to create wlroots allocator"); - } - - this->wayland.compositor = wlr_compositor_create( - this->wayland.display, 5, this->wayland.renderer); - if (this->wayland.compositor == nullptr) { - wlr_log(WLR_ERROR, "Failed to create wlroots compositor"); - } - - this->wayland.subcompositor - = wlr_subcompositor_create(this->wayland.display); - if (this->wayland.subcompositor == nullptr) { - wlr_log(WLR_ERROR, "Failed to create wlroots subcompositor"); - } - - this->wayland.data_device_manager - = wlr_data_device_manager_create(this->wayland.display); - if (this->wayland.data_device_manager == nullptr) { - wlr_log(WLR_ERROR, "Failed to create wlroots data device manager"); - } - - 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.seat = wlr_seat_create(this->wayland.display, "seat0"); - if (this->wayland.seat == nullptr) { - wlr_log(WLR_ERROR, "Failed to create wlroots seat"); - return false; - } - - this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); - - 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); - - if (this->cman->cfg.xwayland.enabled) { - this->wayland.xwayland = wlr_xwayland_create(this->wayland.display, - this->wayland.compositor, this->cman->cfg.xwayland.lazy); - - 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; -} - -static uint32_t get_swapchain_image( - LunarWM *this, int swapchain_images_i, uint32_t index) -{ - return this->xr.swapchain_images[swapchain_images_i].a_imgs[index].image; -} - -static bool init_xr(LunarWM *this) -{ - XrResult res = XR_SUCCESS; - - XrApplicationInfo app_info = { - .applicationVersion = 1, - .engineVersion = 1, - .apiVersion = XR_CURRENT_API_VERSION, - }; - strncpy((char *)app_info.applicationName, "LunarWM", - XR_MAX_APPLICATION_NAME_SIZE); - strncpy( - (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE); - - char const *instance_extensions[] = { - XR_EXT_DEBUG_UTILS_EXTENSION_NAME, - XR_MNDX_EGL_ENABLE_EXTENSION_NAME, - XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, - XR_EXT_HAND_TRACKING_EXTENSION_NAME, - }; - char const **v_active_instance_extensions = vector_create(); - - uint32_t extension_properties_count = 0; - XrExtensionProperties *extension_properties; - if (xrEnumerateInstanceExtensionProperties( - nullptr, 0, &extension_properties_count, nullptr) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, - "Failed to enumerate OpenXR instance extension properties (count)"); - return false; - } - extension_properties - = malloc(sizeof(*extension_properties) * extension_properties_count); - for (uint32_t i = 0; i < extension_properties_count; ++i) { - extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; - extension_properties[i].next = NULL; - } - - if (xrEnumerateInstanceExtensionProperties(nullptr, - extension_properties_count, &extension_properties_count, - extension_properties) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, - "Failed to enumerate OpenXR instance extension properties"); - return false; - } - - for (size_t i = 0; i < ARRAY_SZ(instance_extensions); i++) { - char const *requested_instance_extension = instance_extensions[i]; - bool found = false; - for (int j = 0; j < extension_properties_count; j++) { - if (strcmp(requested_instance_extension, - extension_properties[j].extensionName) - != 0) { - continue; - } - - vector_add( - &v_active_instance_extensions, requested_instance_extension); - found = true; - break; - } - - if (!found) { - wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s", - requested_instance_extension); - return false; - } - } - - { - XrInstanceCreateInfo const ci = { - .type = XR_TYPE_INSTANCE_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .applicationInfo = app_info, - .enabledApiLayerCount = 0, - .enabledApiLayerNames = nullptr, - .enabledExtensionCount - = (uint32_t)vector_size(v_active_instance_extensions), - .enabledExtensionNames = v_active_instance_extensions, - }; - - XrInstance instance = nullptr; - if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR instance"); - return false; - } - this->xr.instance = instance; - - res = xrGetInstanceProcAddr(this->xr.instance, "xrCreateHandTrackerEXT", - (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT); - if (res != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get proc addr xrCreateHandTrackerEXT"); - return false; - } - res = xrGetInstanceProcAddr(this->xr.instance, - "xrDestroyHandTrackerEXT", - (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT); - if (res != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get proc addr xrDestroyHandTrackerEXT"); - return false; - } - res = xrGetInstanceProcAddr(this->xr.instance, "xrLocateHandJointsEXT", - (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT); - if (res != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to get proc addr xrLocateHandJointsEXT"); - return false; - } - } - - { - XrSystemGetInfo gi = { - .type = XR_TYPE_SYSTEM_GET_INFO, - .next = nullptr, - }; - - XrFormFactor const factors[2] = { - XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, - XR_FORM_FACTOR_HANDHELD_DISPLAY, - }; - for (size_t i = 0; i < ARRAY_SZ(factors); i++) { - auto factor = factors[i]; - - gi.formFactor = factor; - XrSystemId system_id = 0; - if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) { - this->xr.system_id = system_id; - break; - } - } - - if (!this->xr.system_id) { - wlr_log(WLR_ERROR, "Failed to find valid form factor"); - return false; - } - } - - { - XrSystemProperties system_props = { - .type = XR_TYPE_SYSTEM_PROPERTIES, - .next = &this->xr.hand_tracking_system_properties, - }; - res = xrGetSystemProperties( - this->xr.instance, this->xr.system_id, &system_props); - if (res != XR_SUCCESS) { - wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res); - return false; - } - } - - XrGraphicsRequirementsOpenGLESKHR reqs = { - .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, - .next = nullptr, - }; - PFN_xrGetOpenGLESGraphicsRequirementsKHR - xrGetOpenGLESGraphicsRequirementsKHR - = nullptr; - xrGetInstanceProcAddr(this->xr.instance, - "xrGetOpenGLESGraphicsRequirementsKHR", - (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR); - if (xrGetOpenGLESGraphicsRequirementsKHR( - this->xr.instance, this->xr.system_id, &reqs) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); - return false; - } - wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n", - XR_VERSION_MAJOR(reqs.minApiVersionSupported), - XR_VERSION_MINOR(reqs.minApiVersionSupported), - XR_VERSION_PATCH(reqs.minApiVersionSupported), - XR_VERSION_MAJOR(reqs.maxApiVersionSupported), - XR_VERSION_MINOR(reqs.maxApiVersionSupported), - XR_VERSION_PATCH(reqs.maxApiVersionSupported)); - - glEnable(GL_DEBUG_OUTPUT_KHR); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); - - { - XrGraphicsBindingEGLMNDX gbind = { - .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, - .next = nullptr, - .getProcAddress = eglGetProcAddress, - .display = this->wayland.egl_display, - .config = this->wayland.egl_config, - .context = this->wayland.egl_context, - }; - - XrSessionCreateInfo const ci = { - .type = XR_TYPE_SESSION_CREATE_INFO, - .next = &gbind, - .createFlags = 0, - .systemId = this->xr.system_id, - }; - - if (xrCreateSession(this->xr.instance, &ci, &this->xr.session) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR session"); - return false; - } - } - - // Swapchain time! - XrViewConfigurationType *a_view_config_types; - uint32_t view_config_types_count = 0; - { - if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, - 0, &view_config_types_count, nullptr) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, - "Failed to get amount of OpenXR view configurations"); - return false; - } - a_view_config_types - = malloc(sizeof(*a_view_config_types) * view_config_types_count); - for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { - this->xr.a_view_configuration_views[i].type - = XR_TYPE_VIEW_CONFIGURATION_VIEW; - this->xr.a_view_configuration_views[i].next = NULL; - } - if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, - view_config_types_count, &view_config_types_count, - a_view_config_types) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to enumerate OpenXR view configurations"); - return false; - } - } - - { - bool found = false; - for (size_t i = 0; i < view_config_types_count; i++) { - if (a_view_config_types[i] - == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { - found = true; - break; - } - } - if (!found) { - wlr_log(WLR_ERROR, - "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present"); - return false; - } - } - - { - if (xrEnumerateViewConfigurationViews(this->xr.instance, - this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - 0, &this->xr.view_configuration_views_count, nullptr) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, - "Failed to get amount of OpenXR view configuration views"); - return false; - } - this->xr.a_view_configuration_views - = malloc(sizeof(*this->xr.a_view_configuration_views) - * this->xr.view_configuration_views_count); - for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { - this->xr.a_view_configuration_views[i].type - = XR_TYPE_VIEW_CONFIGURATION_VIEW; - this->xr.a_view_configuration_views[i].next = NULL; - } - if (xrEnumerateViewConfigurationViews(this->xr.instance, - this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - this->xr.view_configuration_views_count, - &this->xr.view_configuration_views_count, - this->xr.a_view_configuration_views) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, - "Failed to enumerate OpenXR view configuration views"); - return false; - } - } - - int64_t *a_swapchain_formats; - uint32_t a_swapchain_formats_count = 0; - { - if (xrEnumerateSwapchainFormats( - this->xr.session, 0, &a_swapchain_formats_count, nullptr) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get amount of OpenXR swapchain formats"); - return false; - } - a_swapchain_formats - = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count); - if (xrEnumerateSwapchainFormats(this->xr.session, - a_swapchain_formats_count, &a_swapchain_formats_count, - a_swapchain_formats) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats"); - return false; - } - } - - { - bool found = false; - for (size_t i = 0; i < a_swapchain_formats_count; i++) { - if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) { - found = true; - break; - } - } - if (!found) { - wlr_log( - WLR_ERROR, "Failed to find satisfying depth swapchain format"); - return false; - } - } - - auto const swapchain_format_depth = GL_DEPTH_COMPONENT16; - auto const swapchain_format_color = GL_SRGB8_ALPHA8; - - { - bool found = false; - for (size_t i = 0; i < a_swapchain_formats_count; i++) { - if (a_swapchain_formats[i] == GL_SRGB8_ALPHA8) { - found = true; - break; - } - } - if (!found) { - wlr_log( - WLR_ERROR, "Failed to find satisfying color swapchain format"); - return false; - } - } - - uint32_t const view_count = this->xr.view_configuration_views_count; - uint32_t const eye_w - = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; - uint32_t const eye_h - = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; - uint32_t const buf_w = eye_w * view_count; - - if (!this->xr.swapchains.v_color) { - this->xr.swapchains.v_color = vector_create(); - } - if (!this->xr.swapchains.v_depth) { - this->xr.swapchains.v_depth = vector_create(); - } - - vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {}); - vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {}); - - { - auto *color_sc = &this->xr.swapchains.v_color[0]; - auto *depth_sc = &this->xr.swapchains.v_depth[0]; - auto *vcv = &this->xr.a_view_configuration_views[0]; - - { // Create color swapchain - XrSwapchainCreateInfo const ci = { - .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT - | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, - .format = swapchain_format_color, - .sampleCount = vcv->recommendedSwapchainSampleCount, - .width = buf_w, - .height = eye_h, - .faceCount = 1, - .arraySize = 1, - .mipCount = 1, - }; - color_sc->swapchain_format = ci.format; - - if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain"); - return false; - } - } - - { // Create depth swapchain - XrSwapchainCreateInfo const ci = { - .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT - | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .format = swapchain_format_depth, - .sampleCount = vcv->recommendedSwapchainSampleCount, - .width = buf_w, - .height = eye_h, - .faceCount = 1, - .arraySize = 1, - .mipCount = 1, - }; - depth_sc->swapchain_format = ci.format; - - if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain"); - return false; - } - } - - // Enumerate swapchain images - { // Color - this->xr.swapchain_images[0].handle = color_sc->swapchain; - if (xrEnumerateSwapchainImages(color_sc->swapchain, 0, - &this->xr.swapchain_images[0].a_imgs_count, nullptr) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get Color Swapchain Images count."); - return false; - } - this->xr.swapchain_images[0].a_imgs - = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs) - * this->xr.swapchain_images[0].a_imgs_count); - for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count; - ++i) { - this->xr.swapchain_images[0].a_imgs[i].type - = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; - this->xr.swapchain_images[0].a_imgs[i].next = NULL; - } - if (xrEnumerateSwapchainImages(color_sc->swapchain, - this->xr.swapchain_images[0].a_imgs_count, - &this->xr.swapchain_images[0].a_imgs_count, - (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0] - .a_imgs) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to enumerate color swapchain images."); - return false; - } - } - { // Depth - this->xr.swapchain_images[1].handle = depth_sc->swapchain; - if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0, - &this->xr.swapchain_images[1].a_imgs_count, nullptr) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get depth Swapchain Images count."); - return false; - } - this->xr.swapchain_images[1].a_imgs - = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs) - * this->xr.swapchain_images[1].a_imgs_count); - for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count; - ++i) { - this->xr.swapchain_images[1].a_imgs[i].type - = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; - this->xr.swapchain_images[1].a_imgs[i].next = NULL; - } - if (xrEnumerateSwapchainImages(depth_sc->swapchain, - this->xr.swapchain_images[1].a_imgs_count, - &this->xr.swapchain_images[1].a_imgs_count, - (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1] - .a_imgs) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to enumerate depth swapchain images."); - return false; - } - } - - // Get image views - for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count; - j++) { - GLuint framebuffer = 0; - glGenFramebuffers(1, &framebuffer); - - GLenum const attachment = GL_COLOR_ATTACHMENT0; - - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, - GL_TEXTURE_2D, get_swapchain_image(this, 0, j), 0); - - GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); - if (result != GL_FRAMEBUFFER_COMPLETE) { - wlr_log(WLR_ERROR, "Failed to create color framebuffer"); - return false; - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - if (!color_sc->v_image_views) { - color_sc->v_image_views = vector_create(); - } - vector_add(&color_sc->v_image_views, framebuffer); - } - - for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count; - j++) { - GLuint framebuffer = 0; - glGenFramebuffers(1, &framebuffer); - - GLenum const attachment = GL_DEPTH_ATTACHMENT; - - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, - GL_TEXTURE_2D, get_swapchain_image(this, 1, j), 0); - - GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); - if (result != GL_FRAMEBUFFER_COMPLETE) { - wlr_log(WLR_ERROR, "Failed to create depth framebuffer"); - return false; - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - if (!depth_sc->v_image_views) { - depth_sc->v_image_views = vector_create(); - } - vector_add(&depth_sc->v_image_views, framebuffer); - } - } - - assert(this->xr.instance); - assert(this->xr.system_id); - - XrEnvironmentBlendMode *a_environment_blend_modes; - uint32_t a_environment_blend_modes_count = 0; - { // Get available blend modes - if (xrEnumerateEnvironmentBlendModes(this->xr.instance, - this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - 0, &a_environment_blend_modes_count, nullptr) - != XR_SUCCESS) { - wlr_log( - WLR_ERROR, "Failed to get OpenXR environment blend mode count"); - return false; - } - a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes) - * a_environment_blend_modes_count); - if (xrEnumerateEnvironmentBlendModes(this->xr.instance, - this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - a_environment_blend_modes_count, - &a_environment_blend_modes_count, a_environment_blend_modes) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to get XR environment blend modes"); - return false; - } - } - XrEnvironmentBlendMode const requested_environment_blend_modes[] = { - XR_ENVIRONMENT_BLEND_MODE_OPAQUE, - XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, - XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, - }; - - for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) { - this->xr.environment_blend_mode = requested_environment_blend_modes[i]; - bool found = false; - for (size_t j = 0; j < a_environment_blend_modes_count; j++) { - if (requested_environment_blend_modes[i] - == a_environment_blend_modes[j]) { - found = true; - break; - } - } - if (found) { - break; - } - } - if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) { - wlr_log(WLR_INFO, - "Failed to find a compatible blend mode. Defaulting to " - "XR_ENVIRONMENT_BLEND_MODE_OPAQUE."); - this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; - } - - { // Reference space - XrReferenceSpaceCreateInfo const ci = { - .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, - .next = nullptr, - .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL, - .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, - .position = { 0.0f, 0.0f, 0.0f } }, - }; - - if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); - return false; - } - } - - { // View reference space - XrReferenceSpaceCreateInfo const ci = { - .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, - .next = nullptr, - .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, - .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, - .position = { 0.0f, 0.0f, 0.0f } }, - }; - - if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); - return false; - } - } - - { // Create hand trackers - for (size_t i = 0; i < 2; i++) { - auto *hand = &this->xr.hands[i]; - - XrHandTrackerCreateInfoEXT const ci = { - .type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, - .hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, - .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, - }; - - res = this->xr.CreateHandTrackerEXT( - this->xr.session, &ci, &hand->hand_tracker); - if (res != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res); - return false; - } - } - } - - free(extension_properties); - free(a_view_config_types); - free(a_swapchain_formats); - free(a_environment_blend_modes); - vector_free(v_active_instance_extensions); - - return true; -} - -static void sync_config(LunarWM *this) -{ - if (this->cman->cfg.cubemap) { - Skybox_init(&this->renderer.skybox, this->cman->cfg.cubemap); - } else { - Skybox_destroy(&this->renderer.skybox); - } - - if (IsTextureValid(this->renderer.hud_rt.texture)) { - UnloadTexture(this->renderer.hud_rt.texture); - this->renderer.hud_rt.texture.id = 0; - this->renderer.hud_rt.texture.width = 0; - this->renderer.hud_rt.texture.height = 0; - } - - 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; - } - - int vw = (int)this->cman->cfg.displays.virtual.resolution.x; - int vh = (int)this->cman->cfg.displays.virtual.resolution.y; - int hud = this->cman->cfg.displays.hud.size; - - this->wayland.custom_out_virtual = vo_create(this->wayland.display, - "Virtual", "Virtual output", vw, vh, 60000, 1, "LunarWM", "Virtual"); - this->wayland.custom_out_hud = vo_create(this->wayland.display, "HUD", - "HUD output", hud, hud, 60000, 1, "LunarWM", "HUD"); -} - -extern char **environ; -static int l_exec(lua_State *L) -{ - char const *cmd = luaL_checkstring(L, 1); - - pid_t pid; - char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL }; - - int rc = posix_spawnp(&pid, "sh", NULL, NULL, argv, environ); - if (rc != 0) { - lua_pushnil(L); - lua_pushfstring(L, "posix_spawnp failed: %s", strerror(rc)); - return 2; - } - - lua_pushinteger(L, (lua_Integer)pid); - return 1; -} - -static int l_cycle_next(lua_State *L) -{ - if (vector_size(g_wm.wayland.v_toplevels) == 0) { - lua_pushnil(L); - return 1; - } - - g_wm.wayland.current_focus++; - if (g_wm.wayland.current_focus >= vector_size(g_wm.wayland.v_toplevels)) { - g_wm.wayland.current_focus = 0; - } - - LunarWM_Toplevel *tl = g_wm.wayland.v_toplevels[g_wm.wayland.current_focus]; - LunarWM_Toplevel_focus(tl); - - lua_pushnil(L); - return 1; -} - -static int l_recenter(lua_State *L) -{ - (void)L; - - Vector3 pos = g_wm.renderer.camera.position; - Vector3 fwd - = Vector3Normalize(Vector3Subtract(g_wm.renderer.camera.target, pos)); - - float len_xz = sqrtf(fwd.x * fwd.x + fwd.z * fwd.z); - float yaw = (len_xz > 1e-6f) ? atan2f(fwd.x, fwd.z) : 0.0f; - - Quaternion q_step = QuaternionFromAxisAngle((Vector3) { 0, 1, 0 }, -yaw); - Vector3 t_step = Vector3Negate(Vector3RotateByQuaternion(pos, q_step)); - - Quaternion q_total = QuaternionMultiply(q_step, g_wm.xr.recenter_rot); - Vector3 t_total = Vector3Add( - Vector3RotateByQuaternion(g_wm.xr.recenter_trans, q_step), t_step); - - g_wm.xr.recenter_rot = q_total; - g_wm.xr.recenter_trans = t_total; - g_wm.xr.recenter_active = true; - - lua_pushnil(L); - return 1; -} - -static int l_reload_config(lua_State *L) -{ - ConfigManager *cm = g_wm.cman; - if (!cm) { - lua_pushnil(L); - return 1; - } - - config_manager_reload(cm); - sync_config(&g_wm); - lua_pushnil(L); - return 1; -} - -static int l_quit_compositor(lua_State *L) -{ - LunarWM_terminate(&g_wm); - lua_pushnil(L); - return 1; -} - -bool LunarWM_init(LunarWM *this) -{ - { // Init defaults - memset(this, 0, sizeof(*this)); - this->xr.session = XR_NULL_HANDLE; - this->xr.session_state = XR_SESSION_STATE_UNKNOWN; - this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM; - this->xr.local_space = this->xr.view_space = XR_NULL_HANDLE; - this->xr.hand_tracking_system_properties.type - = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; - this->renderer.camera.position = (Vector3) { 0, 0, 0 }; - this->renderer.camera.target = (Vector3) { 0, 0, 1 }; - this->renderer.camera.up = (Vector3) { 0, 1, 0 }; - this->renderer.camera.fovy = 45; - this->renderer.camera.projection = CAMERA_PERSPECTIVE; - this->xr.recenter_rot = (Quaternion) { 0, 0, 0, 1 }; - this->xr.recenter_trans = (Vector3) { 0, 0, 0 }; - this->xr.recenter_active = false; - this->counter = 0; - } - - this->wm.active_workspace = 0; - for (size_t i = 0; i < ARRAY_SZ(this->wm.workspaces); i++) { - this->wm.workspaces[i].v_windows = vector_create(); - } - - this->cman = config_manager_create(get_config_path()); - assert(this->cman); - - { - lua_State *L = this->cman->L; - - lua_newtable(L); - lua_pushcfunction(L, l_quit_compositor); - lua_setfield(L, -2, "quit_compositor"); - lua_pushcfunction(L, l_reload_config); - lua_setfield(L, -2, "reload_config"); - lua_pushcfunction(L, l_recenter); - lua_setfield(L, -2, "recenter"); - lua_pushcfunction(L, l_cycle_next); - lua_setfield(L, -2, "cycle_next"); - lua_pushcfunction(L, l_exec); - lua_setfield(L, -2, "exec"); - lua_setglobal(L, "lunar"); - - config_manager_reload(this->cman); - } - - if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { - wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); - return false; - } - - if (!init_wayland(this)) { - wlr_log(WLR_ERROR, "Failed to initialize wlroots"); - return false; - } - - auto *draw = eglGetCurrentSurface(EGL_DRAW); - auto *read = eglGetCurrentSurface(EGL_READ); - if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, - EGL_NO_SURFACE, this->wayland.egl_context) - == EGL_FALSE) { - wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); - return false; - } - - if (!init_xr(this)) { - wlr_log(WLR_ERROR, "Failed to initialize OpenXR"); - return false; - } - - wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); - InitWindow(0, 0, ""); - - if (eglMakeCurrent( - this->wayland.egl_display, draw, read, this->wayland.egl_context) - == EGL_FALSE) { - wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); - return false; - } - - sync_config(this); - - this->initialized = true; - - return true; -} - -static void free_swapchain_info(LunarWM_SwapchainInfo *sc) -{ - if (!sc) - return; - if (sc->v_image_views) { - for (size_t i = 0; i < vector_size(sc->v_image_views); ++i) { - GLuint fb = sc->v_image_views[i]; - if (fb) - glDeleteFramebuffers(1, &fb); - } - vector_free(sc->v_image_views); - sc->v_image_views = NULL; - } - if (sc->swapchain) { - xrDestroySwapchain(sc->swapchain); - sc->swapchain = XR_NULL_HANDLE; - } -} - -static void cleanup_xr(LunarWM *this) -{ - for (int i = 0; i < 2; ++i) { - if (this->xr.swapchain_images[i].a_imgs) { - free(this->xr.swapchain_images[i].a_imgs); - this->xr.swapchain_images[i].a_imgs = NULL; - this->xr.swapchain_images[i].a_imgs_count = 0; - } - } - - if (this->xr.swapchains.v_color) { - for (size_t i = 0; i < vector_size(this->xr.swapchains.v_color); ++i) - free_swapchain_info(&this->xr.swapchains.v_color[i]); - vector_free(this->xr.swapchains.v_color); - this->xr.swapchains.v_color = NULL; - } - if (this->xr.swapchains.v_depth) { - for (size_t i = 0; i < vector_size(this->xr.swapchains.v_depth); ++i) - free_swapchain_info(&this->xr.swapchains.v_depth[i]); - vector_free(this->xr.swapchains.v_depth); - this->xr.swapchains.v_depth = NULL; - } - - if (this->renderer.fbo) { - glDeleteFramebuffers(1, &this->renderer.fbo); - this->renderer.fbo = 0; - } - this->renderer.tmp_rt = (RenderTexture2D) { 0 }; - - if (this->xr.view_space) - xrDestroySpace(this->xr.view_space), - this->xr.view_space = XR_NULL_HANDLE; - if (this->xr.local_space) - xrDestroySpace(this->xr.local_space), - this->xr.local_space = XR_NULL_HANDLE; - - for (size_t i = 0; i < 2; ++i) { - if (this->xr.hands[i].hand_tracker && this->xr.DestroyHandTrackerEXT) - this->xr.DestroyHandTrackerEXT(this->xr.hands[i].hand_tracker); - this->xr.hands[i].hand_tracker = XR_NULL_HANDLE; - } - - if (this->xr.a_view_configuration_views) { - free(this->xr.a_view_configuration_views); - this->xr.a_view_configuration_views = NULL; - this->xr.view_configuration_views_count = 0; - } - - if (this->xr.session != XR_NULL_HANDLE) { - xrDestroySession(this->xr.session); - this->xr.session = XR_NULL_HANDLE; - this->xr.session_running = false; - } - - if (this->xr.instance) - xrDestroyInstance(this->xr.instance), - this->xr.instance = XR_NULL_HANDLE; -} - -static void cleanup_wayland(LunarWM *this) -{ - assert(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; - } -} - -static void cleanup_lua_cfg(LunarWM *this) -{ - if (this->cman) { - config_manager_destroy(this->cman); - this->cman = NULL; - } -} - -static void cleanup_raylib_egl(LunarWM *this) -{ - if (IsWindowReady()) { - CloseWindow(); - } -} - -void LunarWM_destroy(LunarWM *this) -{ - if (!this) - return; - - eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - this->wayland.egl_context); - - cleanup_xr(this); - cleanup_raylib_egl(this); - cleanup_wayland(this); - cleanup_lua_cfg(this); - - memset(this, 0, sizeof(*this)); -} - -void LunarWM_terminate(LunarWM *this) -{ - wlr_log(WLR_INFO, "Stopping compositor"); - this->running = false; -} - -static void poll_events_xr(LunarWM *this) -{ - XrEventDataBuffer event_data = { - .type = XR_TYPE_EVENT_DATA_BUFFER, - }; - - while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) { - switch (event_data.type) { - case XR_TYPE_EVENT_DATA_EVENTS_LOST: { - auto *el = (XrEventDataEventsLost *)&event_data; - wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); - break; - } - case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { - auto *ilp = (XrEventDataInstanceLossPending *)&event_data; - wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", - ilp->lossTime); - this->xr.session_running = false; - this->running = false; - break; - } - case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { - auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data; - wlr_log(WLR_INFO, - "OPENXR: Interaction Profile changed for Session: " - "%p", - ipc->session); - if (ipc->session != this->xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataInteractionProfileChanged for " - "unknown Session"); - break; - } - break; - } - case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { - auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data; - wlr_log(WLR_INFO, - "OPENXR: Reference Space Change pending for " - "Session: %p", - scp->session); - if (scp->session != this->xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataReferenceSpaceChangePending for " - "unknown " - "Session"); - break; - } - break; - } - case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { - auto *sc = (XrEventDataSessionStateChanged *)&event_data; - if (sc->session != this->xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataSessionStateChanged for unknown " - "Session"); - break; - } - - if (sc->state == XR_SESSION_STATE_READY) { - XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO }; - bi.primaryViewConfigurationType - = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - bi.primaryViewConfigurationType - = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to begin session"); - } else { - this->xr.session_running = true; - } - } - if (sc->state == XR_SESSION_STATE_STOPPING) { - if (xrEndSession(this->xr.session) != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to end session"); - } - this->xr.session_running = false; - } - if (sc->state == XR_SESSION_STATE_EXITING) { - this->xr.session_running = false; - this->running = false; - } - if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { - this->xr.session_running = false; - this->running = false; - } - this->xr.session_state = sc->state; - break; - } - default: { - break; - } - } - } -} - -bool acquire_wait(XrSwapchain sc, uint32_t *idx) -{ - XrSwapchainImageAcquireInfo ai - = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL }; - if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) { - return false; - } - - XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, - .next = NULL, - .timeout = XR_INFINITE_DURATION }; - return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; -} - -static inline Matrix xr_matrix(XrPosef const pose) -{ - Matrix const translation - = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z); - Matrix const rotation = QuaternionToMatrix((Quaternion) { - pose.orientation.x, - pose.orientation.y, - pose.orientation.z, - pose.orientation.w, - }); - return MatrixMultiply(rotation, translation); -} - -static inline Matrix xr_projection_matrix(XrFovf const fov) -{ - static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); - - Matrix matrix = {}; - - auto const near = (float)RL_CULL_DISTANCE_NEAR; - auto const far = (float)RL_CULL_DISTANCE_FAR; - - float const tan_angle_left = tanf(fov.angleLeft); - float const tan_angle_right = tanf(fov.angleRight); - - float const tan_angle_down = tanf(fov.angleDown); - float const tan_angle_up = tanf(fov.angleUp); - - float const tan_angle_width = tan_angle_right - tan_angle_left; - float const tan_angle_height = tan_angle_up - tan_angle_down; - - matrix.m0 = 2 / tan_angle_width; - matrix.m4 = 0; - matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width; - matrix.m12 = 0; - - matrix.m1 = 0; - matrix.m5 = 2 / tan_angle_height; - matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height; - matrix.m13 = 0; - - matrix.m2 = 0; - matrix.m6 = 0; - matrix.m10 = -(far + near) / (far - near); - matrix.m14 = -(far * (near + near)) / (far - near); - - matrix.m3 = 0; - matrix.m7 = 0; - matrix.m11 = -1; - matrix.m15 = 0; - - return matrix; -} - -static void DrawBillboardNoShear( - Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint) -{ - Rectangle const src = { 0, 0, tex.width, tex.height }; - - Vector2 const size = { scale * fabsf(src.width / src.height), -scale }; - Vector2 const origin = { size.x * 0.5f, size.y * 0.5f }; - - DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint); -} - -void DrawTextureCyl( - Texture2D tex, Vector3 center, float radius, float scale, bool y_flip) -{ - if (!tex.id || scale <= 0.0f || radius == 0.0f) - return; - - float r = fabsf(radius); - float arc_len = (float)tex.width * scale; // arc length in world units - float theta = arc_len / r; // radians across the panel - float half_t = 0.5f * theta; - float half_h = 0.5f * (float)tex.height * scale; - - // mid-angle around Y so the segment's middle sits at 'center' - float a0 = atan2f(center.x, center.z); - - // shift so the cylinder surface midpoint matches 'center' - Vector3 mid_ref = (Vector3) { sinf(a0) * r, center.y, cosf(a0) * r }; - Vector3 delta = Vector3Subtract(center, mid_ref); - - // tessellation: about 3° per slice (min 8) - int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f)); - if (slices > 1024) - slices = 1024; - - float vt = y_flip ? 1.0f : 0.0f; - float vb = y_flip ? 0.0f : 1.0f; - - rlDrawRenderBatchActive(); // flush any prior state - rlSetTexture(tex.id); - rlDisableBackfaceCulling(); - rlColor4ub(255, 255, 255, 255); - rlBegin(RL_QUADS); - - for (int i = 0; i < slices; ++i) { - float u0 = (float)i / (float)slices; - float u1 = (float)(i + 1) / (float)slices; - - float aL = a0 - half_t + theta * u0; - float aR = a0 - half_t + theta * u1; - - Vector3 nL = (Vector3) { sinf(aL), 0.0f, cosf(aL) }; - Vector3 nR = (Vector3) { sinf(aR), 0.0f, cosf(aR) }; - if (radius < 0.0f) { - nL = Vector3Negate(nL); - nR = Vector3Negate(nR); - } - - Vector3 pLT = Vector3Add( - (Vector3) { nL.x * r, center.y + half_h, nL.z * r }, delta); - Vector3 pLB = Vector3Add( - (Vector3) { nL.x * r, center.y - half_h, nL.z * r }, delta); - Vector3 pRT = Vector3Add( - (Vector3) { nR.x * r, center.y + half_h, nR.z * r }, delta); - Vector3 pRB = Vector3Add( - (Vector3) { nR.x * r, center.y - half_h, nR.z * r }, delta); - - // match your flat-quad U flip (so WL textures look correct) - float U0 = 1.0f - u0; - float U1 = 1.0f - u1; - - // one normal per-vertex (simple cylindrical) - rlNormal3f(nL.x, nL.y, nL.z); - rlTexCoord2f(U0, vt); - rlVertex3f(pLT.x, pLT.y, pLT.z); - rlNormal3f(nR.x, nR.y, nR.z); - rlTexCoord2f(U1, vt); - rlVertex3f(pRT.x, pRT.y, pRT.z); - rlNormal3f(nR.x, nR.y, nR.z); - rlTexCoord2f(U1, vb); - rlVertex3f(pRB.x, pRB.y, pRB.z); - rlNormal3f(nL.x, nL.y, nL.z); - rlTexCoord2f(U0, vb); - rlVertex3f(pLB.x, pLB.y, pLB.z); - } - - rlEnd(); - rlSetTexture(0); - rlEnableBackfaceCulling(); -} - -static void DrawTextureCyl2(Texture2D tex, Vector3 sphere_center, - SphericalCoord coord, float rad, float scale, bool y_flip) -{ - if (!tex.id || scale <= 0.0f || rad == 0.0f) - return; - - // midpoint on the sphere where the panel should sit (its center) - Vector3 fwd = SphericalToVector3(coord); - if (Vector3Length(fwd) < 1e-6f) - fwd = (Vector3) { 0, 0, 1 }; - fwd = Vector3Normalize(fwd); - - // build a local tangent frame at that point (right, up, forward) - Vector3 worldUp = (Vector3) { 0, 1, 0 }; - if (fabsf(Vector3DotProduct(worldUp, fwd)) > 0.99f) - worldUp = (Vector3) { 1, 0, 0 }; - Vector3 right = Vector3Normalize(Vector3CrossProduct(worldUp, fwd)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(fwd, right)); - - float r = fabsf(rad); - float arc_len = (float)tex.width * scale; // world units across - float theta = arc_len / r; // total horizontal FOV in radians - float half_t = 0.5f * theta; - float half_h = 0.5f * (float)tex.height * scale; - - // shift so cylinder's surface midpoint lands exactly at coord.r from - // sphere_center - Vector3 delta = Vector3Add(sphere_center, - Vector3Add(Vector3Scale(fwd, coord.r - r), (Vector3) { 0, 0, 0 })); - - // tessellation: about 3° per slice (min 8, max 1024) - int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f)); - if (slices > 1024) - slices = 1024; - - float vt = y_flip ? 1.0f : 0.0f; - float vb = y_flip ? 0.0f : 1.0f; - - rlDrawRenderBatchActive(); - rlSetTexture(tex.id); - rlDisableBackfaceCulling(); - rlColor4ub(255, 255, 255, 255); - rlBegin(RL_QUADS); - - for (int i = 0; i < slices; ++i) { - float u0 = (float)i / (float)slices; - float u1 = (float)(i + 1) / (float)slices; - - float aL = -half_t + theta * u0; - float aR = -half_t + theta * u1; - - // local outward directions on the cylindrical surface - Vector3 nL = Vector3Add( - Vector3Scale(right, sinf(aL)), Vector3Scale(fwd, cosf(aL))); - Vector3 nR = Vector3Add( - Vector3Scale(right, sinf(aR)), Vector3Scale(fwd, cosf(aR))); - - if (rad < 0.0f) { - nL = Vector3Negate(nL); - nR = Vector3Negate(nR); - } - - // surface points (center band), then top/bottom by +/- up*half_h - Vector3 cL = Vector3Add(delta, Vector3Scale(nL, r)); - Vector3 cR = Vector3Add(delta, Vector3Scale(nR, r)); - - Vector3 pLT = Vector3Add(cL, Vector3Scale(up, half_h)); - Vector3 pLB = Vector3Add(cL, Vector3Scale(up, -half_h)); - Vector3 pRT = Vector3Add(cR, Vector3Scale(up, half_h)); - Vector3 pRB = Vector3Add(cR, Vector3Scale(up, -half_h)); - - // match the original horizontal flip so Wayland textures look correct - float U0 = 1.0f - u0; - float U1 = 1.0f - u1; - - rlNormal3f(nL.x, nL.y, nL.z); - rlTexCoord2f(U0, vt); - rlVertex3f(pLT.x, pLT.y, pLT.z); - - rlNormal3f(nR.x, nR.y, nR.z); - rlTexCoord2f(U1, vt); - rlVertex3f(pRT.x, pRT.y, pRT.z); - - rlNormal3f(nR.x, nR.y, nR.z); - rlTexCoord2f(U1, vb); - rlVertex3f(pRB.x, pRB.y, pRB.z); - - rlNormal3f(nL.x, nL.y, nL.z); - rlTexCoord2f(U0, vb); - rlVertex3f(pLB.x, pLB.y, pLB.z); - } - - rlEnd(); - rlSetTexture(0); - rlEnableBackfaceCulling(); -} - -static inline Vector3 RecenterPoint(LunarWM *wm, Vector3 p) -{ - if (!wm->xr.recenter_active) - return p; - return Vector3Add(Vector3RotateByQuaternion(p, wm->xr.recenter_rot), - wm->xr.recenter_trans); -} - -static inline Quaternion RecenterOrient(LunarWM *wm, Quaternion q) -{ - if (!wm->xr.recenter_active) - return q; - return QuaternionMultiply(wm->xr.recenter_rot, q); -} - -static LunarWM_Toplevel *find_toplevel(LunarWM *this, int id) -{ - for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { - auto *tl = this->wayland.v_toplevels[i]; - if (tl->id == id) - return tl; - } - - return NULL; -} - -void render_hud(LunarWM *this, float /*dt*/, int hud_size) -{ - ClearBackground((Color) { 0, 0, 0, 0 }); - - float const text_size = this->cman->cfg.displays.hud.font_size; - - char const *txt - = TextFormat("WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY")); - auto txt_w = MeasureText(txt, 24); - DrawText( - txt, hud_size / 2 - txt_w / 2, hud_size - text_size, text_size, WHITE); - - txt = TextFormat("DISPLAY=%s", getenv("DISPLAY")); - txt_w = MeasureText(txt, text_size); - DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - text_size * 2, text_size, - WHITE); - - { - time_t t = time(NULL); - struct tm *tm_info = localtime(&t); - - int hours = tm_info->tm_hour; - int minutes = tm_info->tm_min; - txt = TextFormat("%02d:%02d", hours, minutes); - txt_w = MeasureText(txt, 32); - DrawText(txt, hud_size / 2 - txt_w / 2, 0, text_size, WHITE); - } -} - -void render_windows(LunarWM *this, bool alpha_check) -{ - for (size_t i = 0; i - < vector_size(this->wm.workspaces[this->wm.active_workspace].v_windows); - i++) { - auto *window - = &this->wm.workspaces[this->wm.active_workspace].v_windows[i]; - auto *tl = window->tl; - if (!tl || !tl->surface) { - continue; - } - if (tl->gles_texture) { - if (alpha_check && tl->gles_texture->has_alpha) { - continue; - } - float rad = window->coord.r - 0.01f * (float)i; - DrawTextureCyl2(tl->rl_texture, Vector3Zero(), window->coord, rad, - this->cman->cfg.space.window_scale, false); - } - } -} - -void render_3d(LunarWM *this, float /*dt*/) -{ - render_windows(this, true); - - for (int h = 0; h < 2; ++h) { - auto *hand_info = &this->xr.hands[h]; - for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { - auto const *jl = &hand_info->joint_locations[k]; - Vector3 pos = { - jl->pose.position.x, - jl->pose.position.y, - jl->pose.position.z, - }; - pos = RecenterPoint(this, pos); - DrawSphere(pos, jl->radius, (Color) { 255, 0, 0, 255 }); - } - } - - Skybox_draw(this->renderer.skybox, this->renderer.camera.position); - - rlEnableColorBlend(); - rlDisableDepthMask(); // don't write depth - render_windows(this, false); - // TODO: Replace with actual cursor texture. - { // Cursor - Vector3 tip = SphericalToVector3(this->wm.pointer); - - Vector3 n = Vector3Normalize( - Vector3Subtract(this->renderer.camera.position, tip)); - Vector3 up_hint = (Vector3) { 0, 1, 0 }; - if (fabsf(Vector3DotProduct(up_hint, n)) > 0.98f) - up_hint = (Vector3) { 1, 0, 0 }; - Vector3 right = Vector3Normalize(Vector3CrossProduct(up_hint, n)); - Vector3 up = Vector3Normalize(Vector3CrossProduct(n, right)); - Vector3 down = Vector3Negate(up); - - float const s = 0.03f; - - Vector3 v1 = tip; - Vector3 v2 = Vector3Add(tip, Vector3Scale(down, s)); - Vector3 v3 = Vector3Add(tip, Vector3Scale(right, s)); - - Vector3 normal = Vector3CrossProduct( - Vector3Subtract(v2, v1), Vector3Subtract(v3, v1)); - if (Vector3DotProduct(normal, n) < 0.0f) { - Vector3 tmp = v2; - v2 = v3; - v3 = tmp; - } - - DrawTriangle3D(v1, v2, v3, RED); - } - rlEnableDepthMask(); - - if (IsTextureValid(this->renderer.hud_rt.texture)) { - rlDrawRenderBatchActive(); - rlDisableDepthTest(); - - Vector3 camPos = this->renderer.camera.position; - Vector3 camDir = Vector3Normalize( - Vector3Subtract(this->renderer.camera.target, camPos)); - Vector3 up = this->renderer.camera.up; - Vector3 right = Vector3Normalize(Vector3CrossProduct(camDir, up)); - up = Vector3CrossProduct(right, camDir); - - Vector3 center = Vector3Add(camPos, Vector3Scale(camDir, 0.6f)); - - float heightMeters = 0.10f; - - DrawBillboardNoShear(this->renderer.camera, - this->renderer.hud_rt.texture, center, heightMeters * 5, WHITE); - rlDrawRenderBatchActive(); - - rlEnableDepthTest(); - } -} - -static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) -{ - auto const view_count = (uint32_t)this->xr.view_configuration_views_count; - assert(view_count == 2); - - XrView views[2] = {}; - for (int i = 0; i < ARRAY_SZ(views); i++) { - views[i] = (XrView) { - .type = XR_TYPE_VIEW, - }; - } - - XrViewLocateInfo locInfo = { - .type = XR_TYPE_VIEW_LOCATE_INFO, - .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, - .displayTime = info->predicted_display_time, - .space = this->xr.local_space, - }; - - XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; - uint32_t located = 0; - if (xrLocateViews( - this->xr.session, &locInfo, &viewState, view_count, &located, views) - != XR_SUCCESS - || located != view_count) { - wlr_log(WLR_ERROR, "Failed to locate views"); - return false; - } - - // acquire swapchain images - auto *color_sc = &this->xr.swapchains.v_color[0]; - auto *depth_sc = &this->xr.swapchains.v_depth[0]; - - uint32_t col_idx = 0; - uint32_t dep_idx = 0; - if (!acquire_wait(color_sc->swapchain, &col_idx) - || !acquire_wait(depth_sc->swapchain, &dep_idx)) { - wlr_log(WLR_ERROR, "Swap-chain acquire failed"); - return false; - } - - GLuint color_tex = get_swapchain_image(this, 0, col_idx); - GLuint depth_tex = get_swapchain_image(this, 1, dep_idx); - - // build FBO - if (this->renderer.fbo == 0u) { - glGenFramebuffers(1, &this->renderer.fbo); - } - glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo); - rlFramebufferAttach(this->renderer.fbo, color_tex, - RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); - rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, - RL_ATTACHMENT_TEXTURE2D, 0); - assert(rlFramebufferComplete(this->renderer.fbo)); - - uint32_t const eye_w - = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; - 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)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 head_view = MatrixInvert(xr_matrix(headLoc.pose)); - - // per-eye projection + view-offset - 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); - - Matrix const proj_r = xr_projection_matrix(views[0].fov); - Matrix const proj_l = xr_projection_matrix(views[1].fov); - - int const hud_size = this->cman->cfg.displays.hud.size; - if (!IsTextureValid(this->renderer.hud_rt.texture)) { - this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size); - } - - if (IsTextureValid(this->renderer.hud_rt.texture)) { - BeginTextureMode(this->renderer.hud_rt); - { - render_hud(this, dt, hud_size); - } - EndTextureMode(); - } - - // draw - - if (!IsTextureValid(this->renderer.main_rt.texture)) { - this->renderer.main_rt = LoadRenderTexture(eye_w * view_count, eye_h); - } - - BeginTextureMode(this->renderer.main_rt); - - rlEnableStereoRender(); - rlSetMatrixProjectionStereo(proj_r, proj_l); - rlSetMatrixViewOffsetStereo(view_off_r, view_off_l); - - glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h); - rlClearColor(0, 0, 10, 255); - rlClearScreenBuffers(); - - for (int i = 0; i < 1; i++) { - XrTime const time = info->predicted_display_time; - - XrSpaceLocation view_location = { .type = XR_TYPE_SPACE_LOCATION }; - XrResult const result = xrLocateSpace( - this->xr.view_space, this->xr.local_space, time, &view_location); - if (result != XR_SUCCESS) { - break; - } - - if ((view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) - != 0u) { - auto const pos = view_location.pose.position; - this->renderer.camera.position = (Vector3) { pos.x, pos.y, pos.z }; - } - if ((view_location.locationFlags - & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) - != 0u) { - auto const rot = view_location.pose.orientation; - auto const forward - = Vector3RotateByQuaternion((Vector3) { 0, 0, -1 }, - (Quaternion) { rot.x, rot.y, rot.z, rot.w }); - auto const up = Vector3RotateByQuaternion((Vector3) { 0, 1, 0 }, - (Quaternion) { rot.x, rot.y, rot.z, rot.w }); - this->renderer.camera.target - = Vector3Add(this->renderer.camera.position, forward); - this->renderer.camera.up = up; - } - - if (this->xr.recenter_active) { - Vector3 pos = this->renderer.camera.position; - Vector3 fwd = Vector3Normalize( - Vector3Subtract(this->renderer.camera.target, pos)); - Vector3 up = this->renderer.camera.up; - - pos = Vector3Add( - Vector3RotateByQuaternion(pos, this->xr.recenter_rot), - this->xr.recenter_trans); - fwd = Vector3RotateByQuaternion(fwd, this->xr.recenter_rot); - up = Vector3RotateByQuaternion(up, this->xr.recenter_rot); - - this->renderer.camera.position = pos; - this->renderer.camera.target = Vector3Add(pos, fwd); - this->renderer.camera.up = up; - } - } - - BeginMode3D(this->renderer.camera); - { - ClearBackground(RED); - render_3d(this, dt); - } - EndMode3D(); - - rlDisableStereoRender(); - EndTextureMode(); - - if (!IsShaderValid(this->renderer.linear_srgb)) { - static char const linear_srgb[] = { -#embed "../assets/linear_srgb.fs" - , 0 - }; - this->renderer.linear_srgb = LoadShaderFromMemory(NULL, linear_srgb); - } - - BeginTextureMode(this->renderer.tmp_rt); - rlDisableColorBlend(); - ClearBackground(BLACK); - BeginShaderMode(this->renderer.linear_srgb); - DrawTexturePro(this->renderer.main_rt.texture, - (Rectangle) { - 0, - 0, - this->renderer.main_rt.texture.width, - -this->renderer.main_rt.texture.height, - }, - (Rectangle) { - 0, - 0, - this->renderer.tmp_rt.texture.width, - this->renderer.tmp_rt.texture.height, - }, - (Vector2) { 0, 0 }, 0, WHITE); - EndShaderMode(); - EndTextureMode(); - rlEnableColorBlend(); - - // release swapchain images - XrSwapchainImageReleaseInfo const ri - = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; - xrReleaseSwapchainImage(color_sc->swapchain, &ri); - xrReleaseSwapchainImage(depth_sc->swapchain, &ri); - - // fill projection layer - info->layer_projection_views_count = view_count; - - for (uint32_t i = 0; i < view_count; ++i) { - 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 = eye_w, .height = eye_h }; - pv->subImage.imageArrayIndex = 0; - } - - info->layer_projection = (XrCompositionLayerProjection) { - .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, - .layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT - | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, - .space = this->xr.local_space, - .viewCount = view_count, - .views = info->layer_projection_views, - }; - - info->layers_count = 0; - info->layers[info->layers_count++] - = (XrCompositionLayerBaseHeader *)&info->layer_projection; - - if (this->renderer.first_frame) { - l_recenter(this->cman->L); - lua_pop(this->cman->L, 1); - this->renderer.first_frame = false; - this->wm.pointer = get_forward_spherical_with_nearest( - this->renderer.camera.target, this->cman->cfg.space.radius); - } - - return true; -} - -void LunarWM_run(LunarWM *this) -{ - assert(this); - assert(this->initialized); - - this->renderer.first_frame = true; - - if (!wlr_backend_start(this->wayland.backend)) { - wlr_log(WLR_ERROR, "Failed to start backend"); - return; - } - - auto const *socket = wl_display_add_socket_auto(this->wayland.display); - if (socket == nullptr) { - wlr_log(WLR_ERROR, "Failed to add wayland socket to display"); - return; - } - - setenv("WAYLAND_DISPLAY", socket, 1); - wlr_log(LOG_INFO, "Running on WAYLAND_DISPLAY=%s", socket); - - this->running = true; - - struct timespec last, now; - clock_gettime(CLOCK_MONOTONIC, &last); - while (this->running) { - clock_gettime(CLOCK_MONOTONIC, &now); - float dt = (now.tv_sec - last.tv_sec) - + (now.tv_nsec - last.tv_nsec) / 1000000000.0f; - last = now; - - for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { - LunarWM_Toplevel *tl = this->wayland.v_toplevels[i]; - if (tl->surface) { - wlr_surface_send_frame_done(tl->surface, &now); - } - } - - wl_display_flush_clients(this->wayland.display); - wl_event_loop_dispatch(this->wayland.event_loop, 0); - - auto *draw = eglGetCurrentSurface(EGL_DRAW); - auto *read = eglGetCurrentSurface(EGL_READ); - if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, - EGL_NO_SURFACE, this->wayland.egl_context) - == EGL_FALSE) { - wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); - return; - } - - WindowShouldClose(); - BeginDrawing(); - - poll_events_xr(this); - - if (!this->xr.session_running) { - EndDrawing(); - continue; - } - - XrFrameState frame_state = { - .type = XR_TYPE_FRAME_STATE, - }; - XrFrameWaitInfo const frame_wait_info = { - .type = XR_TYPE_FRAME_WAIT_INFO, - .next = nullptr, - }; - if (xrWaitFrame(this->xr.session, &frame_wait_info, &frame_state) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame"); - return; - } - - XrFrameBeginInfo const frame_begin_info = { - .type = XR_TYPE_FRAME_BEGIN_INFO, - .next = nullptr, - }; - XrResult res = xrBeginFrame(this->xr.session, &frame_begin_info); - if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to begin the OpenXR Frame: %d", res); - return; - } - - if (this->xr.hand_tracking_system_properties.supportsHandTracking) { - XrActionStateGetInfo const si = { - .type = XR_TYPE_ACTION_STATE_GET_INFO, - }; - for (size_t i = 0; i < 2; i++) { - LunarWM_Hand *hand = &this->xr.hands[i]; - bool const unobstructed = true; - - XrHandJointsMotionRangeInfoEXT mri = { - .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, - }; - if (unobstructed) { - mri.handJointsMotionRange - = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; - } else { - mri.handJointsMotionRange - = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; - } - - XrHandJointsLocateInfoEXT const li = { - .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, - .next = &mri, - .baseSpace = this->xr.local_space, - .time = frame_state.predictedDisplayTime, - }; - - XrHandJointLocationsEXT hji = { - .type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT, - .jointCount = XR_HAND_JOINT_COUNT_EXT, - .jointLocations = hand->joint_locations, - }; - - if (this->xr.LocateHandJointsEXT(hand->hand_tracker, &li, &hji) - != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to locate hand joints"); - return; - } - } - } - - LunarWM_RenderLayerInfo render_layer_info = { - .predicted_display_time = frame_state.predictedDisplayTime, - .layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, - .layer_projection.next = nullptr, - }; - bool const session_active - = (this->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED - || this->xr.session_state == XR_SESSION_STATE_VISIBLE - || this->xr.session_state == XR_SESSION_STATE_FOCUSED); - if (session_active && (frame_state.shouldRender != 0u)) { - auto rendered = render_layer(this, &render_layer_info, dt); - if (rendered) { - render_layer_info.layers[render_layer_info.layers_count] - = (XrCompositionLayerBaseHeader *)&render_layer_info - .layer_projection; - } - } - - XrFrameEndInfo const frame_end_info = { - .type = XR_TYPE_FRAME_END_INFO, - .displayTime = frame_state.predictedDisplayTime, - .environmentBlendMode = this->xr.environment_blend_mode, - .layerCount = render_layer_info.layers_count, - .layers - = (XrCompositionLayerBaseHeader const **)render_layer_info.layers, - }; - if (xrEndFrame(this->xr.session, &frame_end_info) != XR_SUCCESS) { - wlr_log(WLR_ERROR, "Failed to end OpenXR frame"); - return; - } - - EndDrawing(); - } -} diff --git a/src/LunarWM.h b/src/LunarWM.h index 2d71d78..de19e24 100644 --- a/src/LunarWM.h +++ b/src/LunarWM.h @@ -1,283 +1,7 @@ #ifndef LUNAR_WM_H #define LUNAR_WM_H -#include -#include -#include -#include -#include -#include -#include -#include +#include "LunarWM_types.h" +#include "LunarWM_core.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "Config.h" -#include "RayExt.h" - -struct LunarWM; - -typedef struct { - float r, theta, phi; -} SphericalCoord; - -static inline SphericalCoord Vector3ToSpherical(Vector3 v) -{ - SphericalCoord s; - s.r = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); - if (s.r > 0.0f) { - s.theta = atan2f(v.z, v.x); // azimuth around Y axis - s.phi = acosf(v.y / s.r); // polar angle from Y+ - } else { - s.theta = 0.0f; - s.phi = 0.0f; - } - return s; -} - -static inline Vector3 SphericalToVector3(SphericalCoord s) -{ - Vector3 v; - float sin_phi = sinf(s.phi); - v.x = s.r * cosf(s.theta) * sin_phi; - v.y = s.r * cosf(s.phi); - v.z = s.r * sinf(s.theta) * sin_phi; - return v; -} - -typedef struct virtual_output { - struct wl_global *global; - struct wl_display *display; - struct wl_list clients; // vo_client_res.link - - // state we advertise - int32_t x, y; // compositor space; keep 0,0 if not relevant - int32_t phys_w_mm, phys_h_mm; - int32_t width, height; // current mode - int32_t refresh_mhz; // mHz - int32_t scale; - enum wl_output_subpixel subpixel; - enum wl_output_transform transform; - char const *make, *model; - char const *name, *desc; -} LunarWM_VirtualOutput; - -typedef struct { - struct LunarWM *server; - - struct wl_list link; - struct wlr_keyboard *wlr_keyboard; - - struct wl_listener modifiers; - struct wl_listener key; - struct wl_listener destroy; -} LunarWM_Keyboard; - -typedef struct { - struct LunarWM *server; - - struct wl_list link; - struct wlr_pointer *wlr_pointer; - - struct wl_listener motion; - struct wl_listener destroy; -} LunarWM_Pointer; - -typedef struct { - uint32_t id; - - bool is_xwayland; - - struct LunarWM *server; - - struct wl_listener commit; - struct wl_listener destroy; - - struct wl_listener map, unmap; - - union { - struct wlr_xdg_toplevel *xdg; - struct wlr_xwayland_surface *xwl; - } u; - - struct wlr_surface *surface; - struct wlr_texture *texture; - - struct wlr_buffer *locked_buffer; - struct wlr_gles2_texture_attribs attribs; - struct wlr_gles2_texture *gles_texture; - Texture2D rl_texture; -} LunarWM_Toplevel; - -typedef struct { - LunarWM_Toplevel *tl; - SphericalCoord coord; -} LunarWM_Window; - -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); -void LunarWM_Toplevel_focus(LunarWM_Toplevel *this); - -typedef struct { - XrSwapchain swapchain; - int64_t swapchain_format; - GLuint *v_image_views; -} LunarWM_SwapchainInfo; - -typedef struct { - XrSwapchain handle; - XrSwapchainImageOpenGLESKHR *a_imgs; - uint32_t a_imgs_count; -} LunarWM_SwapchainImagesEntry; - -typedef struct { - XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; - XrHandTrackerEXT hand_tracker; -} LunarWM_Hand; - -typedef struct { - XrTime predicted_display_time; - XrCompositionLayerProjection layer_projection; - XrCompositionLayerBaseHeader *layers[10]; - uint32_t layers_count; - XrCompositionLayerProjectionView - layer_projection_views[10]; // Hopefully we dont have more than 10. - uint32_t layer_projection_views_count; -} LunarWM_RenderLayerInfo; - -typedef struct LunarWM { - struct { - struct wl_display *display; - struct wl_event_loop *event_loop; - - struct wlr_backend *backend; - struct wlr_renderer *renderer; - struct wlr_session *session; - - struct wlr_egl *egl; - EGLDisplay egl_display; - EGLContext egl_context; - EGLConfig egl_config; - - struct wlr_allocator *allocator; - struct wlr_compositor *compositor; - struct wlr_subcompositor *subcompositor; - struct wlr_data_device_manager *data_device_manager; - - struct wlr_seat *seat; - struct wl_list keyboards; - struct wl_list pointers; - struct wl_listener new_input_listener; - - struct wlr_xdg_shell *xdg_shell; - struct wl_listener new_xdg_toplevel_listener; - struct wl_listener new_xdg_popup_listener; - - 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_VirtualOutput *custom_out_virtual; - LunarWM_VirtualOutput *custom_out_hud; - - LunarWM_Toplevel **v_toplevels; - int current_focus; - } wayland; - - struct { - XrInstance instance; - XrSystemId system_id; - XrSession session; - XrSessionState session_state; - struct { - LunarWM_SwapchainInfo *v_color; - LunarWM_SwapchainInfo *v_depth; - } swapchains; - LunarWM_SwapchainImagesEntry - swapchain_images[2]; // 0 is color, 1 is depth - XrViewConfigurationView *a_view_configuration_views; - uint32_t view_configuration_views_count; - XrEnvironmentBlendMode environment_blend_mode; - XrSpace local_space, view_space; - LunarWM_Hand hands[2]; - XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties; - - PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT; - PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT; - PFN_xrLocateHandJointsEXT LocateHandJointsEXT; - - Quaternion recenter_rot; - Vector3 recenter_trans; - bool recenter_active; - - bool session_running; - } xr; - - struct { - GLuint fbo; - RenderTexture2D tmp_rt; - RenderTexture2D main_rt; - RenderTexture2D hud_rt; - Camera3D camera; - Shader linear_srgb; - - Skybox skybox; - - bool first_frame; - } renderer; - - struct { - SphericalCoord pointer; - int active_workspace; - struct { - LunarWM_Window *v_windows; - } workspaces[10]; - } wm; - - ConfigManager *cman; - - _Atomic(int) counter; - - bool initialized; - bool running; -} LunarWM; - -bool LunarWM_init(LunarWM *wm); -void LunarWM_destroy(LunarWM *this); - -void LunarWM_terminate(LunarWM *this); -void LunarWM_run(LunarWM *this); - -static inline bool LunarWM_get_new_id(LunarWM *this) { return ++this->counter; } - -extern LunarWM g_wm; - -#endif // LUNAR_WM_H +#endif diff --git a/src/LunarWM_core.c b/src/LunarWM_core.c new file mode 100644 index 0000000..7c8c344 --- /dev/null +++ b/src/LunarWM_core.c @@ -0,0 +1,408 @@ +#include "LunarWM_core.h" + +#include "LunarWM_render.h" +#include "LunarWM_wayland.h" +#include "LunarWM_xr.h" +#include "RayExt.h" +#include "common.h" +#include "vec.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +extern char **environ; + +static void cleanup_raylib_egl(LunarWM *wm) +{ + if (IsWindowReady()) { + CloseWindow(); + } +} + +static void cleanup_lua_cfg(LunarWM *wm) +{ + if (wm->cman) { + config_manager_destroy(wm->cman); + wm->cman = NULL; + } +} + +void LunarWM_set_recenter_from_camera(LunarWM *wm) +{ + Vector3 pos = wm->renderer.camera.position; + Vector3 fwd + = Vector3Normalize(Vector3Subtract(wm->renderer.camera.target, pos)); + + float len_xz = sqrtf(fwd.x * fwd.x + fwd.z * fwd.z); + float yaw = (len_xz > 1e-6f) ? atan2f(fwd.x, fwd.z) : 0.0f; + + Quaternion q_step = QuaternionFromAxisAngle((Vector3) { 0, 1, 0 }, -yaw); + Vector3 t_step = Vector3Negate(Vector3RotateByQuaternion(pos, q_step)); + + Quaternion q_total = QuaternionMultiply(q_step, wm->xr.recenter_rot); + Vector3 t_total = Vector3Add( + Vector3RotateByQuaternion(wm->xr.recenter_trans, q_step), t_step); + + wm->xr.recenter_rot = q_total; + wm->xr.recenter_trans = t_total; + wm->xr.recenter_active = true; +} + +static void sync_config(LunarWM *wm) +{ + if (wm->cman->cfg.cubemap) { + Skybox_init(&wm->renderer.skybox, wm->cman->cfg.cubemap); + } else { + Skybox_destroy(&wm->renderer.skybox); + } + + if (IsTextureValid(wm->renderer.hud_rt.texture)) { + UnloadTexture(wm->renderer.hud_rt.texture); + wm->renderer.hud_rt.texture.id = 0; + wm->renderer.hud_rt.texture.width = 0; + wm->renderer.hud_rt.texture.height = 0; + } + + int vw = (int)wm->cman->cfg.displays.virtual.resolution.x; + int vh = (int)wm->cman->cfg.displays.virtual.resolution.y; + int hud = wm->cman->cfg.displays.hud.size; + + LunarWM_wayland_update_virtual_outputs(wm, vw, vh, hud); +} + +static int l_exec(lua_State *L) +{ + char const *cmd = luaL_checkstring(L, 1); + + pid_t pid; + char *argv[] = { (char *)"sh", (char *)"-c", (char *)cmd, NULL }; + + int rc = posix_spawnp(&pid, "sh", NULL, NULL, argv, environ); + if (rc != 0) { + lua_pushnil(L); + lua_pushfstring(L, "posix_spawnp failed: %s", strerror(rc)); + return 2; + } + + lua_pushinteger(L, (lua_Integer)pid); + return 1; +} + +static int l_cycle_next(lua_State *L) +{ + LunarWM *wm = &g_wm; + if (vector_size(wm->wayland.v_toplevels) == 0) { + lua_pushnil(L); + return 1; + } + + wm->wayland.current_focus++; + if (wm->wayland.current_focus >= vector_size(wm->wayland.v_toplevels)) { + wm->wayland.current_focus = 0; + } + + LunarWM_Toplevel *tl = wm->wayland.v_toplevels[wm->wayland.current_focus]; + LunarWM_Toplevel_focus(tl); + + lua_pushnil(L); + return 1; +} + +static int l_recenter(lua_State *L) +{ + (void)L; + + LunarWM_set_recenter_from_camera(&g_wm); + + lua_pushnil(L); + return 1; +} + +static int l_reload_config(lua_State *L) +{ + LunarWM *wm = &g_wm; + ConfigManager *cm = wm->cman; + if (!cm) { + lua_pushnil(L); + return 1; + } + + config_manager_reload(cm); + sync_config(wm); + lua_pushnil(L); + return 1; +} + +static int l_quit_compositor(lua_State *L) +{ + (void)L; + LunarWM_terminate(&g_wm); + lua_pushnil(L); + return 1; +} + +bool LunarWM_init(LunarWM *wm) +{ + memset(wm, 0, sizeof(*wm)); + wm->xr.session = XR_NULL_HANDLE; + wm->xr.session_state = XR_SESSION_STATE_UNKNOWN; + wm->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM; + wm->xr.local_space = wm->xr.view_space = XR_NULL_HANDLE; + wm->xr.hand_tracking_system_properties.type + = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; + wm->renderer.camera.position = (Vector3) { 0, 0, 0 }; + wm->renderer.camera.target = (Vector3) { 0, 0, 1 }; + wm->renderer.camera.up = (Vector3) { 0, 1, 0 }; + wm->renderer.camera.fovy = 45; + wm->renderer.camera.projection = CAMERA_PERSPECTIVE; + wm->xr.recenter_rot = (Quaternion) { 0, 0, 0, 1 }; + wm->xr.recenter_trans = (Vector3) { 0, 0, 0 }; + wm->xr.recenter_active = false; + wm->counter = 0; + + wm->wm.active_workspace = 0; + for (size_t i = 0; i < ARRAY_SZ(wm->wm.workspaces); i++) { + wm->wm.workspaces[i].v_windows = vector_create(); + } + + wm->cman = config_manager_create(get_config_path()); + assert(wm->cman); + + { + lua_State *L = wm->cman->L; + + lua_newtable(L); + lua_pushcfunction(L, l_quit_compositor); + lua_setfield(L, -2, "quit_compositor"); + lua_pushcfunction(L, l_reload_config); + lua_setfield(L, -2, "reload_config"); + lua_pushcfunction(L, l_recenter); + lua_setfield(L, -2, "recenter"); + lua_pushcfunction(L, l_cycle_next); + lua_setfield(L, -2, "cycle_next"); + lua_pushcfunction(L, l_exec); + lua_setfield(L, -2, "exec"); + lua_setglobal(L, "lunar"); + + config_manager_reload(wm->cman); + } + + if (getenv("DISPLAY") != NULL || getenv("WAYLAND_DISPLAY") != NULL) { + wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); + return false; + } + + if (!LunarWM_wayland_init(wm)) { + wlr_log(WLR_ERROR, "Failed to initialize wlroots"); + return false; + } + + EGLSurface draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface read = eglGetCurrentSurface(EGL_READ); + if (eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + wm->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return false; + } + + if (!LunarWM_xr_init(wm)) { + wlr_log(WLR_ERROR, "Failed to initialize OpenXR"); + return false; + } + + wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); + InitWindow(0, 0, ""); + + if (eglMakeCurrent( + wm->wayland.egl_display, draw, read, wm->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return false; + } + + sync_config(wm); + + wm->initialized = true; + + return true; +} + +void LunarWM_destroy(LunarWM *wm) +{ + if (!wm) + return; + + eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + wm->wayland.egl_context); + + LunarWM_xr_cleanup(wm); + cleanup_raylib_egl(wm); + LunarWM_wayland_cleanup(wm); + cleanup_lua_cfg(wm); + + memset(wm, 0, sizeof(*wm)); +} + +void LunarWM_terminate(LunarWM *wm) +{ + wlr_log(WLR_INFO, "Stopping compositor"); + wm->running = false; +} + +void LunarWM_run(LunarWM *wm) +{ + assert(wm); + assert(wm->initialized); + + wm->renderer.first_frame = true; + + if (!wlr_backend_start(wm->wayland.backend)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + return; + } + + char const *socket = wl_display_add_socket_auto(wm->wayland.display); + if (socket == NULL) { + wlr_log(WLR_ERROR, "Failed to add wayland socket to display"); + return; + } + + setenv("WAYLAND_DISPLAY", socket, 1); + wlr_log(LOG_INFO, "Running on WAYLAND_DISPLAY=%s", socket); + + wm->running = true; + + struct timespec last, now; + clock_gettime(CLOCK_MONOTONIC, &last); + while (wm->running) { + clock_gettime(CLOCK_MONOTONIC, &now); + float dt = (now.tv_sec - last.tv_sec) + + (now.tv_nsec - last.tv_nsec) / 1000000000.0f; + last = now; + + for (size_t i = 0; i < vector_size(wm->wayland.v_toplevels); i++) { + LunarWM_Toplevel *tl = wm->wayland.v_toplevels[i]; + if (tl->surface) { + wlr_surface_send_frame_done(tl->surface, &now); + } + } + + wl_display_flush_clients(wm->wayland.display); + wl_event_loop_dispatch(wm->wayland.event_loop, 0); + + EGLSurface draw = eglGetCurrentSurface(EGL_DRAW); + EGLSurface read = eglGetCurrentSurface(EGL_READ); + if (eglMakeCurrent(wm->wayland.egl_display, EGL_NO_SURFACE, + EGL_NO_SURFACE, wm->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return; + } + + WindowShouldClose(); + BeginDrawing(); + + LunarWM_xr_poll_events(wm); + + if (!wm->xr.session_running) { + EndDrawing(); + continue; + } + + XrFrameState frame_state = { + .type = XR_TYPE_FRAME_STATE, + }; + XrFrameWaitInfo frame_wait_info = { + .type = XR_TYPE_FRAME_WAIT_INFO, + }; + if (xrWaitFrame(wm->xr.session, &frame_wait_info, &frame_state) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame"); + return; + } + + XrFrameBeginInfo frame_begin_info = { + .type = XR_TYPE_FRAME_BEGIN_INFO, + }; + XrResult res = xrBeginFrame(wm->xr.session, &frame_begin_info); + if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to begin the OpenXR Frame: %d", res); + return; + } + + if (wm->xr.hand_tracking_system_properties.supportsHandTracking) { + for (size_t i = 0; i < 2; i++) { + LunarWM_Hand *hand = &wm->xr.hands[i]; + bool const unobstructed = true; + + XrHandJointsMotionRangeInfoEXT mri = { + .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, + }; + mri.handJointsMotionRange = unobstructed + ? XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT + : XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; + + XrHandJointsLocateInfoEXT li = { + .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, + .next = &mri, + .baseSpace = wm->xr.local_space, + .time = frame_state.predictedDisplayTime, + }; + + XrHandJointLocationsEXT hji = { + .type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT, + .jointCount = XR_HAND_JOINT_COUNT_EXT, + .jointLocations = hand->joint_locations, + }; + + if (wm->xr.LocateHandJointsEXT(hand->hand_tracker, &li, &hji) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to locate hand joints"); + return; + } + } + } + + LunarWM_RenderLayerInfo render_layer_info = { + .predicted_display_time = frame_state.predictedDisplayTime, + .layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, + }; + bool const session_active + = (wm->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED) + || (wm->xr.session_state == XR_SESSION_STATE_VISIBLE) + || (wm->xr.session_state == XR_SESSION_STATE_FOCUSED); + if (session_active && (frame_state.shouldRender != 0u)) { + bool rendered = LunarWM_render_layer(wm, &render_layer_info, dt); + if (rendered) { + render_layer_info.layers[render_layer_info.layers_count] + = (XrCompositionLayerBaseHeader *)&render_layer_info + .layer_projection; + } + } + + XrFrameEndInfo frame_end_info = { + .type = XR_TYPE_FRAME_END_INFO, + .displayTime = frame_state.predictedDisplayTime, + .environmentBlendMode = wm->xr.environment_blend_mode, + .layerCount = render_layer_info.layers_count, + .layers + = (XrCompositionLayerBaseHeader const **)render_layer_info.layers, + }; + if (xrEndFrame(wm->xr.session, &frame_end_info) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to end OpenXR frame"); + return; + } + + EndDrawing(); + } +} diff --git a/src/LunarWM_core.h b/src/LunarWM_core.h new file mode 100644 index 0000000..7fd17b3 --- /dev/null +++ b/src/LunarWM_core.h @@ -0,0 +1,14 @@ +#ifndef LUNAR_WM_CORE_H +#define LUNAR_WM_CORE_H + +#include "LunarWM_types.h" + +bool LunarWM_init(LunarWM *wm); +void LunarWM_destroy(LunarWM *wm); +void LunarWM_terminate(LunarWM *wm); +void LunarWM_run(LunarWM *wm); +void LunarWM_set_recenter_from_camera(LunarWM *wm); + +extern LunarWM g_wm; + +#endif diff --git a/src/LunarWM_render.c b/src/LunarWM_render.c new file mode 100644 index 0000000..92445d5 --- /dev/null +++ b/src/LunarWM_render.c @@ -0,0 +1,662 @@ +#include "LunarWM_render.h" + +#include "LunarWM_core.h" +#include "LunarWM_xr.h" +#include "common.h" +#include "vec.h" + +#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; + } + Vector3 vec = Vector3Scale(Vector3Normalize(fwd), r); + return Vector3ToSpherical(vec); +} + +static inline Matrix xr_matrix(XrPosef const pose) +{ + Matrix const translation + = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z); + Matrix const rotation = QuaternionToMatrix((Quaternion) { + pose.orientation.x, + pose.orientation.y, + pose.orientation.z, + pose.orientation.w, + }); + return MatrixMultiply(rotation, translation); +} + +static inline Matrix xr_projection_matrix(XrFovf const fov) +{ + static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); + + Matrix matrix = {}; + + auto const near = (float)RL_CULL_DISTANCE_NEAR; + auto const far = (float)RL_CULL_DISTANCE_FAR; + + float const tan_angle_left = tanf(fov.angleLeft); + float const tan_angle_right = tanf(fov.angleRight); + + float const tan_angle_down = tanf(fov.angleDown); + float const tan_angle_up = tanf(fov.angleUp); + + float const tan_angle_width = tan_angle_right - tan_angle_left; + float const tan_angle_height = tan_angle_up - tan_angle_down; + + matrix.m0 = 2 / tan_angle_width; + matrix.m4 = 0; + matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width; + matrix.m12 = 0; + + matrix.m1 = 0; + matrix.m5 = 2 / tan_angle_height; + matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height; + matrix.m13 = 0; + + matrix.m2 = 0; + matrix.m6 = 0; + matrix.m10 = -(far + near) / (far - near); + matrix.m14 = -(far * (near + near)) / (far - near); + + matrix.m3 = 0; + matrix.m7 = 0; + matrix.m11 = -1; + matrix.m15 = 0; + + return matrix; +} + +static void DrawBillboardNoShear( + Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint) +{ + Rectangle const src = { 0, 0, tex.width, tex.height }; + + Vector2 const size = { scale * fabsf(src.width / src.height), -scale }; + Vector2 const origin = { size.x * 0.5f, size.y * 0.5f }; + + DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint); +} + +void DrawTextureCyl( + Texture2D tex, Vector3 center, float radius, float scale, bool y_flip) +{ + if (!tex.id || scale <= 0.0f || radius == 0.0f) + return; + + float r = fabsf(radius); + float arc_len = (float)tex.width * scale; // arc length in world units + float theta = arc_len / r; // radians across the panel + float half_t = 0.5f * theta; + float half_h = 0.5f * (float)tex.height * scale; + + // mid-angle around Y so the segment's middle sits at 'center' + float a0 = atan2f(center.x, center.z); + + // shift so the cylinder surface midpoint matches 'center' + Vector3 mid_ref = (Vector3) { sinf(a0) * r, center.y, cosf(a0) * r }; + Vector3 delta = Vector3Subtract(center, mid_ref); + + // tessellation: about 3° per slice (min 8) + int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f)); + if (slices > 1024) + slices = 1024; + + float vt = y_flip ? 1.0f : 0.0f; + float vb = y_flip ? 0.0f : 1.0f; + + rlDrawRenderBatchActive(); // flush any prior state + rlSetTexture(tex.id); + rlDisableBackfaceCulling(); + rlColor4ub(255, 255, 255, 255); + rlBegin(RL_QUADS); + + for (int i = 0; i < slices; ++i) { + float u0 = (float)i / (float)slices; + float u1 = (float)(i + 1) / (float)slices; + + float aL = a0 - half_t + theta * u0; + float aR = a0 - half_t + theta * u1; + + Vector3 nL = (Vector3) { sinf(aL), 0.0f, cosf(aL) }; + Vector3 nR = (Vector3) { sinf(aR), 0.0f, cosf(aR) }; + if (radius < 0.0f) { + nL = Vector3Negate(nL); + nR = Vector3Negate(nR); + } + + Vector3 pLT = Vector3Add( + (Vector3) { nL.x * r, center.y + half_h, nL.z * r }, delta); + Vector3 pLB = Vector3Add( + (Vector3) { nL.x * r, center.y - half_h, nL.z * r }, delta); + Vector3 pRT = Vector3Add( + (Vector3) { nR.x * r, center.y + half_h, nR.z * r }, delta); + Vector3 pRB = Vector3Add( + (Vector3) { nR.x * r, center.y - half_h, nR.z * r }, delta); + + // match your flat-quad U flip (so WL textures look correct) + float U0 = 1.0f - u0; + float U1 = 1.0f - u1; + + // one normal per-vertex (simple cylindrical) + rlNormal3f(nL.x, nL.y, nL.z); + rlTexCoord2f(U0, vt); + rlVertex3f(pLT.x, pLT.y, pLT.z); + rlNormal3f(nR.x, nR.y, nR.z); + rlTexCoord2f(U1, vt); + rlVertex3f(pRT.x, pRT.y, pRT.z); + rlNormal3f(nR.x, nR.y, nR.z); + rlTexCoord2f(U1, vb); + rlVertex3f(pRB.x, pRB.y, pRB.z); + rlNormal3f(nL.x, nL.y, nL.z); + rlTexCoord2f(U0, vb); + rlVertex3f(pLB.x, pLB.y, pLB.z); + } + + rlEnd(); + rlSetTexture(0); + rlEnableBackfaceCulling(); +} + +static void DrawTextureCyl2(Texture2D tex, Vector3 sphere_center, + SphericalCoord coord, float rad, float scale, bool y_flip) +{ + if (!tex.id || scale <= 0.0f || rad == 0.0f) + return; + + // midpoint on the sphere where the panel should sit (its center) + Vector3 fwd = SphericalToVector3(coord); + if (Vector3Length(fwd) < 1e-6f) + fwd = (Vector3) { 0, 0, 1 }; + fwd = Vector3Normalize(fwd); + + // build a local tangent frame at that point (right, up, forward) + Vector3 worldUp = (Vector3) { 0, 1, 0 }; + if (fabsf(Vector3DotProduct(worldUp, fwd)) > 0.99f) + worldUp = (Vector3) { 1, 0, 0 }; + Vector3 right = Vector3Normalize(Vector3CrossProduct(worldUp, fwd)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(fwd, right)); + + float r = fabsf(rad); + float arc_len = (float)tex.width * scale; // world units across + float theta = arc_len / r; // total horizontal FOV in radians + float half_t = 0.5f * theta; + float half_h = 0.5f * (float)tex.height * scale; + + // shift so cylinder's surface midpoint lands exactly at coord.r from + // sphere_center + Vector3 delta = Vector3Add(sphere_center, + Vector3Add(Vector3Scale(fwd, coord.r - r), (Vector3) { 0, 0, 0 })); + + // tessellation: about 3° per slice (min 8, max 1024) + int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f)); + if (slices > 1024) + slices = 1024; + + float vt = y_flip ? 1.0f : 0.0f; + float vb = y_flip ? 0.0f : 1.0f; + + rlDrawRenderBatchActive(); + rlSetTexture(tex.id); + rlDisableBackfaceCulling(); + rlColor4ub(255, 255, 255, 255); + rlBegin(RL_QUADS); + + for (int i = 0; i < slices; ++i) { + float u0 = (float)i / (float)slices; + float u1 = (float)(i + 1) / (float)slices; + + float aL = -half_t + theta * u0; + float aR = -half_t + theta * u1; + + // local outward directions on the cylindrical surface + Vector3 nL = Vector3Add( + Vector3Scale(right, sinf(aL)), Vector3Scale(fwd, cosf(aL))); + Vector3 nR = Vector3Add( + Vector3Scale(right, sinf(aR)), Vector3Scale(fwd, cosf(aR))); + + if (rad < 0.0f) { + nL = Vector3Negate(nL); + nR = Vector3Negate(nR); + } + + // surface points (center band), then top/bottom by +/- up*half_h + Vector3 cL = Vector3Add(delta, Vector3Scale(nL, r)); + Vector3 cR = Vector3Add(delta, Vector3Scale(nR, r)); + + Vector3 pLT = Vector3Add(cL, Vector3Scale(up, half_h)); + Vector3 pLB = Vector3Add(cL, Vector3Scale(up, -half_h)); + Vector3 pRT = Vector3Add(cR, Vector3Scale(up, half_h)); + Vector3 pRB = Vector3Add(cR, Vector3Scale(up, -half_h)); + + // match the original horizontal flip so Wayland textures look correct + float U0 = 1.0f - u0; + float U1 = 1.0f - u1; + + rlNormal3f(nL.x, nL.y, nL.z); + rlTexCoord2f(U0, vt); + rlVertex3f(pLT.x, pLT.y, pLT.z); + + rlNormal3f(nR.x, nR.y, nR.z); + rlTexCoord2f(U1, vt); + rlVertex3f(pRT.x, pRT.y, pRT.z); + + rlNormal3f(nR.x, nR.y, nR.z); + rlTexCoord2f(U1, vb); + rlVertex3f(pRB.x, pRB.y, pRB.z); + + rlNormal3f(nL.x, nL.y, nL.z); + rlTexCoord2f(U0, vb); + rlVertex3f(pLB.x, pLB.y, pLB.z); + } + + rlEnd(); + rlSetTexture(0); + rlEnableBackfaceCulling(); +} + +static inline Vector3 RecenterPoint(LunarWM *wm, Vector3 p) +{ + if (!wm->xr.recenter_active) + return p; + return Vector3Add(Vector3RotateByQuaternion(p, wm->xr.recenter_rot), + wm->xr.recenter_trans); +} + +static inline Quaternion RecenterOrient(LunarWM *wm, Quaternion q) +{ + if (!wm->xr.recenter_active) + return q; + return QuaternionMultiply(wm->xr.recenter_rot, q); +} + +static LunarWM_Toplevel *find_toplevel(LunarWM *this, int id) +{ + for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { + auto *tl = this->wayland.v_toplevels[i]; + if (tl->id == id) + return tl; + } + + return NULL; +} + +void LunarWM_render_hud(LunarWM *this, float /*dt*/, int hud_size) +{ + ClearBackground((Color) { 0, 0, 0, 0 }); + + float const text_size = this->cman->cfg.displays.hud.font_size; + + char const *txt + = TextFormat("WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY")); + auto txt_w = MeasureText(txt, 24); + DrawText( + txt, hud_size / 2 - txt_w / 2, hud_size - text_size, text_size, WHITE); + + txt = TextFormat("DISPLAY=%s", getenv("DISPLAY")); + txt_w = MeasureText(txt, text_size); + DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - text_size * 2, text_size, + WHITE); + + { + time_t t = time(NULL); + struct tm *tm_info = localtime(&t); + + int hours = tm_info->tm_hour; + int minutes = tm_info->tm_min; + txt = TextFormat("%02d:%02d", hours, minutes); + txt_w = MeasureText(txt, 32); + DrawText(txt, hud_size / 2 - txt_w / 2, 0, text_size, WHITE); + } +} + +void LunarWM_render_windows(LunarWM *this, bool alpha_check) +{ + for (size_t i = 0; i + < vector_size(this->wm.workspaces[this->wm.active_workspace].v_windows); + i++) { + auto *window + = &this->wm.workspaces[this->wm.active_workspace].v_windows[i]; + auto *tl = window->tl; + if (!tl || !tl->surface) { + continue; + } + if (tl->gles_texture) { + if (alpha_check && tl->gles_texture->has_alpha) { + continue; + } + float rad = window->coord.r - 0.01f * (float)i; + DrawTextureCyl2(tl->rl_texture, Vector3Zero(), window->coord, rad, + this->cman->cfg.space.window_scale, false); + } + } +} + +static void render_3d(LunarWM *this, float /*dt*/) +{ + LunarWM_render_windows(this, true); + + for (int h = 0; h < 2; ++h) { + auto *hand_info = &this->xr.hands[h]; + for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { + auto const *jl = &hand_info->joint_locations[k]; + Vector3 pos = { + jl->pose.position.x, + jl->pose.position.y, + jl->pose.position.z, + }; + pos = RecenterPoint(this, pos); + DrawSphere(pos, jl->radius, (Color) { 255, 0, 0, 255 }); + } + } + + Skybox_draw(this->renderer.skybox, this->renderer.camera.position); + + rlEnableColorBlend(); + rlDisableDepthMask(); // don't write depth + LunarWM_render_windows(this, false); + // TODO: Replace with actual cursor texture. + { // Cursor + rlDrawRenderBatchActive(); + rlDisableDepthTest(); + Vector3 tip = SphericalToVector3(this->wm.pointer); + + Vector3 n = Vector3Normalize( + Vector3Subtract(this->renderer.camera.position, tip)); + Vector3 up_hint = (Vector3) { 0, 1, 0 }; + if (fabsf(Vector3DotProduct(up_hint, n)) > 0.98f) + up_hint = (Vector3) { 1, 0, 0 }; + Vector3 right = Vector3Normalize(Vector3CrossProduct(up_hint, n)); + Vector3 up = Vector3Normalize(Vector3CrossProduct(n, right)); + Vector3 down = Vector3Negate(up); + + float const s = 0.03f; + + Vector3 v1 = tip; + Vector3 v2 = Vector3Add(tip, Vector3Scale(down, s)); + Vector3 v3 = Vector3Add(tip, Vector3Scale(right, s)); + + Vector3 normal = Vector3CrossProduct( + Vector3Subtract(v2, v1), Vector3Subtract(v3, v1)); + if (Vector3DotProduct(normal, n) < 0.0f) { + Vector3 tmp = v2; + v2 = v3; + v3 = tmp; + } + + DrawTriangle3D(v1, v2, v3, RED); + rlDrawRenderBatchActive(); + rlEnableDepthTest(); + } + rlEnableDepthMask(); + + if (IsTextureValid(this->renderer.hud_rt.texture)) { + rlDrawRenderBatchActive(); + rlDisableDepthTest(); + + Vector3 camPos = this->renderer.camera.position; + Vector3 camDir = Vector3Normalize( + Vector3Subtract(this->renderer.camera.target, camPos)); + Vector3 up = this->renderer.camera.up; + Vector3 right = Vector3Normalize(Vector3CrossProduct(camDir, up)); + up = Vector3CrossProduct(right, camDir); + + Vector3 center = Vector3Add(camPos, Vector3Scale(camDir, 0.6f)); + + float heightMeters = 0.10f; + + DrawBillboardNoShear(this->renderer.camera, + this->renderer.hud_rt.texture, center, heightMeters * 5, WHITE); + rlDrawRenderBatchActive(); + + rlEnableDepthTest(); + } +} + +bool LunarWM_render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) +{ + auto const view_count = (uint32_t)this->xr.view_configuration_views_count; + assert(view_count == 2); + + XrView views[2] = {}; + for (int i = 0; i < ARRAY_SZ(views); i++) { + views[i] = (XrView) { + .type = XR_TYPE_VIEW, + }; + } + + XrViewLocateInfo locInfo = { + .type = XR_TYPE_VIEW_LOCATE_INFO, + .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + .displayTime = info->predicted_display_time, + .space = this->xr.local_space, + }; + + XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; + uint32_t located = 0; + if (xrLocateViews( + this->xr.session, &locInfo, &viewState, view_count, &located, views) + != XR_SUCCESS + || located != view_count) { + wlr_log(WLR_ERROR, "Failed to locate views"); + return false; + } + + // acquire swapchain images + auto *color_sc = &this->xr.swapchains.v_color[0]; + auto *depth_sc = &this->xr.swapchains.v_depth[0]; + + uint32_t col_idx = 0; + uint32_t dep_idx = 0; + if (!LunarWM_xr_acquire_wait(color_sc->swapchain, &col_idx) + || !LunarWM_xr_acquire_wait(depth_sc->swapchain, &dep_idx)) { + wlr_log(WLR_ERROR, "Swap-chain acquire failed"); + return false; + } + + GLuint color_tex = LunarWM_xr_get_swapchain_image(this, 0, col_idx); + GLuint depth_tex = LunarWM_xr_get_swapchain_image(this, 1, dep_idx); + + // build FBO + if (this->renderer.fbo == 0u) { + glGenFramebuffers(1, &this->renderer.fbo); + } + glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo); + rlFramebufferAttach(this->renderer.fbo, color_tex, + RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, + RL_ATTACHMENT_TEXTURE2D, 0); + assert(rlFramebufferComplete(this->renderer.fbo)); + + uint32_t const eye_w + = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; + 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)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 head_view = MatrixInvert(xr_matrix(headLoc.pose)); + + // per-eye projection + view-offset + 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); + + Matrix const proj_r = xr_projection_matrix(views[0].fov); + Matrix const proj_l = xr_projection_matrix(views[1].fov); + + int const hud_size = this->cman->cfg.displays.hud.size; + if (!IsTextureValid(this->renderer.hud_rt.texture)) { + this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size); + } + + if (IsTextureValid(this->renderer.hud_rt.texture)) { + BeginTextureMode(this->renderer.hud_rt); + { + LunarWM_render_hud(this, dt, hud_size); + } + EndTextureMode(); + } + + // draw + + if (!IsTextureValid(this->renderer.main_rt.texture)) { + this->renderer.main_rt = LoadRenderTexture(eye_w * view_count, eye_h); + } + + BeginTextureMode(this->renderer.main_rt); + + rlEnableStereoRender(); + rlSetMatrixProjectionStereo(proj_r, proj_l); + rlSetMatrixViewOffsetStereo(view_off_r, view_off_l); + + glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h); + rlClearColor(0, 0, 10, 255); + rlClearScreenBuffers(); + + for (int i = 0; i < 1; i++) { + XrTime const time = info->predicted_display_time; + + XrSpaceLocation view_location = { .type = XR_TYPE_SPACE_LOCATION }; + XrResult const result = xrLocateSpace( + this->xr.view_space, this->xr.local_space, time, &view_location); + if (result != XR_SUCCESS) { + break; + } + + if ((view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) + != 0u) { + auto const pos = view_location.pose.position; + this->renderer.camera.position = (Vector3) { pos.x, pos.y, pos.z }; + } + if ((view_location.locationFlags + & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) + != 0u) { + auto const rot = view_location.pose.orientation; + auto const forward + = Vector3RotateByQuaternion((Vector3) { 0, 0, -1 }, + (Quaternion) { rot.x, rot.y, rot.z, rot.w }); + auto const up = Vector3RotateByQuaternion((Vector3) { 0, 1, 0 }, + (Quaternion) { rot.x, rot.y, rot.z, rot.w }); + this->renderer.camera.target + = Vector3Add(this->renderer.camera.position, forward); + this->renderer.camera.up = up; + } + + if (this->xr.recenter_active) { + Vector3 pos = this->renderer.camera.position; + Vector3 fwd = Vector3Normalize( + Vector3Subtract(this->renderer.camera.target, pos)); + Vector3 up = this->renderer.camera.up; + + pos = Vector3Add( + Vector3RotateByQuaternion(pos, this->xr.recenter_rot), + this->xr.recenter_trans); + fwd = Vector3RotateByQuaternion(fwd, this->xr.recenter_rot); + up = Vector3RotateByQuaternion(up, this->xr.recenter_rot); + + this->renderer.camera.position = pos; + this->renderer.camera.target = Vector3Add(pos, fwd); + this->renderer.camera.up = up; + } + } + + BeginMode3D(this->renderer.camera); + { + ClearBackground(RED); + render_3d(this, dt); + } + EndMode3D(); + + rlDisableStereoRender(); + EndTextureMode(); + + if (!IsShaderValid(this->renderer.linear_srgb)) { + static char const linear_srgb[] = { +#embed "../assets/linear_srgb.fs" + , 0 + }; + this->renderer.linear_srgb = LoadShaderFromMemory(NULL, linear_srgb); + } + + BeginTextureMode(this->renderer.tmp_rt); + rlDisableColorBlend(); + ClearBackground(BLACK); + BeginShaderMode(this->renderer.linear_srgb); + DrawTexturePro(this->renderer.main_rt.texture, + (Rectangle) { + 0, + 0, + this->renderer.main_rt.texture.width, + -this->renderer.main_rt.texture.height, + }, + (Rectangle) { + 0, + 0, + this->renderer.tmp_rt.texture.width, + this->renderer.tmp_rt.texture.height, + }, + (Vector2) { 0, 0 }, 0, WHITE); + EndShaderMode(); + EndTextureMode(); + rlEnableColorBlend(); + + // release swapchain images + XrSwapchainImageReleaseInfo const ri + = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO }; + xrReleaseSwapchainImage(color_sc->swapchain, &ri); + xrReleaseSwapchainImage(depth_sc->swapchain, &ri); + + // fill projection layer + info->layer_projection_views_count = view_count; + + for (uint32_t i = 0; i < view_count; ++i) { + 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 = eye_w, .height = eye_h }; + pv->subImage.imageArrayIndex = 0; + } + + info->layer_projection = (XrCompositionLayerProjection) { + .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, + .layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT + | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, + .space = this->xr.local_space, + .viewCount = view_count, + .views = info->layer_projection_views, + }; + + info->layers_count = 0; + info->layers[info->layers_count++] + = (XrCompositionLayerBaseHeader *)&info->layer_projection; + + if (this->renderer.first_frame) { + LunarWM_set_recenter_from_camera(this); + this->renderer.first_frame = false; + this->wm.pointer = get_forward_spherical_with_nearest( + this->renderer.camera.target, this->cman->cfg.space.radius); + } + + return true; +} diff --git a/src/LunarWM_render.h b/src/LunarWM_render.h new file mode 100644 index 0000000..f09a880 --- /dev/null +++ b/src/LunarWM_render.h @@ -0,0 +1,11 @@ +#ifndef LUNAR_WM_RENDER_H +#define LUNAR_WM_RENDER_H + +#include "LunarWM_types.h" + +void LunarWM_render_hud(LunarWM *wm, float dt, int hud_size); +void LunarWM_render_windows(LunarWM *wm, bool alpha_check); +bool LunarWM_render_layer( + LunarWM *wm, LunarWM_RenderLayerInfo *info, float dt); + +#endif diff --git a/src/LunarWM_types.h b/src/LunarWM_types.h new file mode 100644 index 0000000..65b4c62 --- /dev/null +++ b/src/LunarWM_types.h @@ -0,0 +1,272 @@ +#ifndef LUNAR_WM_TYPES_H +#define LUNAR_WM_TYPES_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Config.h" +#include "RayExt.h" + +struct LunarWM; + +typedef struct { + float r, theta, phi; +} SphericalCoord; + +static inline SphericalCoord Vector3ToSpherical(Vector3 v) +{ + SphericalCoord s; + s.r = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); + if (s.r > 0.0f) { + s.theta = atan2f(v.z, v.x); + s.phi = acosf(v.y / s.r); + } else { + s.theta = 0.0f; + s.phi = 0.0f; + } + return s; +} + +static inline Vector3 SphericalToVector3(SphericalCoord s) +{ + Vector3 v; + float sin_phi = sinf(s.phi); + v.x = s.r * cosf(s.theta) * sin_phi; + v.y = s.r * cosf(s.phi); + v.z = s.r * sinf(s.theta) * sin_phi; + return v; +} + +typedef struct virtual_output { + struct wl_global *global; + struct wl_display *display; + struct wl_list clients; + + int32_t x, y; + int32_t phys_w_mm, phys_h_mm; + int32_t width, height; + int32_t refresh_mhz; + int32_t scale; + enum wl_output_subpixel subpixel; + enum wl_output_transform transform; + char const *make, *model; + char const *name, *desc; +} LunarWM_VirtualOutput; + +typedef struct { + struct LunarWM *server; + + struct wl_list link; + struct wlr_keyboard *wlr_keyboard; + + struct wl_listener modifiers; + struct wl_listener key; + struct wl_listener destroy; +} LunarWM_Keyboard; + +typedef struct { + struct LunarWM *server; + + struct wl_list link; + struct wlr_pointer *wlr_pointer; + + struct wl_listener motion; + struct wl_listener destroy; +} LunarWM_Pointer; + +typedef struct { + uint32_t id; + + bool is_xwayland; + + struct LunarWM *server; + + struct wl_listener commit; + struct wl_listener destroy; + + struct wl_listener map, unmap; + + union { + struct wlr_xdg_toplevel *xdg; + struct wlr_xwayland_surface *xwl; + } u; + + struct wlr_surface *surface; + struct wlr_texture *texture; + + struct wlr_buffer *locked_buffer; + struct wlr_gles2_texture_attribs attribs; + struct wlr_gles2_texture *gles_texture; + Texture2D rl_texture; +} LunarWM_Toplevel; + +typedef struct { + LunarWM_Toplevel *tl; + SphericalCoord coord; +} LunarWM_Window; + +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); +void LunarWM_Toplevel_focus(LunarWM_Toplevel *this); + +typedef struct { + XrSwapchain swapchain; + int64_t swapchain_format; + GLuint *v_image_views; +} LunarWM_SwapchainInfo; + +typedef struct { + XrSwapchain handle; + XrSwapchainImageOpenGLESKHR *a_imgs; + uint32_t a_imgs_count; +} LunarWM_SwapchainImagesEntry; + +typedef struct { + XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; + XrHandTrackerEXT hand_tracker; +} LunarWM_Hand; + +typedef struct { + XrTime predicted_display_time; + XrCompositionLayerProjection layer_projection; + XrCompositionLayerBaseHeader *layers[10]; + uint32_t layers_count; + XrCompositionLayerProjectionView layer_projection_views[10]; + uint32_t layer_projection_views_count; +} LunarWM_RenderLayerInfo; + +typedef struct LunarWM { + struct { + struct wl_display *display; + struct wl_event_loop *event_loop; + + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_session *session; + + struct wlr_egl *egl; + EGLDisplay egl_display; + EGLContext egl_context; + EGLConfig egl_config; + + struct wlr_allocator *allocator; + struct wlr_compositor *compositor; + struct wlr_subcompositor *subcompositor; + struct wlr_data_device_manager *data_device_manager; + + struct wlr_seat *seat; + struct wl_list keyboards; + struct wl_list pointers; + struct wl_listener new_input_listener; + + struct wlr_xdg_shell *xdg_shell; + struct wl_listener new_xdg_toplevel_listener; + struct wl_listener new_xdg_popup_listener; + + struct wlr_xwayland *xwayland; + + struct wl_listener xwayland_ready; + struct wl_listener xwayland_new_surface; + struct wl_listener xwayland_associate_tmp; + struct wl_listener xwayland_dissociate_tmp; + + LunarWM_VirtualOutput *custom_out_virtual; + LunarWM_VirtualOutput *custom_out_hud; + + LunarWM_Toplevel **v_toplevels; + int current_focus; + } wayland; + + struct { + XrInstance instance; + XrSystemId system_id; + XrSession session; + XrSessionState session_state; + struct { + LunarWM_SwapchainInfo *v_color; + LunarWM_SwapchainInfo *v_depth; + } swapchains; + LunarWM_SwapchainImagesEntry swapchain_images[2]; + XrViewConfigurationView *a_view_configuration_views; + uint32_t view_configuration_views_count; + XrEnvironmentBlendMode environment_blend_mode; + XrSpace local_space, view_space; + LunarWM_Hand hands[2]; + XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties; + + PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT; + PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT; + PFN_xrLocateHandJointsEXT LocateHandJointsEXT; + + Quaternion recenter_rot; + Vector3 recenter_trans; + bool recenter_active; + + bool session_running; + } xr; + + struct { + GLuint fbo; + RenderTexture2D tmp_rt; + RenderTexture2D main_rt; + RenderTexture2D hud_rt; + Camera3D camera; + Shader linear_srgb; + + Skybox skybox; + + bool first_frame; + } renderer; + + struct { + SphericalCoord pointer; + int active_workspace; + struct { + LunarWM_Window *v_windows; + } workspaces[10]; + } wm; + + ConfigManager *cman; + + _Atomic(int) counter; + + bool initialized; + bool running; +} LunarWM; + +static inline bool LunarWM_get_new_id(LunarWM *this) { return ++this->counter; } + +#endif diff --git a/src/LunarWM_wayland.c b/src/LunarWM_wayland.c new file mode 100644 index 0000000..9760300 --- /dev/null +++ b/src/LunarWM_wayland.c @@ -0,0 +1,1250 @@ +#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"); +} diff --git a/src/LunarWM_wayland.h b/src/LunarWM_wayland.h new file mode 100644 index 0000000..2a78ad3 --- /dev/null +++ b/src/LunarWM_wayland.h @@ -0,0 +1,11 @@ +#ifndef LUNAR_WM_WAYLAND_H +#define LUNAR_WM_WAYLAND_H + +#include "LunarWM_types.h" + +bool LunarWM_wayland_init(LunarWM *wm); +void LunarWM_wayland_cleanup(LunarWM *wm); +void LunarWM_wayland_update_virtual_outputs( + LunarWM *wm, int virtual_width, int virtual_height, int hud_size); + +#endif diff --git a/src/LunarWM_xr.c b/src/LunarWM_xr.c new file mode 100644 index 0000000..ee02585 --- /dev/null +++ b/src/LunarWM_xr.c @@ -0,0 +1,824 @@ +#include "LunarWM_xr.h" + +#include "common.h" +#include "vec.h" + +#include +#include +#include + +GLuint LunarWM_xr_get_swapchain_image( + LunarWM *wm, int swapchain_images_i, uint32_t index) +{ + return wm->xr.swapchain_images[swapchain_images_i].a_imgs[index].image; +} + + +bool LunarWM_xr_init(LunarWM *this) +{ + XrResult res = XR_SUCCESS; + + XrApplicationInfo app_info = { + .applicationVersion = 1, + .engineVersion = 1, + .apiVersion = XR_CURRENT_API_VERSION, + }; + strncpy((char *)app_info.applicationName, "LunarWM", + XR_MAX_APPLICATION_NAME_SIZE); + strncpy( + (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE); + + char const *instance_extensions[] = { + XR_EXT_DEBUG_UTILS_EXTENSION_NAME, + XR_MNDX_EGL_ENABLE_EXTENSION_NAME, + XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, + XR_EXT_HAND_TRACKING_EXTENSION_NAME, + }; + char const **v_active_instance_extensions = vector_create(); + + uint32_t extension_properties_count = 0; + XrExtensionProperties *extension_properties; + if (xrEnumerateInstanceExtensionProperties( + nullptr, 0, &extension_properties_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR instance extension properties (count)"); + return false; + } + extension_properties + = malloc(sizeof(*extension_properties) * extension_properties_count); + for (uint32_t i = 0; i < extension_properties_count; ++i) { + extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; + extension_properties[i].next = NULL; + } + + if (xrEnumerateInstanceExtensionProperties(nullptr, + extension_properties_count, &extension_properties_count, + extension_properties) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR instance extension properties"); + return false; + } + + for (size_t i = 0; i < ARRAY_SZ(instance_extensions); i++) { + char const *requested_instance_extension = instance_extensions[i]; + bool found = false; + for (int j = 0; j < extension_properties_count; j++) { + if (strcmp(requested_instance_extension, + extension_properties[j].extensionName) + != 0) { + continue; + } + + vector_add( + &v_active_instance_extensions, requested_instance_extension); + found = true; + break; + } + + if (!found) { + wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s", + requested_instance_extension); + return false; + } + } + + { + XrInstanceCreateInfo const ci = { + .type = XR_TYPE_INSTANCE_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .applicationInfo = app_info, + .enabledApiLayerCount = 0, + .enabledApiLayerNames = nullptr, + .enabledExtensionCount + = (uint32_t)vector_size(v_active_instance_extensions), + .enabledExtensionNames = v_active_instance_extensions, + }; + + XrInstance instance = nullptr; + if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR instance"); + return false; + } + this->xr.instance = instance; + + res = xrGetInstanceProcAddr(this->xr.instance, "xrCreateHandTrackerEXT", + (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT); + if (res != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get proc addr xrCreateHandTrackerEXT"); + return false; + } + res = xrGetInstanceProcAddr(this->xr.instance, + "xrDestroyHandTrackerEXT", + (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT); + if (res != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get proc addr xrDestroyHandTrackerEXT"); + return false; + } + res = xrGetInstanceProcAddr(this->xr.instance, "xrLocateHandJointsEXT", + (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get proc addr xrLocateHandJointsEXT"); + return false; + } + } + + { + XrSystemGetInfo gi = { + .type = XR_TYPE_SYSTEM_GET_INFO, + .next = nullptr, + }; + + XrFormFactor const factors[2] = { + XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, + XR_FORM_FACTOR_HANDHELD_DISPLAY, + }; + for (size_t i = 0; i < ARRAY_SZ(factors); i++) { + auto factor = factors[i]; + + gi.formFactor = factor; + XrSystemId system_id = 0; + if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) { + this->xr.system_id = system_id; + break; + } + } + + if (!this->xr.system_id) { + wlr_log(WLR_ERROR, "Failed to find valid form factor"); + return false; + } + } + + { + XrSystemProperties system_props = { + .type = XR_TYPE_SYSTEM_PROPERTIES, + .next = &this->xr.hand_tracking_system_properties, + }; + res = xrGetSystemProperties( + this->xr.instance, this->xr.system_id, &system_props); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res); + return false; + } + } + + XrGraphicsRequirementsOpenGLESKHR reqs = { + .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, + .next = nullptr, + }; + PFN_xrGetOpenGLESGraphicsRequirementsKHR + xrGetOpenGLESGraphicsRequirementsKHR + = nullptr; + xrGetInstanceProcAddr(this->xr.instance, + "xrGetOpenGLESGraphicsRequirementsKHR", + (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR); + if (xrGetOpenGLESGraphicsRequirementsKHR( + this->xr.instance, this->xr.system_id, &reqs) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); + return false; + } + wlr_log(WLR_INFO, "OpenGL ES range: %d.%d.%d - %d.%d.%d\n", + XR_VERSION_MAJOR(reqs.minApiVersionSupported), + XR_VERSION_MINOR(reqs.minApiVersionSupported), + XR_VERSION_PATCH(reqs.minApiVersionSupported), + XR_VERSION_MAJOR(reqs.maxApiVersionSupported), + XR_VERSION_MINOR(reqs.maxApiVersionSupported), + XR_VERSION_PATCH(reqs.maxApiVersionSupported)); + + glEnable(GL_DEBUG_OUTPUT_KHR); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + + { + XrGraphicsBindingEGLMNDX gbind = { + .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, + .next = nullptr, + .getProcAddress = eglGetProcAddress, + .display = this->wayland.egl_display, + .config = this->wayland.egl_config, + .context = this->wayland.egl_context, + }; + + XrSessionCreateInfo const ci = { + .type = XR_TYPE_SESSION_CREATE_INFO, + .next = &gbind, + .createFlags = 0, + .systemId = this->xr.system_id, + }; + + if (xrCreateSession(this->xr.instance, &ci, &this->xr.session) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR session"); + return false; + } + } + + // Swapchain time! + XrViewConfigurationType *a_view_config_types; + uint32_t view_config_types_count = 0; + { + if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, + 0, &view_config_types_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to get amount of OpenXR view configurations"); + return false; + } + a_view_config_types + = malloc(sizeof(*a_view_config_types) * view_config_types_count); + for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { + this->xr.a_view_configuration_views[i].type + = XR_TYPE_VIEW_CONFIGURATION_VIEW; + this->xr.a_view_configuration_views[i].next = NULL; + } + if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, + view_config_types_count, &view_config_types_count, + a_view_config_types) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate OpenXR view configurations"); + return false; + } + } + + { + bool found = false; + for (size_t i = 0; i < view_config_types_count; i++) { + if (a_view_config_types[i] + == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_ERROR, + "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present"); + return false; + } + } + + { + if (xrEnumerateViewConfigurationViews(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + 0, &this->xr.view_configuration_views_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to get amount of OpenXR view configuration views"); + return false; + } + this->xr.a_view_configuration_views + = malloc(sizeof(*this->xr.a_view_configuration_views) + * this->xr.view_configuration_views_count); + for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { + this->xr.a_view_configuration_views[i].type + = XR_TYPE_VIEW_CONFIGURATION_VIEW; + this->xr.a_view_configuration_views[i].next = NULL; + } + if (xrEnumerateViewConfigurationViews(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + this->xr.view_configuration_views_count, + &this->xr.view_configuration_views_count, + this->xr.a_view_configuration_views) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR view configuration views"); + return false; + } + } + + int64_t *a_swapchain_formats; + uint32_t a_swapchain_formats_count = 0; + { + if (xrEnumerateSwapchainFormats( + this->xr.session, 0, &a_swapchain_formats_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get amount of OpenXR swapchain formats"); + return false; + } + a_swapchain_formats + = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count); + if (xrEnumerateSwapchainFormats(this->xr.session, + a_swapchain_formats_count, &a_swapchain_formats_count, + a_swapchain_formats) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats"); + return false; + } + } + + { + bool found = false; + for (size_t i = 0; i < a_swapchain_formats_count; i++) { + if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) { + found = true; + break; + } + } + if (!found) { + wlr_log( + WLR_ERROR, "Failed to find satisfying depth swapchain format"); + return false; + } + } + + auto const swapchain_format_depth = GL_DEPTH_COMPONENT16; + auto const swapchain_format_color = GL_SRGB8_ALPHA8; + + { + bool found = false; + for (size_t i = 0; i < a_swapchain_formats_count; i++) { + if (a_swapchain_formats[i] == GL_SRGB8_ALPHA8) { + found = true; + break; + } + } + if (!found) { + wlr_log( + WLR_ERROR, "Failed to find satisfying color swapchain format"); + return false; + } + } + + uint32_t const view_count = this->xr.view_configuration_views_count; + uint32_t const eye_w + = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; + uint32_t const eye_h + = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; + uint32_t const buf_w = eye_w * view_count; + + if (!this->xr.swapchains.v_color) { + this->xr.swapchains.v_color = vector_create(); + } + if (!this->xr.swapchains.v_depth) { + this->xr.swapchains.v_depth = vector_create(); + } + + vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {}); + vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {}); + + { + auto *color_sc = &this->xr.swapchains.v_color[0]; + auto *depth_sc = &this->xr.swapchains.v_depth[0]; + auto *vcv = &this->xr.a_view_configuration_views[0]; + + { // Create color swapchain + XrSwapchainCreateInfo const ci = { + .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT + | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, + .format = swapchain_format_color, + .sampleCount = vcv->recommendedSwapchainSampleCount, + .width = buf_w, + .height = eye_h, + .faceCount = 1, + .arraySize = 1, + .mipCount = 1, + }; + color_sc->swapchain_format = ci.format; + + if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain"); + return false; + } + } + + { // Create depth swapchain + XrSwapchainCreateInfo const ci = { + .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT + | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .format = swapchain_format_depth, + .sampleCount = vcv->recommendedSwapchainSampleCount, + .width = buf_w, + .height = eye_h, + .faceCount = 1, + .arraySize = 1, + .mipCount = 1, + }; + depth_sc->swapchain_format = ci.format; + + if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain"); + return false; + } + } + + // Enumerate swapchain images + { // Color + this->xr.swapchain_images[0].handle = color_sc->swapchain; + if (xrEnumerateSwapchainImages(color_sc->swapchain, 0, + &this->xr.swapchain_images[0].a_imgs_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get Color Swapchain Images count."); + return false; + } + this->xr.swapchain_images[0].a_imgs + = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs) + * this->xr.swapchain_images[0].a_imgs_count); + for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count; + ++i) { + this->xr.swapchain_images[0].a_imgs[i].type + = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + this->xr.swapchain_images[0].a_imgs[i].next = NULL; + } + if (xrEnumerateSwapchainImages(color_sc->swapchain, + this->xr.swapchain_images[0].a_imgs_count, + &this->xr.swapchain_images[0].a_imgs_count, + (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0] + .a_imgs) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate color swapchain images."); + return false; + } + } + { // Depth + this->xr.swapchain_images[1].handle = depth_sc->swapchain; + if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0, + &this->xr.swapchain_images[1].a_imgs_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get depth Swapchain Images count."); + return false; + } + this->xr.swapchain_images[1].a_imgs + = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs) + * this->xr.swapchain_images[1].a_imgs_count); + for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count; + ++i) { + this->xr.swapchain_images[1].a_imgs[i].type + = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + this->xr.swapchain_images[1].a_imgs[i].next = NULL; + } + if (xrEnumerateSwapchainImages(depth_sc->swapchain, + this->xr.swapchain_images[1].a_imgs_count, + &this->xr.swapchain_images[1].a_imgs_count, + (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1] + .a_imgs) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate depth swapchain images."); + return false; + } + } + + // Get image views + for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count; + j++) { + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + + GLenum const attachment = GL_COLOR_ATTACHMENT0; + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, + GL_TEXTURE_2D, LunarWM_xr_get_swapchain_image(this, 0, j), 0); + + GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (result != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create color framebuffer"); + return false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (!color_sc->v_image_views) { + color_sc->v_image_views = vector_create(); + } + vector_add(&color_sc->v_image_views, framebuffer); + } + + for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count; + j++) { + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + + GLenum const attachment = GL_DEPTH_ATTACHMENT; + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, + GL_TEXTURE_2D, LunarWM_xr_get_swapchain_image(this, 1, j), 0); + + GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (result != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create depth framebuffer"); + return false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (!depth_sc->v_image_views) { + depth_sc->v_image_views = vector_create(); + } + vector_add(&depth_sc->v_image_views, framebuffer); + } + } + + assert(this->xr.instance); + assert(this->xr.system_id); + + XrEnvironmentBlendMode *a_environment_blend_modes; + uint32_t a_environment_blend_modes_count = 0; + { // Get available blend modes + if (xrEnumerateEnvironmentBlendModes(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + 0, &a_environment_blend_modes_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get OpenXR environment blend mode count"); + return false; + } + a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes) + * a_environment_blend_modes_count); + if (xrEnumerateEnvironmentBlendModes(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + a_environment_blend_modes_count, + &a_environment_blend_modes_count, a_environment_blend_modes) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get XR environment blend modes"); + return false; + } + } + XrEnvironmentBlendMode const requested_environment_blend_modes[] = { + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, + XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, + XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, + }; + + for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) { + this->xr.environment_blend_mode = requested_environment_blend_modes[i]; + bool found = false; + for (size_t j = 0; j < a_environment_blend_modes_count; j++) { + if (requested_environment_blend_modes[i] + == a_environment_blend_modes[j]) { + found = true; + break; + } + } + if (found) { + break; + } + } + if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) { + wlr_log(WLR_INFO, + "Failed to find a compatible blend mode. Defaulting to " + "XR_ENVIRONMENT_BLEND_MODE_OPAQUE."); + this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + } + + { // Reference space + XrReferenceSpaceCreateInfo const ci = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .next = nullptr, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL, + .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, + .position = { 0.0f, 0.0f, 0.0f } }, + }; + + if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); + return false; + } + } + + { // View reference space + XrReferenceSpaceCreateInfo const ci = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .next = nullptr, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, + .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, + .position = { 0.0f, 0.0f, 0.0f } }, + }; + + if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); + return false; + } + } + + { // Create hand trackers + for (size_t i = 0; i < 2; i++) { + auto *hand = &this->xr.hands[i]; + + XrHandTrackerCreateInfoEXT const ci = { + .type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, + .hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, + .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, + }; + + res = this->xr.CreateHandTrackerEXT( + this->xr.session, &ci, &hand->hand_tracker); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res); + return false; + } + } + } + + free(extension_properties); + free(a_view_config_types); + free(a_swapchain_formats); + free(a_environment_blend_modes); + vector_free(v_active_instance_extensions); + + return true; +} + +static void free_swapchain_info(LunarWM_SwapchainInfo *sc) +{ + if (!sc) + return; + if (sc->v_image_views) { + for (size_t i = 0; i < vector_size(sc->v_image_views); ++i) { + GLuint fb = sc->v_image_views[i]; + if (fb) + glDeleteFramebuffers(1, &fb); + } + vector_free(sc->v_image_views); + sc->v_image_views = NULL; + } + if (sc->swapchain) { + xrDestroySwapchain(sc->swapchain); + sc->swapchain = XR_NULL_HANDLE; + } +} + +void LunarWM_xr_cleanup(LunarWM *this) +{ + for (int i = 0; i < 2; ++i) { + if (this->xr.swapchain_images[i].a_imgs) { + free(this->xr.swapchain_images[i].a_imgs); + this->xr.swapchain_images[i].a_imgs = NULL; + this->xr.swapchain_images[i].a_imgs_count = 0; + } + } + + if (this->xr.swapchains.v_color) { + for (size_t i = 0; i < vector_size(this->xr.swapchains.v_color); ++i) + free_swapchain_info(&this->xr.swapchains.v_color[i]); + vector_free(this->xr.swapchains.v_color); + this->xr.swapchains.v_color = NULL; + } + if (this->xr.swapchains.v_depth) { + for (size_t i = 0; i < vector_size(this->xr.swapchains.v_depth); ++i) + free_swapchain_info(&this->xr.swapchains.v_depth[i]); + vector_free(this->xr.swapchains.v_depth); + this->xr.swapchains.v_depth = NULL; + } + + if (this->renderer.fbo) { + glDeleteFramebuffers(1, &this->renderer.fbo); + this->renderer.fbo = 0; + } + this->renderer.tmp_rt = (RenderTexture2D) { 0 }; + + if (this->xr.view_space) + xrDestroySpace(this->xr.view_space), + this->xr.view_space = XR_NULL_HANDLE; + if (this->xr.local_space) + xrDestroySpace(this->xr.local_space), + this->xr.local_space = XR_NULL_HANDLE; + + for (size_t i = 0; i < 2; ++i) { + if (this->xr.hands[i].hand_tracker && this->xr.DestroyHandTrackerEXT) + this->xr.DestroyHandTrackerEXT(this->xr.hands[i].hand_tracker); + this->xr.hands[i].hand_tracker = XR_NULL_HANDLE; + } + + if (this->xr.a_view_configuration_views) { + free(this->xr.a_view_configuration_views); + this->xr.a_view_configuration_views = NULL; + this->xr.view_configuration_views_count = 0; + } + + if (this->xr.session != XR_NULL_HANDLE) { + xrDestroySession(this->xr.session); + this->xr.session = XR_NULL_HANDLE; + this->xr.session_running = false; + } + + if (this->xr.instance) + xrDestroyInstance(this->xr.instance), + this->xr.instance = XR_NULL_HANDLE; +} +void LunarWM_xr_poll_events(LunarWM *this) +{ + XrEventDataBuffer event_data = { + .type = XR_TYPE_EVENT_DATA_BUFFER, + }; + + while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) { + switch (event_data.type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + auto *el = (XrEventDataEventsLost *)&event_data; + wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); + break; + } + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + auto *ilp = (XrEventDataInstanceLossPending *)&event_data; + wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", + ilp->lossTime); + this->xr.session_running = false; + this->running = false; + break; + } + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data; + wlr_log(WLR_INFO, + "OPENXR: Interaction Profile changed for Session: " + "%p", + ipc->session); + if (ipc->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataInteractionProfileChanged for " + "unknown Session"); + break; + } + break; + } + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data; + wlr_log(WLR_INFO, + "OPENXR: Reference Space Change pending for " + "Session: %p", + scp->session); + if (scp->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataReferenceSpaceChangePending for " + "unknown " + "Session"); + break; + } + break; + } + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + auto *sc = (XrEventDataSessionStateChanged *)&event_data; + if (sc->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataSessionStateChanged for unknown " + "Session"); + break; + } + + if (sc->state == XR_SESSION_STATE_READY) { + XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO }; + bi.primaryViewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + bi.primaryViewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to begin session"); + } else { + this->xr.session_running = true; + } + } + if (sc->state == XR_SESSION_STATE_STOPPING) { + if (xrEndSession(this->xr.session) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to end session"); + } + this->xr.session_running = false; + } + if (sc->state == XR_SESSION_STATE_EXITING) { + this->xr.session_running = false; + this->running = false; + } + if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { + this->xr.session_running = false; + this->running = false; + } + this->xr.session_state = sc->state; + break; + } + default: { + break; + } + } + } +} + +bool LunarWM_xr_acquire_wait(XrSwapchain sc, uint32_t *idx) +{ + XrSwapchainImageAcquireInfo ai + = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL }; + if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) { + return false; + } + + XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, + .next = NULL, + .timeout = XR_INFINITE_DURATION }; + return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; +} diff --git a/src/LunarWM_xr.h b/src/LunarWM_xr.h new file mode 100644 index 0000000..e526d15 --- /dev/null +++ b/src/LunarWM_xr.h @@ -0,0 +1,13 @@ +#ifndef LUNAR_WM_XR_H +#define LUNAR_WM_XR_H + +#include "LunarWM_types.h" + +bool LunarWM_xr_init(LunarWM *wm); +void LunarWM_xr_cleanup(LunarWM *wm); +void LunarWM_xr_poll_events(LunarWM *wm); +bool LunarWM_xr_acquire_wait(XrSwapchain sc, uint32_t *idx); +GLuint LunarWM_xr_get_swapchain_image( + LunarWM *wm, int swapchain_images_i, uint32_t index); + +#endif