diff --git a/flake.lock b/flake.lock index 10df781..507f6a8 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1752950548, - "narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=", + "lastModified": 1754725699, + "narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c87b95e25065c028d31a94f06a62927d18763fdf", + "rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "type": "github" }, "original": { @@ -76,11 +76,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1752101817, - "narHash": "sha256-NUSPmdoPUYQDxFHnSJX9yV+tluZVmR62P4IjPTnm8dY=", + "lastModified": 1754816070, + "narHash": "sha256-Yyup5t4BQslBIip8rLb8kEbNs8iAB15Oaad9Y08P09U=", "owner": "slendidev", "repo": "wlroots-lunar", - "rev": "ef7aadec563747f8e1026b2c339d1132ae63f8a0", + "rev": "2fd8463ad8c78469744d854b3d155a3b55fc7ae1", "type": "github" }, "original": { diff --git a/lunarwm/init.lua b/lunarwm/init.lua index 05f4699..c9abf7a 100644 --- a/lunarwm/init.lua +++ b/lunarwm/init.lua @@ -11,9 +11,10 @@ return { keybindings = { { bind = main("Shift-Q"), action = lunar.quit_compositor }, { bind = main("Shift-R"), action = lunar.reload_config }, + { bind = main("R"), action = lunar.recenter }, + { bind = main("Tab"), action = lunar.cycle_next }, }, - space = { - offset = { 0, 0, 0 }, - radius = 0.7, + xwayland = { + lazy = false, }, } diff --git a/src/Config.c b/src/Config.c index a01fcae..5f22c44 100644 --- a/src/Config.c +++ b/src/Config.c @@ -1,5 +1,7 @@ #include "Config.h" + #include "common.h" +#include "lua_helpers.h" #include #include @@ -155,6 +157,16 @@ int config_load_ref(lua_State *L, int idx, Config *out) memset(out, 0, sizeof(*out)); + // ======== DEFAULTS ======== + out->space.offset = (Vector3) { 0, 0, 0 }; + out->space.initial_center = (Vector3) { 0, 1, 0 }; + out->space.radius = 0.7f; + out->space.window_scale = 0.001f; + // ====== END DEFAULTS ====== + + out->xwayland.enabled = true; + out->xwayland.lazy = true; + int cfg_abs = lua_absindex(L, idx); if (push_config_table_from_idx(L, cfg_abs) != 0) return -1; @@ -215,12 +227,12 @@ int config_load_ref(lua_State *L, int idx, Config *out) if (ok == 0) { free(arr); } else if (ok < n) { - BindingRef *shr = (BindingRef *)realloc(arr, ok * sizeof(*arr)); + BindingRef *shr = (BindingRef *)realloc(arr, ok * sizeof(*shr)); if (shr) arr = shr; } - out->keybindings.items = (ok ? arr : NULL); + out->keybindings.items = ok ? arr : NULL; out->keybindings.count = ok; } lua_pop(L, 1); @@ -240,6 +252,44 @@ int config_load_ref(lua_State *L, int idx, Config *out) } lua_pop(L, 1); + lua_getfield(L, -1, "space"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "offset"); + if (lua_istable(L, -1) || lua_isuserdata(L, -1)) + out->space.offset = lua_readVector3(L, lua_absindex(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, -1, "initial_center"); + if (lua_istable(L, -1) || lua_isuserdata(L, -1)) + out->space.initial_center = lua_readVector3(L, lua_absindex(L, -1)); + lua_pop(L, 1); + + lua_getfield(L, -1, "radius"); + if (lua_isnumber(L, -1)) + out->space.radius = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + + lua_getfield(L, -1, "window_scale"); + if (lua_isnumber(L, -1)) + out->space.window_scale = (float)lua_tonumber(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "xwayland"); + if (lua_istable(L, -1)) { + lua_getfield(L, -1, "enabled"); + if (lua_isboolean(L, -1)) + out->xwayland.enabled = lua_toboolean(L, -1); + lua_pop(L, 1); + + lua_getfield(L, -1, "lazy"); + if (lua_isboolean(L, -1)) + out->xwayland.lazy = lua_toboolean(L, -1); + lua_pop(L, 1); + } + lua_pop(L, 1); + lua_pop(L, 1); return 0; } diff --git a/src/Config.h b/src/Config.h index 46e8a50..8517b5b 100644 --- a/src/Config.h +++ b/src/Config.h @@ -1,9 +1,11 @@ #ifndef CONFIG_H #define CONFIG_H -#include #include +#include +#include + #include typedef struct { @@ -23,6 +25,18 @@ typedef struct { BindingRef *items; size_t count; } keybindings; + + struct { + Vector3 offset; + Vector3 initial_center; + float radius; + float window_scale; + } space; + + struct { + bool enabled; + bool lazy; + } xwayland; } Config; char const *get_config_path(void); diff --git a/src/LunarWM.c b/src/LunarWM.c index 0c0e7b2..cb8ca03 100644 --- a/src/LunarWM.c +++ b/src/LunarWM.c @@ -1,5 +1,7 @@ #include "LunarWM.h" +#include "common.h" + #include #include #include @@ -66,6 +68,7 @@ void toplevel_destroy_notify(struct wl_listener *l, void *) bool LunarWM_Toplevel_init( LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg) { + tl->id = vector_size(wm->wayland.v_toplevels) + 1; tl->server = wm; tl->xdg_toplevel = xdg; tl->surface = xdg->base->surface; @@ -128,6 +131,33 @@ bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) return true; } +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->xdg_toplevel->base->surface; + if (prev_surface == surface) { + return; + } + if (prev_surface) { + struct wlr_xdg_toplevel *prev_toplevel + = wlr_xdg_toplevel_try_from_wlr_surface(prev_surface); + if (prev_toplevel != NULL) { + wlr_xdg_toplevel_set_activated(prev_toplevel, false); + } + } + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(seat); + wlr_xdg_toplevel_set_activated(this->xdg_toplevel, 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) @@ -420,6 +450,13 @@ static bool init_wayland(LunarWM *this) 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); + + setenv("DISPLAY", this->wayland.xwayland->display_name, 1); + } + return true; } @@ -1052,6 +1089,34 @@ static bool init_xr(LunarWM *this) return true; } +static void sync_config(LunarWM *this) { } + +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) +{ + g_wm.renderer.center = g_wm.renderer.camera.position; + lua_pushnil(L); + return 1; +} + static int l_reload_config(lua_State *L) { ConfigManager *cm = g_wm.cman; @@ -1061,6 +1126,7 @@ static int l_reload_config(lua_State *L) } config_manager_reload(cm); + sync_config(&g_wm); lua_pushnil(L); return 1; } @@ -1068,7 +1134,8 @@ static int l_reload_config(lua_State *L) static int l_quit_compositor(lua_State *L) { LunarWM_terminate(&g_wm); - return 0; + lua_pushnil(L); + return 1; } bool LunarWM_init(LunarWM *this) @@ -1086,6 +1153,7 @@ bool LunarWM_init(LunarWM *this) this->renderer.camera.up = (Vector3) { 0, 1, 0 }; this->renderer.camera.fovy = 45; this->renderer.camera.projection = CAMERA_PERSPECTIVE; + this->renderer.center = (Vector3) { 0, 0, 0 }; } this->cman = config_manager_create(get_config_path()); @@ -1099,9 +1167,16 @@ bool LunarWM_init(LunarWM *this) 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_setglobal(L, "lunar"); config_manager_reload(this->cman); + sync_config(this); + + this->renderer.center = this->cman->cfg.space.initial_center; } if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { @@ -1227,6 +1302,11 @@ static void cleanup_wayland(LunarWM *this) { assert(this); + if (this->wayland.xwayland) { + 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); @@ -1488,28 +1568,108 @@ static void DrawBillboardNoShear( { Rectangle const src = { 0, 0, tex.width, tex.height }; - Vector2 const size = { scale * fabsf(src.width / src.height), scale }; + 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); } +// pass yFlip from tl->attribs.invert_y +static void DrawTexture3D( + Texture2D tex, Vector3 position, Vector3 target, float scale, bool yFlip) +{ + if (!tex.id) + return; + + Vector3 fwd = Vector3Normalize(Vector3Subtract(target, position)); + Vector3 upRef = (fabsf(fwd.y) > 0.99f) ? (Vector3) { 0, 0, 1 } + : (Vector3) { 0, 1, 0 }; + Vector3 right = Vector3Normalize(Vector3CrossProduct(fwd, upRef)); + Vector3 up = Vector3CrossProduct(right, fwd); + + float halfW = 0.5f * (float)tex.width * scale; + float halfH = 0.5f * (float)tex.height * scale; + + Vector3 tl + = Vector3Add(Vector3Subtract(position, Vector3Scale(right, halfW)), + Vector3Scale(up, halfH)); + Vector3 tr = Vector3Add(Vector3Add(position, Vector3Scale(right, halfW)), + Vector3Scale(up, halfH)); + Vector3 br + = Vector3Subtract(Vector3Add(position, Vector3Scale(right, halfW)), + Vector3Scale(up, halfH)); + Vector3 bl + = Vector3Subtract(Vector3Subtract(position, Vector3Scale(right, halfW)), + Vector3Scale(up, halfH)); + + float vt = yFlip ? 1.0f : 0.0f; + float vb = yFlip ? 0.0f : 1.0f; + + rlDisableBackfaceCulling(); // ensure visible regardless of winding + rlSetTexture(tex.id); + rlBegin(RL_QUADS); + rlNormal3f(fwd.x, fwd.y, fwd.z); + + // TL, TR, BR, BL with canonical UVs + rlTexCoord2f(1.0f, vt); + rlVertex3f(tl.x, tl.y, tl.z); + rlTexCoord2f(0.0f, vt); + rlVertex3f(tr.x, tr.y, tr.z); + rlTexCoord2f(0.0f, vb); + rlVertex3f(br.x, br.y, br.z); + rlTexCoord2f(1.0f, vb); + rlVertex3f(bl.x, bl.y, bl.z); + rlEnd(); + rlSetTexture(0); + rlEnableBackfaceCulling(); +} + +void render_hud(LunarWM *this, float /*dt*/, int hud_size) +{ + ClearBackground((Color) { 0, 0, 0, 0 }); + + 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 - 24, 24, WHITE); + + txt = TextFormat("DISPLAY=%s", getenv("DISPLAY")); + txt_w = MeasureText(txt, 24); + DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - 24 - 24, 24, 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, 32, WHITE); + } +} + void render_3d(LunarWM *this, float /*dt*/) { DrawGrid(10, 1); + rlDisableBackfaceCulling(); for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { auto *tl = this->wayland.v_toplevels[i]; LunarWM_Toplevel_update(tl); - DrawBillboardNoShear(this->renderer.camera, tl->rl_texture, - (Vector3) { 0, 1, -1.4f }, 1.0f, WHITE); + + DrawTexture3D(tl->rl_texture, + Vector3Add(this->renderer.center, + (Vector3) { 0, 0, this->cman->cfg.space.radius }), + this->renderer.center, this->cman->cfg.space.window_scale, false); } + rlEnableBackfaceCulling(); for (int h = 0; h < 2; ++h) { auto *handInfo = &this->xr.hands[h]; for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { - auto const *jl = &handInfo->joint_locations[k]; // NOLINT + auto const *jl = &handInfo->joint_locations[k]; Vector3 const pos = { jl->pose.position.x, jl->pose.position.y, @@ -1518,6 +1678,26 @@ void render_3d(LunarWM *this, float /*dt*/) DrawSphere(pos, jl->radius, RED); } } + + if (IsTextureValid(this->renderer.hud_rt.texture)) { + 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); + + rlEnableDepthTest(); + } } static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) @@ -1598,6 +1778,19 @@ static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) Matrix const viewOffL = MatrixMultiply(xr_matrix(views[0].pose), headView); Matrix const viewOffR = MatrixMultiply(xr_matrix(views[1].pose), headView); + int const hud_size = eyeH * 0.3; + 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 BeginTextureMode(this->renderer.tmp_rt); diff --git a/src/LunarWM.h b/src/LunarWM.h index 7060c57..6be17d6 100644 --- a/src/LunarWM.h +++ b/src/LunarWM.h @@ -26,13 +26,13 @@ #include #include #include +#include #include #include #include #include "Config.h" -#include "common.h" struct LunarWM; @@ -48,6 +48,8 @@ typedef struct { } LunarWM_Keyboard; typedef struct { + uint32_t id; + struct LunarWM *server; struct wl_listener commit; @@ -68,6 +70,7 @@ bool LunarWM_Toplevel_init( 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; @@ -125,7 +128,10 @@ typedef struct LunarWM { struct wlr_cursor *cursor; + struct wlr_xwayland *xwayland; + LunarWM_Toplevel **v_toplevels; + int current_focus; } wayland; struct { @@ -156,7 +162,10 @@ typedef struct LunarWM { struct { GLuint fbo; RenderTexture2D tmp_rt; + RenderTexture2D hud_rt; Camera3D camera; + + Vector3 center; } renderer; ConfigManager *cman; diff --git a/src/lua_helpers.h b/src/lua_helpers.h new file mode 100644 index 0000000..9fd1bfb --- /dev/null +++ b/src/lua_helpers.h @@ -0,0 +1,78 @@ +#ifndef LUA_HELPERS_H +#define LUA_HELPERS_H + +#include +#include +#include + +static Vector3 lua_readVector3(lua_State *L, int index) +{ + Vector3 v = { 0 }; + + if (!lua_istable(L, index)) + return v; + + lua_getfield(L, index, "x"); + if (lua_isnumber(L, -1)) { + v.x = lua_tonumber(L, -1); + lua_pop(L, 1); + + lua_getfield(L, index, "y"); + v.y = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + lua_getfield(L, index, "z"); + v.z = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + return v; + } + lua_pop(L, 1); + + lua_rawgeti(L, index, 1); + v.x = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + lua_rawgeti(L, index, 2); + v.y = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + lua_rawgeti(L, index, 3); + v.z = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + return v; +} + +static Vector2 lua_readVector2(lua_State *L, int index) +{ + Vector2 v = { 0 }; + + if (!lua_istable(L, index)) + return v; + + lua_getfield(L, index, "x"); + if (lua_isnumber(L, -1)) { + v.x = lua_tonumber(L, -1); + lua_pop(L, 1); + + lua_getfield(L, index, "y"); + v.y = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + return v; + } + lua_pop(L, 1); + + lua_rawgeti(L, index, 1); + v.x = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + lua_rawgeti(L, index, 2); + v.y = luaL_optnumber(L, -1, 0); + lua_pop(L, 1); + + return v; +} + +#endif // LUA_HELPERS_H