Initial commit

Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-06-29 15:57:38 +03:00
commit f74937bb6a
37 changed files with 7400 additions and 0 deletions

26
.clang-format Normal file
View File

@@ -0,0 +1,26 @@
UseTab: ForIndentation
TabWidth: 4
IndentWidth: 4
ColumnLimit: 80
AlignEscapedNewlines: DontAlign
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
BasedOnStyle: WebKit
BraceWrapping:
AfterFunction: true
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: true
BreakConstructorInitializers: BeforeComma
IndentPPDirectives: AfterHash
IndentRequiresClause: false
InsertNewlineAtEOF: true
LineEnding: LF
NamespaceIndentation: None
PointerAlignment: Right # east pointer
QualifierAlignment: Right # east const
RemoveSemicolon: true
RequiresClausePosition: WithFollowing
RequiresExpressionIndentation: OuterScope
SpaceAfterTemplateKeyword: false

0
.clangd Normal file
View File

2
.envrc Normal file
View File

@@ -0,0 +1,2 @@
use flake

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.cache
[Bb]uild*
/target
.direnv
log

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "wlroots-lunar"]
path = wlroots-lunar
url = git@github.com:slendidev/wlroots-lunar

127
CMakeLists.txt Normal file
View File

@@ -0,0 +1,127 @@
cmake_minimum_required(VERSION 3.31)
project(LunarWM LANGUAGES C)
set(CMAKE_C_STANDARD 23)
set(CMAKE_C_STANDARD_REQUIRED 23)
set(CMAKE_CXX_STANDARD 23)
add_compile_options(
-fstack-protector-strong
-fwrapv
)
add_compile_definitions(
WLR_USE_UNSTABLE
XR_USE_PLATFORM_EGL
XR_USE_GRAPHICS_API_OPENGL_ES
)
find_package(PkgConfig REQUIRED)
pkg_check_modules(WAYLAND REQUIRED IMPORTED_TARGET GLOBAL wayland-server)
pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl)
pkg_check_modules(GLES2 REQUIRED IMPORTED_TARGET glesv2)
pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots-0.20)
pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon)
pkg_check_modules(OPENXR REQUIRED IMPORTED_TARGET openxr)
pkg_check_modules(LUA REQUIRED IMPORTED_TARGET lua)
pkg_check_modules(PIXMAN REQUIRED IMPORTED_TARGET pixman-1)
find_program(WAYLAND_SCANNER_EXECUTABLE wayland-scanner REQUIRED)
message(STATUS "Found wayland-scanner at ${WAYLAND_SCANNER_EXECUTABLE}")
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
message(
STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}")
include(FetchContent)
FetchContent_Declare(
raylib
GIT_REPOSITORY https://github.com/slendidev/raylib.git
GIT_TAG "lunar"
GIT_SHALLOW 1
)
set(OPENGL_VERSION "ES 3.0")
set(PLATFORM DRM)
set(BUILD_EXAMPLES OFF)
FetchContent_MakeAvailable(raylib)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PUBLIC
src/vec.c
src/RayExt.c
src/Config.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
PkgConfig::XKBCOMMON
PkgConfig::WAYLAND
PkgConfig::EGL
PkgConfig::GLES2
PkgConfig::WLROOTS
PkgConfig::OPENXR
PkgConfig::LUA
PkgConfig::PIXMAN
raylib
)
# Wayland protocol codegen
set(XDG_SHELL_XML
${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml
)
set(XDG_SHELL_HEADER
${CMAKE_BINARY_DIR}/xdg-shell-protocol.h
)
set(XDG_SHELL_C
${CMAKE_BINARY_DIR}/xdg-shell-protocol.c
)
add_custom_command(
OUTPUT ${XDG_SHELL_HEADER}
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} server-header
${XDG_SHELL_XML} ${XDG_SHELL_HEADER}
DEPENDS ${XDG_SHELL_XML}
COMMENT "Generating xdg-shell-protocol.h (server header)"
VERBATIM
)
add_custom_command(
OUTPUT ${XDG_SHELL_C}
COMMAND ${WAYLAND_SCANNER_EXECUTABLE} private-code
${XDG_SHELL_XML} ${XDG_SHELL_C}
DEPENDS ${XDG_SHELL_XML}
COMMENT "Generating xdg-shell-protocol.c"
VERBATIM
)
add_custom_target(xdg_shell_protocol
DEPENDS ${XDG_SHELL_HEADER} ${XDG_SHELL_C}
)
add_library(xdg_shell STATIC
${XDG_SHELL_C}
)
add_dependencies(xdg_shell xdg_shell_protocol)
target_include_directories(xdg_shell PUBLIC
${CMAKE_BINARY_DIR}
)
add_dependencies(${PROJECT_NAME} xdg_shell_protocol)
target_link_libraries(${PROJECT_NAME} PRIVATE xdg_shell)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_BINARY_DIR}
)

722
assets/hand_l.gltf Normal file

File diff suppressed because one or more lines are too long

722
assets/hand_r.gltf Normal file

File diff suppressed because one or more lines are too long

18
assets/linear_srgb.fs Normal file
View File

@@ -0,0 +1,18 @@
precision mediump float;
varying vec2 fragTexCoord;
varying vec4 fragColor;
uniform sampler2D texture0;
vec3 srgb_to_linear(vec3 c) {
bvec3 cutoff = lessThanEqual(c, vec3(0.04045));
vec3 low = c / 12.92;
vec3 high = pow((c + 0.055) / 1.055, vec3(2.4));
return mix(high, low, vec3(cutoff));
}
void main() {
vec4 c = texture2D(texture0, fragTexCoord) * fragColor;
c.rgb = srgb_to_linear(c.rgb); // decode to linear
gl_FragColor = c;
}

116
flake.lock generated Normal file
View File

@@ -0,0 +1,116 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1759036355,
"narHash": "sha256-0m27AKv6ka+q270dw48KflE0LwQYrO7Fm4/2//KCVWg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e9f00bd893984bc8ce46c895c3bf7cac95331127",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"wlroots-lunar": "wlroots-lunar"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"wlroots-lunar": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1759266345,
"narHash": "sha256-BJ+CTRXaFArVFgJfL19QpoR7Ebk8HU63Lz0+jQvhV3Y=",
"owner": "slendidev",
"repo": "wlroots-lunar",
"rev": "1179ca07821decbff320eafd7ffb3caaadcefbf4",
"type": "github"
},
"original": {
"owner": "slendidev",
"repo": "wlroots-lunar",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

88
flake.nix Normal file
View File

@@ -0,0 +1,88 @@
{
description = "LunarWM is a VR-based Wayland compositor";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
wlroots-lunar = {
url = "github:slendidev/wlroots-lunar";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
{
self,
nixpkgs,
flake-utils,
wlroots-lunar,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
system = system;
config.cudaSupport = true;
config.allowUnfree = true;
};
in
{
devShells.default = pkgs.mkShell.override { stdenv = pkgs.llvmPackages_20.libcxxStdenv; } {
hardeningDisable = [ "fortify" ];
packages = with pkgs; [
pkg-config
cmake
ninja
(pkgs.llvmPackages_20.clang-tools.override { enableLibcxx = true; })
lldb
lua
# For wlroots
libxkbcommon
libxkbcommon.dev
libdrm
xorg.libxcb
pixman
libgbm
lcms2
seatd
libdisplay-info
libliftoff
libinput
xorg.xcbutilrenderutil
xorg.xcbutilwm
xorg.xcbutilerrors
vulkan-loader
wlroots-lunar.packages."${system}".default
# For raylib
xorg.libXrandr
xorg.libXinerama
xorg.libXcursor
xorg.libXi
glfw
boost
libffi
wayland
wayland-scanner
wayland-protocols
openxr-loader
libGL
glm
xorg.libX11
xorg.libXau
xorg.libXdmcp
];
shellHook = ''
export NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE -B${pkgs.llvmPackages_20.libcxx}/lib -I${pkgs.llvmPackages_20.libcxx.dev}/include/c++/v1"
'';
};
}
);
}

34
launch_settings.cap Normal file
View File

@@ -0,0 +1,34 @@
{
"rdocCaptureSettings": 1,
"settings": {
"autoStart": false,
"commandLine": "",
"environment": [
{
"separator": "Platform style",
"type": "Set",
"value": "1",
"variable": "LWM_NO_XR"
}
],
"executable": "/home/lain/Documents/projs/lunarwm/build/LunarWM",
"inject": false,
"numQueuedFrames": 0,
"options": {
"allowFullscreen": true,
"allowVSync": true,
"apiValidation": false,
"captureAllCmdLists": false,
"captureCallstacks": false,
"captureCallstacksOnlyDraws": false,
"debugOutputMute": true,
"delayForDebugger": 0,
"hookIntoChildren": false,
"refAllResources": false,
"softMemoryLimit": 0,
"verifyBufferAccess": false
},
"queuedFrameCap": 0,
"workingDir": "/home/lain/Documents/projs/lunarwm"
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 MiB

BIN
lunarwm/cubemap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

34
lunarwm/init.lua Normal file
View File

@@ -0,0 +1,34 @@
function main(kbd)
return "Super-" .. kbd
end
return {
input = {
keyboard = {
xkb_options = { "altwin:swap_lalt_lwin" },
},
mouse = {
invert_y = false,
},
},
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 },
{ bind = main("Space"), action = function() lunar.exec("kitty") end },
},
space = {
radius = 1.0,
},
displays = {
hud = {
size = 720,
font_size = 24,
},
virtual = {
resolution = { 2560, 1440 },
},
},
cubemap = 'cubemap.png',
}

493
src/Config.c Normal file
View File

@@ -0,0 +1,493 @@
#include "Config.h"
#include "common.h"
#include "lua_helpers.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <lauxlib.h>
#include <lualib.h>
#include <wlr/types/wlr_keyboard.h>
#include <xkbcommon/xkbcommon.h>
char const *get_config_path(void)
{
static char const *paths[] = {
"lunarwm/init.lua",
};
for (size_t i = 0; i < ARRAY_SZ(paths); ++i) {
char const *p = paths[i];
struct stat s;
if (stat(p, &s) == 0)
return p;
}
return NULL;
}
static int dupstr(char const *s, char **out)
{
if (!s) {
*out = NULL;
return 0;
}
size_t n = strlen(s) + 1;
char *m = (char *)malloc(n);
if (!m)
return -1;
memcpy(m, s, n);
*out = m;
return 0;
}
static uint32_t mod_from_token(char const *t)
{
if (!t)
return 0;
if (strcasecmp(t, "Super") == 0)
return WLR_MODIFIER_LOGO;
if (strcasecmp(t, "Shift") == 0)
return WLR_MODIFIER_SHIFT;
if (strcasecmp(t, "Ctrl") == 0 || strcasecmp(t, "Control") == 0)
return WLR_MODIFIER_CTRL;
if (strcasecmp(t, "Alt") == 0)
return WLR_MODIFIER_ALT;
return 0;
}
static int parse_bind(
char const *bind, uint32_t *mods_out, xkb_keysym_t *sym_out)
{
if (!bind || !mods_out || !sym_out)
return -1;
char buf[256];
strncpy(buf, bind, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
char *save = NULL;
char *tok = strtok_r(buf, "-", &save);
char *last = tok;
while (tok) {
last = tok;
tok = strtok_r(NULL, "-", &save);
}
strncpy(buf, bind, sizeof buf - 1);
buf[sizeof buf - 1] = 0;
save = NULL;
uint32_t mods = 0;
for (char *t = strtok_r(buf, "-", &save); t;
t = strtok_r(NULL, "-", &save)) {
if (t == last)
break;
mods |= mod_from_token(t);
}
int flags = XKB_KEYSYM_CASE_INSENSITIVE;
xkb_keysym_t sym = xkb_keysym_from_name(last, flags);
if (sym == XKB_KEY_NoSymbol)
return -1;
*mods_out = mods;
*sym_out = sym;
return 0;
}
static int push_config_table_from_idx(lua_State *L, int idx_abs)
{
if (!lua_istable(L, idx_abs))
return luaL_error(L, "config: expected table at index %d", idx_abs);
lua_pushvalue(L, idx_abs);
return 0;
}
static int join_string_array(lua_State *L, int idx_abs, char **out)
{
*out = NULL;
if (!lua_istable(L, idx_abs))
return 0;
size_t n = (size_t)lua_rawlen(L, idx_abs);
if (n == 0)
return 0;
size_t total = 1;
for (size_t i = 1; i <= n; ++i) {
lua_rawgeti(L, idx_abs, (lua_Integer)i);
size_t len = 0;
(void)lua_tolstring(L, -1, &len);
total += len + (i < n ? 1 : 0);
lua_pop(L, 1);
}
char *buf = (char *)malloc(total);
if (!buf)
return -1;
size_t off = 0;
for (size_t i = 1; i <= n; ++i) {
lua_rawgeti(L, idx_abs, (lua_Integer)i);
size_t len = 0;
char const *s = lua_tolstring(L, -1, &len);
if (!s) {
lua_pop(L, 1);
free(buf);
return -1;
}
memcpy(buf + off, s, len);
off += len;
lua_pop(L, 1);
if (i != n)
buf[off++] = ',';
}
buf[off] = 0;
*out = buf;
return 0;
}
int config_load_ref(lua_State *L, int idx, Config *out)
{
if (!L || !out)
return -1;
memset(out, 0, sizeof(*out));
// ======== DEFAULTS ========
out->space.offset = (Vector3) { 0, 0, 0 };
out->space.radius = 1.0f;
out->space.window_scale = 0.001f;
out->displays.hud.size = 720;
out->displays.hud.font_size = 24;
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);
if (push_config_table_from_idx(L, cfg_abs) != 0)
return -1;
lua_getfield(L, -1, "keybindings");
if (!lua_istable(L, -1)) {
lua_pop(L, 2);
return luaL_error(L, "config: 'keybindings' must be a table (array)");
}
size_t n = (size_t)lua_rawlen(L, -1);
if (n) {
BindingRef *arr = (BindingRef *)calloc(n, sizeof(*arr));
if (!arr) {
lua_pop(L, 2);
return luaL_error(L, "config: OOM");
}
size_t ok = 0;
for (size_t i = 0; i < n; ++i) {
lua_rawgeti(L, -1, (lua_Integer)(i + 1));
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
continue;
}
lua_getfield(L, -1, "bind");
char const *bind = lua_tostring(L, -1);
if (!bind) {
lua_pop(L, 2);
continue;
}
uint32_t mods = 0;
xkb_keysym_t sym = XKB_KEY_NoSymbol;
if (parse_bind(bind, &mods, &sym) != 0) {
lua_pop(L, 2);
continue;
}
lua_getfield(L, -2, "action");
if (!lua_isfunction(L, -1)) {
lua_pop(L, 3);
continue;
}
int ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
arr[ok].mods_mask = mods;
arr[ok].sym = sym;
arr[ok].action_ref = ref;
++ok;
lua_pop(L, 1);
}
if (ok == 0) {
free(arr);
} else if (ok < n) {
BindingRef *shr = (BindingRef *)realloc(arr, ok * sizeof(*shr));
if (shr)
arr = shr;
}
out->keybindings.items = ok ? arr : NULL;
out->keybindings.count = ok;
}
lua_pop(L, 1);
lua_getfield(L, -1, "input");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "keyboard");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "xkb_options");
if (lua_istable(L, -1)) {
(void)join_string_array(
L, lua_absindex(L, -1), &out->input.keyboard.xkb_options);
}
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);
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, "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_getfield(L, -1, "displays");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "hud");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "size");
if (lua_isnumber(L, -1))
out->displays.hud.size = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "font_size");
if (lua_isnumber(L, -1))
out->displays.hud.font_size = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_getfield(L, -1, "virtual");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "resolution");
if (lua_istable(L, -1) || lua_isuserdata(L, -1))
out->displays.virtual.resolution
= lua_readVector2(L, lua_absindex(L, -1));
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
lua_getfield(L, -1, "cubemap");
if (lua_isstring(L, -1)) {
char const *s = lua_tostring(L, -1);
if (s && s[0]) {
(void)dupstr(s, (char **)&out->cubemap);
}
}
lua_pop(L, 1);
lua_pop(L, 1);
return 0;
}
void config_unref(lua_State *L, Config *cfg)
{
if (!cfg)
return;
for (size_t i = 0; i < cfg->keybindings.count; ++i) {
int r = cfg->keybindings.items ? cfg->keybindings.items[i].action_ref
: LUA_NOREF;
if (r != LUA_NOREF && r != LUA_REFNIL)
luaL_unref(L, LUA_REGISTRYINDEX, r);
}
free(cfg->keybindings.items);
cfg->keybindings.items = NULL;
cfg->keybindings.count = 0;
free(cfg->input.keyboard.xkb_options);
cfg->input.keyboard.xkb_options = NULL;
if (cfg->cubemap) {
free((void *)cfg->cubemap);
cfg->cubemap = NULL;
}
}
void config_trigger_ref(lua_State *L, Config *cfg, int ref)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (!lua_isfunction(L, -1)) {
lua_pop(L, 1);
return;
}
if (lua_pcall(L, 0, 0, 0) != LUA_OK) {
fprintf(stderr, "config: action error: %s\n", lua_tostring(L, -1));
lua_pop(L, 1);
return;
}
}
static int load_config_file(lua_State *L, char const *path)
{
if (!path || !path[0])
return -1;
if (luaL_loadfile(L, path) != LUA_OK) {
char const *err = lua_tostring(L, -1);
fprintf(
stderr, "config: loadfile failed: %s\n", err ? err : "(unknown)");
lua_pop(L, 1);
return -1;
}
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
char const *err = lua_tostring(L, -1);
fprintf(stderr, "config: executing '%s' failed: %s\n", path,
err ? err : "(unknown)");
lua_pop(L, 1);
return -1;
}
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
fprintf(stderr, "config: '%s' did not return a table\n", path);
return -1;
}
return 0;
}
ConfigManager *config_manager_create(char const *path)
{
ConfigManager *cm = (ConfigManager *)calloc(1, sizeof(*cm));
if (!cm)
return NULL;
cm->L = luaL_newstate();
if (!cm->L) {
free(cm);
return NULL;
}
luaL_openlibs(cm->L);
if (!path)
path = get_config_path();
if (path && dupstr(path, &cm->path) != 0) {
lua_close(cm->L);
free(cm);
return NULL;
}
return cm;
}
void config_manager_destroy(ConfigManager *cm)
{
if (!cm)
return;
config_unref(cm->L, &cm->cfg);
if (cm->L)
lua_close(cm->L);
free(cm->path);
free(cm);
}
int config_manager_reload(ConfigManager *cm)
{
if (!cm || !cm->path)
return -1;
config_unref(cm->L, &cm->cfg);
if (load_config_file(cm->L, cm->path) != 0)
return -1;
int rc = config_load_ref(cm->L, -1, &cm->cfg);
lua_pop(cm->L, 1);
if (rc != 0)
return rc;
if (cm->cfg.cubemap && cm->cfg.cubemap[0] != '/') {
char const *slash = strrchr(cm->path, '/');
char const *dir = ".";
size_t dirlen = 1;
if (slash) {
dir = cm->path;
dirlen = (size_t)(slash - cm->path);
}
size_t n = dirlen + 1 + strlen(cm->cfg.cubemap)
+ 1; // dir + '/' + hdri + '\0'
char *full = (char *)malloc(n);
if (full) {
memcpy(full, dir, dirlen);
full[dirlen] = '/';
strcpy(full + dirlen + 1, cm->cfg.cubemap);
free((void *)cm->cfg.cubemap);
cm->cfg.cubemap = full;
}
}
return 0;
}
lua_State *config_manager_lua(ConfigManager *cm) { return cm ? cm->L : NULL; }
Config const *config_manager_get(ConfigManager *cm)
{
return cm ? &cm->cfg : NULL;
}
char const *config_manager_path(ConfigManager *cm)
{
return cm ? cm->path : NULL;
}

79
src/Config.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <stddef.h>
#include <lua.h>
#include <raylib.h>
#include <xkbcommon/xkbcommon.h>
typedef struct {
xkb_keysym_t sym;
uint32_t mods_mask;
int action_ref;
} BindingRef;
typedef struct {
struct {
struct {
char *xkb_options;
} keyboard;
struct {
bool invert_x;
bool invert_y;
} mouse;
} input;
struct {
BindingRef *items;
size_t count;
} keybindings;
struct {
Vector3 offset;
float radius;
float window_scale;
} space;
struct {
bool enabled;
bool lazy;
} xwayland;
struct {
struct {
int size;
float font_size;
} hud;
struct {
Vector2 resolution;
} virtual;
} displays;
char const *cubemap;
} Config;
char const *get_config_path(void);
int config_load_ref(lua_State *L, int idx, Config *out);
void config_unref(lua_State *L, Config *cfg);
void config_trigger_ref(lua_State *L, Config *cfg, int ref);
struct ConfigManager {
lua_State *L;
Config cfg;
char *path;
};
typedef struct ConfigManager ConfigManager;
ConfigManager *config_manager_create(char const *path);
void config_manager_destroy(ConfigManager *cm);
int config_manager_reload(ConfigManager *cm);
lua_State *config_manager_lua(ConfigManager *cm);
Config const *config_manager_get(ConfigManager *cm);
char const *config_manager_path(ConfigManager *cm);
#endif // CONFIG_H

7
src/LunarWM.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef LUNAR_WM_H
#define LUNAR_WM_H
#include "LunarWM_core.h"
#include "LunarWM_types.h"
#endif

434
src/LunarWM_core.c Normal file
View File

@@ -0,0 +1,434 @@
#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 <assert.h>
#include <math.h>
#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
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->xr.hand_tracking_system_properties.next = NULL;
wm->xr.hand_tracking_system_properties.supportsHandTracking = XR_FALSE;
wm->xr.hand_tracking_enabled = false;
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;
}
wm->xr.available = false;
{
char *no_xr = getenv("LWM_NO_XR");
bool xr = true;
if (no_xr != NULL && no_xr[0] != '\0')
xr = false;
if (xr) {
if (!LunarWM_xr_init(wm)) {
wlr_log(
WLR_ERROR, "Failed to initialize OpenXR! Disabling XR...");
LunarWM_xr_cleanup(wm);
} else {
wm->xr.available = true;
}
}
}
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();
if (wm->xr.available) {
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_enabled) {
for (size_t i = 0; i < 2; i++) {
LunarWM_Hand *hand = &wm->xr.hands[i];
bool const unobstructed = true;
if (!wm->xr.LocateHandJointsEXT
|| hand->hand_tracker == XR_NULL_HANDLE) {
continue;
}
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();
} else {
wm->renderer.camera.fovy = 75;
}
}
}

14
src/LunarWM_core.h Normal file
View File

@@ -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

675
src/LunarWM_render.c Normal file
View File

@@ -0,0 +1,675 @@
#include "LunarWM_render.h"
#include "LunarWM_core.h"
#include "LunarWM_xr.h"
#include "common.h"
#include "vec.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
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->composed_has_alpha) {
continue;
}
Texture2D tex = tl->rl_texture;
bool y_flip = false;
if (IsRenderTextureValid(tl->surface_rt)) {
tex = tl->surface_rt.texture;
tex.width = tl->rl_texture.width;
tex.height = tl->rl_texture.height;
y_flip = true;
}
if (!tex.id)
continue;
float rad = window->coord.r - 0.01f * (float)i;
DrawTextureCyl2(tex, Vector3Zero(), window->coord, rad,
this->cman->cfg.space.window_scale, y_flip);
}
}
}
void LunarWM_render_3d(LunarWM *this, float /*dt*/)
{
LunarWM_render_windows(this, true);
for (int h = 0; this->xr.hand_tracking_enabled && h < 2; ++h) {
auto *hand_info = &this->xr.hands[h];
if (hand_info->hand_tracker == XR_NULL_HANDLE)
continue;
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);
LunarWM_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;
}

11
src/LunarWM_render.h Normal file
View File

@@ -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);
void LunarWM_render_3d(LunarWM *this, float dt);
#endif

293
src/LunarWM_types.h Normal file
View File

@@ -0,0 +1,293 @@
#ifndef LUNAR_WM_TYPES_H
#define LUNAR_WM_TYPES_H
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <GLES3/gl3.h>
#include <GLES3/gl31.h>
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>
#include <wayland-server-core.h>
#include <wlr/backend/session.h>
#include <wlr/backend/wayland.h>
#include <wlr/render/allocator.h>
#include <wlr/render/egl.h>
#include <wlr/render/gles2.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_data_device.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_subcompositor.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
#include <wlr/xwayland/xwayland.h>
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
#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 {
struct LunarWM *wm;
struct wlr_output *wlr_output;
struct wl_listener frame;
struct wl_listener destroy;
} LunarWM_Output;
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;
RenderTexture2D surface_rt;
struct wlr_box surface_extents;
bool composed_has_alpha;
} 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 wl_listener new_surface_listener;
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;
struct wl_listener new_output_listener;
LunarWM_Output **v_outputs;
} wayland;
struct {
bool available;
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;
bool hand_tracking_enabled;
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

2043
src/LunarWM_wayland.c Normal file

File diff suppressed because it is too large Load Diff

11
src/LunarWM_wayland.h Normal file
View File

@@ -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

909
src/LunarWM_xr.c Normal file
View File

@@ -0,0 +1,909 @@
#include "LunarWM_xr.h"
#include "common.h"
#include "vec.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
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 *required_instance_extensions[] = {
XR_EXT_DEBUG_UTILS_EXTENSION_NAME,
XR_MNDX_EGL_ENABLE_EXTENSION_NAME,
XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME,
};
char const *optional_instance_extensions[] = {
XR_EXT_HAND_TRACKING_EXTENSION_NAME,
};
bool hand_tracking_ext_available = false;
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(required_instance_extensions); i++) {
char const *requested_instance_extension
= required_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;
}
}
for (size_t i = 0; i < ARRAY_SZ(optional_instance_extensions); i++) {
char const *requested_instance_extension
= optional_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_INFO, "Optional OpenXR instance extension missing: %s",
requested_instance_extension);
} else if (strcmp(requested_instance_extension,
XR_EXT_HAND_TRACKING_EXTENSION_NAME)
== 0) {
hand_tracking_ext_available = true;
}
}
{
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;
this->xr.CreateHandTrackerEXT = NULL;
this->xr.DestroyHandTrackerEXT = NULL;
this->xr.LocateHandJointsEXT = NULL;
if (hand_tracking_ext_available) {
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 "
"(optional): %d",
res);
hand_tracking_ext_available = false;
}
}
if (hand_tracking_ext_available) {
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 "
"(optional): %d",
res);
hand_tracking_ext_available = false;
}
}
if (hand_tracking_ext_available) {
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 (optional): "
"%d",
res);
hand_tracking_ext_available = 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;
}
}
{
this->xr.hand_tracking_enabled = hand_tracking_ext_available;
XrSystemProperties system_props = {
.type = XR_TYPE_SYSTEM_PROPERTIES,
.next = this->xr.hand_tracking_enabled
? &this->xr.hand_tracking_system_properties
: NULL,
};
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;
}
if (this->xr.hand_tracking_enabled
&& !this->xr.hand_tracking_system_properties.supportsHandTracking) {
wlr_log(WLR_INFO,
"Hand tracking extension present but system does not support "
"it");
this->xr.hand_tracking_enabled = 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));
wlr_log(WLR_DEBUG, "Creating XR stuff..");
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!
wlr_log(WLR_DEBUG, "Creating XR swapchains...");
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_COMPONENT24;
auto const swapchain_format_color = GL_RGBA8;
{
bool found = false;
for (size_t i = 0; i < a_swapchain_formats_count; i++) {
if (a_swapchain_formats[i] == swapchain_format_color) {
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_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_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);
wlr_log(WLR_DEBUG, "Fetching blend modes...");
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;
}
wlr_log(WLR_DEBUG, "Getting reference space...");
{ // 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;
}
}
if (this->xr.hand_tracking_enabled && this->xr.CreateHandTrackerEXT) {
bool hand_trackers_created = true;
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);
hand_trackers_created = false;
break;
}
}
if (!hand_trackers_created) {
if (this->xr.DestroyHandTrackerEXT) {
for (size_t i = 0; i < 2; i++) {
auto *hand = &this->xr.hands[i];
if (hand->hand_tracker != XR_NULL_HANDLE) {
this->xr.DestroyHandTrackerEXT(hand->hand_tracker);
hand->hand_tracker = XR_NULL_HANDLE;
}
}
}
this->xr.hand_tracking_enabled = false;
}
}
if (!this->xr.hand_tracking_enabled) {
for (size_t i = 0; i < 2; i++) {
this->xr.hands[i].hand_tracker = XR_NULL_HANDLE;
}
this->xr.hand_tracking_system_properties.supportsHandTracking
= XR_FALSE;
}
free(extension_properties);
free(a_view_config_types);
free(a_swapchain_formats);
free(a_environment_blend_modes);
vector_free(v_active_instance_extensions);
wlr_log(WLR_INFO, "OpenXR initialized.");
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;
}

13
src/LunarWM_xr.h Normal file
View File

@@ -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

118
src/RayExt.c Normal file
View File

@@ -0,0 +1,118 @@
#include "RayExt.h"
#include <GLES2/gl2.h>
#include <raylib.h>
#include <raymath.h>
#include <rlgl.h>
static char const *SKYBOX_VS
= "#version 300 es\n"
"precision mediump float;\n"
"layout(location=0) in vec3 vertexPosition;\n"
"uniform mat4 mvp;\n"
"out vec3 vDir;\n"
"void main(){\n"
"\tvDir = vertexPosition;\n"
"\tvec4 pos = mvp * vec4(vertexPosition, 1.0);\n"
"\tgl_Position = vec4(pos.xy, pos.w, pos.w); // z := 1 after division\n"
"}\n";
static char const *SKYBOX_FS
= "#version 300 es\n"
"precision highp float;\n"
"in vec3 vDir;\n"
"uniform samplerCube environmentMap;\n"
"out vec4 finalColor;\n"
"void main(){ vec3 dir=normalize(vDir); "
"finalColor=vec4(texture(environmentMap, normalize(vDir)).rgb, 1.0); }\n";
void Skybox_init(Skybox *skybox, char const *fp)
{
if (skybox->ok) {
Skybox_destroy(skybox);
}
// 1) Load cubemap from a 3x4 cross image
Image img = LoadImage(fp);
if (img.width == 0 || img.height == 0) {
TraceLog(LOG_ERROR, "Skybox: failed to load image: %s", fp);
skybox->ok = false;
return;
}
TextureCubemap cubemap
= LoadTextureCubemap(img, CUBEMAP_LAYOUT_AUTO_DETECT);
UnloadImage(img);
if (cubemap.id == 0) {
TraceLog(LOG_ERROR, "Skybox: failed to create cubemap from %s", fp);
skybox->ok = false;
return;
}
// 2) Make an inward-facing cube mesh
Mesh m = GenMeshCube(
2.0f, 2.0f, 2.0f); // size doesn't matter; depth writes off
// Invert winding so we see the inside
for (int i = 0; i < m.triangleCount; ++i) {
unsigned short *idx = &m.indices[i * 3];
unsigned short tmp = idx[1];
idx[1] = idx[2];
idx[2] = tmp;
}
UploadMesh(&m, false);
Model cube = LoadModelFromMesh(m);
Shader sh = LoadShaderFromMemory(SKYBOX_VS, SKYBOX_FS);
// make raylib aware which sampler is the cubemap
sh.locs[SHADER_LOC_MAP_CUBEMAP] = GetShaderLocation(sh, "environmentMap");
cube.materials[0].shader = sh;
// put the cubemap in the expected material slot
cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = cubemap;
// nicer defaults
SetTextureWrap(cubemap, TEXTURE_WRAP_CLAMP);
SetTextureFilter(cubemap, TEXTURE_FILTER_BILINEAR);
skybox->cubemap = cubemap;
skybox->shader = sh;
skybox->cube = cube;
skybox->ok = true;
}
void Skybox_destroy(Skybox *skybox)
{
if (!skybox->ok)
return;
UnloadModel(skybox->cube); // also unloads Mesh
UnloadTexture(skybox->cubemap);
UnloadShader(skybox->shader);
*skybox = (Skybox) { 0 };
}
void Skybox_draw(Skybox const skybox, Vector3 camPos)
{
if (!skybox.ok)
return;
rlDisableBackfaceCulling();
rlDisableDepthMask();
rlDisableColorBlend();
rlDrawRenderBatchActive();
GLint oldFunc = 0;
glGetIntegerv(GL_DEPTH_FUNC, &oldFunc);
glDepthFunc(GL_LEQUAL);
DrawModel(skybox.cube, camPos, 500.0f, (Color) { 255, 255, 255, 255 });
rlDrawRenderBatchActive();
glDepthFunc(oldFunc ? oldFunc : GL_LESS);
rlEnableColorBlend();
rlEnableDepthMask();
rlEnableBackfaceCulling();
}

18
src/RayExt.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef RAYEXT_H
#define RAYEXT_H
#include <raylib.h>
typedef struct {
bool ok;
Model cube;
Shader shader;
TextureCubemap cubemap;
} Skybox;
void Skybox_init(Skybox *skybox, char const *fp);
void Skybox_destroy(Skybox *skybox);
void Skybox_draw(Skybox const skybox, Vector3 position);
#endif // RAYEXT_H

6
src/common.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef COMMON_H
#define COMMON_H
#define ARRAY_SZ(arr) (sizeof(arr) / sizeof(*arr))
#endif // COMMON_H

78
src/lua_helpers.h Normal file
View File

@@ -0,0 +1,78 @@
#ifndef LUA_HELPERS_H
#define LUA_HELPERS_H
#include <lauxlib.h>
#include <lua.h>
#include <raylib.h>
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

16
src/main.c Normal file
View File

@@ -0,0 +1,16 @@
#include <assert.h>
#include <signal.h>
#include "LunarWM.h"
LunarWM g_wm = {};
void sigint_handler(int) { LunarWM_terminate(&g_wm); }
int main(void)
{
assert(LunarWM_init(&g_wm));
signal(SIGINT, sigint_handler);
LunarWM_run(&g_wm);
LunarWM_destroy(&g_wm);
}

148
src/vec.c Normal file
View File

@@ -0,0 +1,148 @@
/*
BSD 3-Clause License
Copyright (c) 2024, Mashpoe
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "vec.h"
#include <string.h>
typedef struct
{
vec_size_t size;
vec_size_t capacity;
unsigned char data[];
} vector_header;
vector_header* vector_get_header(vector vec) { return &((vector_header*)vec)[-1]; }
vector vector_create(void)
{
vector_header* h = (vector_header*)malloc(sizeof(vector_header));
h->capacity = 0;
h->size = 0;
return &h->data;
}
void vector_free(vector vec) { free(vector_get_header(vec)); }
vec_size_t vector_size(vector vec) { return vector_get_header(vec)->size; }
vec_size_t vector_capacity(vector vec) { return vector_get_header(vec)->capacity; }
vector_header* vector_realloc(vector_header* h, vec_type_t type_size)
{
vec_size_t new_capacity = (h->capacity == 0) ? 1 : h->capacity * 2;
vector_header* new_h = (vector_header*)realloc(h, sizeof(vector_header) + new_capacity * type_size);
new_h->capacity = new_capacity;
return new_h;
}
bool vector_has_space(vector_header* h)
{
return h->capacity - h->size > 0;
}
void* _vector_add_dst(vector* vec_addr, vec_type_t type_size)
{
vector_header* h = vector_get_header(*vec_addr);
if (!vector_has_space(h))
{
h = vector_realloc(h, type_size);
*vec_addr = h->data;
}
return &h->data[type_size * h->size++];
}
void* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos)
{
vector_header* h = vector_get_header(*vec_addr);
vec_size_t new_length = h->size + 1;
// make sure there is enough room for the new element
if (!vector_has_space(h))
{
h = vector_realloc(h, type_size);
*vec_addr = h->data;
}
// move trailing elements
memmove(&h->data[(pos + 1) * type_size],
&h->data[pos * type_size],
(h->size - pos) * type_size);
h->size = new_length;
return &h->data[pos * type_size];
}
void _vector_erase(vector vec, vec_type_t type_size, vec_size_t pos, vec_size_t len)
{
vector_header* h = vector_get_header(vec);
memmove(&h->data[pos * type_size],
&h->data[(pos + len) * type_size],
(h->size - pos - len) * type_size);
h->size -= len;
}
void _vector_remove(vector vec, vec_type_t type_size, vec_size_t pos)
{
_vector_erase(vec, type_size, pos, 1);
}
void vector_pop(vector vec) { --vector_get_header(vec)->size; }
void _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity)
{
vector_header* h = vector_get_header(*vec_addr);
if (h->capacity >= capacity)
{
return;
}
h = (vector_header*)realloc(h, sizeof(vector_header) + capacity * type_size);
h->capacity = capacity;
*vec_addr = &h->data;
}
vector _vector_copy(vector vec, vec_type_t type_size)
{
vector_header* h = vector_get_header(vec);
size_t alloc_size = sizeof(vector_header) + h->size * type_size;
vector_header* copy_h = (vector_header*)malloc(alloc_size);
memcpy(copy_h, h, alloc_size);
copy_h->capacity = copy_h->size;
return &copy_h->data;
}

128
src/vec.h Normal file
View File

@@ -0,0 +1,128 @@
/*
BSD 3-Clause License
Copyright (c) 2024, Mashpoe
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef vec_h
#define vec_h
#ifdef __cpp_decltype
# include <type_traits>
# define typeof(T) \
std::remove_reference< \
std::add_lvalue_reference<decltype(T)>::type>::type
#endif
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdlib.h>
// generic type for internal use
typedef void *vector;
// number of elements in a vector
typedef size_t vec_size_t;
// number of bytes for a type
typedef size_t vec_type_t;
// TODO: more rigorous check for typeof support with different compilers
#if _MSC_VER == 0 || __STDC_VERSION__ >= 202311L || defined __cpp_decltype
// shortcut defines
// vec_addr is a vector* (aka type**)
# define vector_add_dst(vec_addr) \
((typeof(*vec_addr))(_vector_add_dst( \
(vector *)vec_addr, sizeof(**vec_addr))))
# define vector_insert_dst(vec_addr, pos) \
((typeof(*vec_addr))(_vector_insert_dst( \
(vector *)vec_addr, sizeof(**vec_addr), pos)))
# define vector_add(vec_addr, value) (*vector_add_dst(vec_addr) = value)
# define vector_insert(vec_addr, pos, value) \
(*vector_insert_dst(vec_addr, pos) = value)
#else
# define vector_add_dst(vec_addr, type) \
((type *)_vector_add_dst((vector *)vec_addr, sizeof(type)))
# define vector_insert_dst(vec_addr, type, pos) \
((type *)_vector_insert_dst((vector *)vec_addr, sizeof(type), pos))
# define vector_add(vec_addr, type, value) \
(*vector_add_dst(vec_addr, type) = value)
# define vector_insert(vec_addr, type, pos, value) \
(*vector_insert_dst(vec_addr, type, pos) = value)
#endif
// vec is a vector (aka type*)
#define vector_erase(vec, pos, len) \
(_vector_erase((vector)vec, sizeof(*vec), pos, len))
#define vector_remove(vec, pos) (_vector_remove((vector)vec, sizeof(*vec), pos))
#define vector_reserve(vec_addr, capacity) \
(_vector_reserve((vector *)vec_addr, sizeof(**vec_addr), capacity))
#define vector_copy(vec) (_vector_copy((vector)vec, sizeof(*vec)))
vector vector_create(void);
void vector_free(vector vec);
void *_vector_add_dst(vector *vec_addr, vec_type_t type_size);
void *_vector_insert_dst(
vector *vec_addr, vec_type_t type_size, vec_size_t pos);
void _vector_erase(
vector vec_addr, vec_type_t type_size, vec_size_t pos, vec_size_t len);
void _vector_remove(vector vec_addr, vec_type_t type_size, vec_size_t pos);
void vector_pop(vector vec);
void _vector_reserve(
vector *vec_addr, vec_type_t type_size, vec_size_t capacity);
vector _vector_copy(vector vec, vec_type_t type_size);
vec_size_t vector_size(vector vec);
vec_size_t vector_capacity(vector vec);
// closing bracket for extern "C"
#ifdef __cplusplus
}
#endif
#endif /* vec_h */

8
tools/format.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
find "$ROOT/src" -type f -name '*.c' -o -name '*.h' -print0 | while IFS= read -r -d '' f; do
clang-format -i --style=file "$f"
done

1
wlroots-lunar Submodule

Submodule wlroots-lunar added at a9abd5a6e4