diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c561e8..4901fe9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,10 @@ cmake_minimum_required(VERSION 3.31) -set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "0e5b6991-d74f-4b3d-a41c-cf096e0b2508") - -project(LunarWM LANGUAGES C CXX) +project(LunarWM LANGUAGES C) +set(CMAKE_C_STANDARD 23) +set(CMAKE_C_STANDARD_REQUIRED 23) set(CMAKE_CXX_STANDARD 23) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_MODULE_STD 1) add_compile_options( -fstack-protector-strong @@ -20,8 +18,6 @@ add_compile_definitions( find_package(PkgConfig REQUIRED) -find_package(Boost 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) @@ -50,23 +46,12 @@ set(PLATFORM DRM) set(BUILD_EXAMPLES OFF) FetchContent_MakeAvailable(raylib) -FetchContent_Declare( - cpptrace - GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v1.0.1 -) -FetchContent_MakeAvailable(cpptrace) - add_executable(${PROJECT_NAME}) target_sources(${PROJECT_NAME} PUBLIC - src/dhos_config.cpp - src/main.cpp -) -target_sources(${PROJECT_NAME} PUBLIC FILE_SET CXX_MODULES FILES - src/Util.cppm - src/Math.cppm - src/Config.cppm - src/LunarWM.cppm + src/vec.c + + src/LunarWM.c + src/main.c ) target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::XKBCOMMON @@ -76,13 +61,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC PkgConfig::WLROOTS PkgConfig::OPENXR - Boost::boost raylib - cpptrace::cpptrace -) - -set_target_properties(${PROJECT_NAME} PROPERTIES - CXX_CLANG_TIDY "clang-tidy;-header-filter=^${CMAKE_SOURCE_DIR}/src/.*;-checks=*,-some-disabled-checks,misc-const-correctness,modernize-use-nullptr,bugprone-branch-clone,bugprone-use-after-move,performance-unnecessary-value-param,hicpp-no-malloc,cppcoreguidelines-pro-type-const-cast,clang-analyzer-core.NullDereference,-fuchsia-overloaded-operator,-readability-identifier-length,-bugprone-easily-swappable-parameters,-fuchsia-trailing-return,-misc-non-private-member-variables-in-classes,-readability-math-missing-parentheses,-llvmlibc-implementation-in-namespace,-misc-include-cleaner,-modernize-use-std-print,-llvmlibc-restrict-system-libc-headers,-fuchsia-statically-constructed-objects,-cppcoreguidelines-avoid-non-const-global-variables,-llvmlibc-callee-namespace,-misc-use-anonymous-namespace,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-hicpp-uppercase-literal-suffix,-readability-uppercase-literal-suffix,-fuchsia-default-arguments-calls,-altera-unroll-loops,-fuchsia-default-arguments-declarations,-readability-function-cognitive-complexity,-altera-struct-pack-align,-altera-id-dependent-backward-branch,-cppcoreguidelines-pro-type-reinterpret-cast,-boost-use-ranges,-cppcoreguidelines-owning-memory,-readability-redundant-declaration,-bugprone-unchecked-optional-access,-readability-const-return-type,-llvm-namespace-comment,-google-readability-namespace-comments,-cert-arr39-c,-cert-con36-c,-cert-con54-cpp,-cert-ctr56-cpp,-cert-dcl03-c,-cert-dcl16-c,-cert-dcl37-c,-cert-dcl51-cpp,-cert-dcl54-cpp,-cert-dcl59-cpp,-cert-err09-cpp,-cert-err61-cpp,-cert-exp42-c,-cert-fio38-c,-cert-flp37-c,-cert-int09-c,-cert-msc24-c,-cert-msc30-c,-cert-msc32-c,-cert-msc33-c,-cert-msc54-cpp,-cert-oop11-cpp,-cert-oop54-cpp,-cert-pos44-c,-cert-pos47-c,-cert-sig30-c,-cert-str34-c,-clang-analyzer-core.BitwiseShift,-clang-analyzer-core.CallAndMessage,-clang-analyzer-core.DivideZero,-clang-analyzer-core.NonNullParamChecker,-clang-analyzer-core.NullDereference,-clang-analyzer-core.StackAddressEscape,-clang-analyzer-core.UndefinedBinaryOperatorResult,-clang-analyzer-core.VLASize,-clang-analyzer-core.uninitialized.ArraySubscript,-clang-analyzer-core.uninitialized.Assign,-clang-analyzer-core.uninitialized.Branch,-clang-analyzer-core.uninitialized.CapturedBlockVariable,-clang-analyzer-core.uninitialized.NewArraySize,-clang-analyzer-core.uninitialized.UndefReturn,-clang-analyzer-cplusplus.ArrayDelete,-clang-analyzer-cplusplus.InnerPointer,-clang-analyzer-cplusplus.Move,-clang-analyzer-cplusplus.NewDelete,-clang-analyzer-cplusplus.NewDeleteLeaks,-clang-analyzer-cplusplus.PlacementNew,-clang-analyzer-cplusplus.SelfAssignment,-clang-analyzer-cplusplus.StringChecker,-clang-analyzer-deadcode.DeadStores,-clang-analyzer-fuchsia.HandleChecker,-clang-analyzer-nullability.NullPassedToNonnull,-clang-analyzer-nullability.NullReturnedFromNonnull,-clang-analyzer-nullability.NullableDereferenced,-clang-analyzer-nullability.NullablePassedToNonnull,-clang-analyzer-nullability.NullableReturnedFromNonnull,-clang-analyzer-optin.core.EnumCastOutOfRange,-clang-analyzer-optin.cplusplus.UninitializedObject,-clang-analyzer-optin.cplusplus.VirtualCall,-clang-analyzer-optin.mpi.MPI-Checker,-clang-analyzer-optin.osx.cocoa.localizability.EmptyLocalizationContextChecker,-clang-analyzer-optin.osx.cocoa.localizability.NonLocalizedStringChecker,-clang-analyzer-optin.performance.GCDAntipattern,-clang-analyzer-optin.performance.Padding,-clang-analyzer-optin.portability.UnixAPI,-clang-analyzer-optin.taint.TaintedAlloc,-clang-analyzer-osx.API,-clang-analyzer-osx.NumberObjectConversion,-clang-analyzer-osx.ObjCProperty,-clang-analyzer-osx.SecKeychainAPI,-clang-analyzer-osx.cocoa.AtSync,-clang-analyzer-osx.cocoa.AutoreleaseWrite,-clang-analyzer-osx.cocoa.ClassRelease,-clang-analyzer-osx.cocoa.Dealloc,-clang-analyzer-osx.cocoa.IncompatibleMethodTypes,-clang-analyzer-osx.cocoa.Loops,-clang-analyzer-osx.cocoa.MissingSuperCall,-clang-analyzer-osx.cocoa.NSAutoreleasePool,-clang-analyzer-osx.cocoa.NSError,-clang-analyzer-osx.cocoa.NilArg,-clang-analyzer-osx.cocoa.NonNilReturnValue,-clang-analyzer-osx.cocoa.ObjCGenerics,-clang-analyzer-osx.cocoa.RetainCount,-clang-analyzer-osx.cocoa.RunLoopAutoreleaseLeak,-clang-analyzer-osx.cocoa.SelfInit,-clang-analyzer-osx.cocoa.SuperDealloc,-clang-analyzer-osx.cocoa.UnusedIvars,-clang-analyzer-osx.cocoa.VariadicMethodTypes,-clang-analyzer-osx.coreFoundation.CFError,-clang-analyzer-osx.coreFoundation.CFNumber,-clang-analyzer-osx.coreFoundation.CFRetainRelease,-clang-analyzer-osx.coreFoundation.containers.OutOfBounds,-clang-analyzer-osx.coreFoundation.containers.PointerSizedValues,-clang-analyzer-security.FloatLoopCounter,-clang-analyzer-security.PutenvStackArray,-clang-analyzer-security.SetgidSetuidOrder,-clang-analyzer-security.cert.env.InvalidPtr,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-analyzer-security.insecureAPI.UncheckedReturn,-clang-analyzer-security.insecureAPI.bcmp,-clang-analyzer-security.insecureAPI.bcopy,-clang-analyzer-security.insecureAPI.bzero,-clang-analyzer-security.insecureAPI.decodeValueOfObjCType,-clang-analyzer-security.insecureAPI.getpw,-clang-analyzer-security.insecureAPI.gets,-clang-analyzer-security.insecureAPI.mkstemp,-clang-analyzer-security.insecureAPI.mktemp,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-security.insecureAPI.strcpy,-clang-analyzer-security.insecureAPI.vfork,-clang-analyzer-unix.API,-clang-analyzer-unix.BlockInCriticalSection,-clang-analyzer-unix.Errno,-clang-analyzer-unix.Malloc,-clang-analyzer-unix.MallocSizeof,-clang-analyzer-unix.MismatchedDeallocator,-clang-analyzer-unix.StdCLibraryFunctions,-clang-analyzer-unix.Stream,-clang-analyzer-unix.Vfork,-clang-analyzer-unix.cstring.BadSizeArg,-clang-analyzer-unix.cstring.NullArg,-clang-analyzer-webkit.NoUncountedMemberChecker,-clang-analyzer-webkit.RefCntblBaseVirtualDtor,-clang-analyzer-webkit.UncountedLambdaCapturesChecker,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-c-copy-assignment-signature,-cppcoreguidelines-explicit-virtual-functions,-cppcoreguidelines-macro-to-enum,-cppcoreguidelines-narrowing-conversions,-cppcoreguidelines-noexcept-destructor,-cppcoreguidelines-noexcept-move-operations,-cppcoreguidelines-noexcept-swap,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-use-default-member-init,-fuchsia-header-anon-namespaces,-google-readability-braces-around-statements,-google-readability-function-size,-google-readability-namespace-comments,-hicpp-avoid-c-arrays,-hicpp-avoid-goto,-hicpp-braces-around-statements,-hicpp-deprecated-headers,-hicpp-explicit-conversions,-hicpp-function-size,-hicpp-invalid-access-moved,-hicpp-member-init,-hicpp-move-const-arg,-hicpp-named-parameter,-hicpp-new-delete-operators,-hicpp-no-array-decay,-hicpp-no-malloc,-hicpp-noexcept-move,-hicpp-special-member-functions,-hicpp-static-assert,-hicpp-undelegated-constructor,-hicpp-uppercase-literal-suffix,-hicpp-use-auto,-hicpp-use-emplace,-hicpp-use-equals-default,-hicpp-use-equals-delete,-hicpp-use-noexcept,-hicpp-use-nullptr,-hicpp-use-override,-hicpp-vararg,-llvm-else-after-return,-llvm-qualified-auto,-llvm-header-guard,-readability-else-after-return" ) # Wayland protocol codegen diff --git a/src/Config.cppm b/src/Config.cppm deleted file mode 100644 index fe427c4..0000000 --- a/src/Config.cppm +++ /dev/null @@ -1,271 +0,0 @@ -module; - -#include - -extern "C" { -#include -} - -#include - -#include "dhos_config.h" - -export module LunarWM.Config; - -import std; - -static auto default_configuration_paths() - -> std::vector const -{ - std::vector paths = { - "lunarwm/lunarwm.dcfg", - "lunarwm.dcfg", - "/etc/lunarwm.dcfg", - "/etc/lunarwm/lunarwm.dcfg", - }; - - if (char const *xdg_dirs - = std::getenv("XDG_CONFIG_DIRS")) { // NOLINT(concurrency-mt-unsafe) - std::string const dirs { xdg_dirs }; - std::istringstream ss { dirs }; - std::string dir; - while (std::getline(ss, dir, ':')) { - if (!dir.empty()) { - paths.push_back( - std::filesystem::path(dir) / "lunarwm/lunarwm.dcfg"); - paths.push_back(std::filesystem::path(dir) / "lunarwm.dcfg"); - } - } - } - - if (char const *home - = std::getenv("HOME")) { // NOLINT(concurrency-mt-unsafe) - std::filesystem::path const home_path(home); - paths.push_back(home_path / ".config/lunarwm/lunarwm.dcfg"); - } - - return paths; -} - -static inline auto string_to_modifier(std::string_view str) - -> std::optional -{ - if (boost::iequals(str, "shift")) { - return WLR_MODIFIER_SHIFT; - } else if (boost::iequals(str, "ctrl")) { - return WLR_MODIFIER_CTRL; - } else if (boost::iequals(str, "alt")) { - return WLR_MODIFIER_ALT; - } else if (boost::iequals(str, "mod2")) { - return WLR_MODIFIER_MOD2; - } else if (boost::iequals(str, "mod3")) { - return WLR_MODIFIER_MOD3; - } else if (boost::iequals(str, "logo") || boost::iequals(str, "win") - || boost::iequals(str, "super") || boost::iequals(str, "mod4")) { - return WLR_MODIFIER_LOGO; - } else if (boost::iequals(str, "mod5")) { - return WLR_MODIFIER_MOD5; - } - return std::nullopt; -} - -export namespace LunarWM::Config { - -struct Configuration { - dhos::Value e; - - struct KeyCombo { - uint32_t modifiers {}; - xkb_keysym_t sym {}; - - static auto parse(std::string const &str, - std::unordered_map const &modifier_alias) - -> std::optional; - }; - struct Keybind { - KeyCombo combo; - dhos::Value action; - }; - - struct { - struct { - std::vector xkb_options; - } keyboard {}; - } input {}; - - std::unordered_map modifier_aliases; - std::vector keybindings; - - dhos::Object custom_lib; - - void execute_keybind(Keybind const &keybind) - { - dhos::eval(keybind.action, &this->custom_lib); - } - - void add_to_custom_lib(std::string const &name, dhos::Builtin const &fn) - { - this->custom_lib.insert_or_assign(name, dhos::Value(fn, e.env)); - } - - void load() { this->load(default_configuration_paths()); } - void load(std::vector const &paths); -}; - -auto Configuration::KeyCombo::parse(std::string const &str, - std::unordered_map const &modifier_alias) - -> std::optional -{ - std::istringstream iss(str); - std::string component; - std::vector components; - components.reserve(5); - while (std::getline(iss, component, '-')) { - boost::algorithm::trim(component); - if (component.empty()) { - continue; - } - components.push_back(component); - } - - if (components.size() < 2) { - return std::nullopt; - } - - wlr_keyboard_modifier mods {}; - for (auto const &entry : - std::span(components.begin(), components.end() - 1)) { - auto mod_opt = string_to_modifier(entry); - if (!mod_opt) { - if (modifier_alias.contains(entry)) { - mod_opt = static_cast( - modifier_alias.at(entry)); - } else { - return std::nullopt; - } - } - - mods = static_cast( - static_cast(mods) | static_cast(*mod_opt)); - } - - auto const &key_name = components.back(); - xkb_keysym_t const sym = xkb_keysym_from_name(key_name.c_str(), - static_cast(XKB_KEYSYM_CASE_INSENSITIVE)); - if (sym == XKB_KEY_NoSymbol) { - return std::nullopt; - } - - Configuration::KeyCombo const combo { - .modifiers = mods, - .sym = sym, - }; - return combo; -} - -void Configuration::load(std::vector const &paths) -{ - for (auto const &path : paths) { - try { - e = dhos::eval(dhos::parse_config(path), &this->custom_lib); - if (!e.is_obj()) { - throw std::runtime_error("Top level is not an object!"); - } - - static dhos::Object base = dhos::make_default_lib(); - auto merged = dhos::merge_lib(base, &this->custom_lib); - dhos::get_env_tbl(&*e.env)["lib"] = dhos::Value(merged, e.env); - - if (e["input"].is_obj()) { - auto const &input = e["input"]; - if (input["keyboard"].is_obj()) { - auto const &keyboard = input["keyboard"]; - if (keyboard["xkb_options"].is_arr()) { - auto const &arr = keyboard["xkb_options"].arr(); - this->input.keyboard.xkb_options.clear(); - for (auto const &value : arr) { - if (value.is_str()) { - this->input.keyboard.xkb_options.push_back( - value.str()); - } else { - // FIXME: Send warning - std::println("Invalid xkb_options type"); - } - } - } - } - } - - if (e["modifier_aliases"].is_obj()) { - auto const &modifier_aliases = e["modifier_aliases"]; - this->modifier_aliases.clear(); - - for (auto const &[k, v] : modifier_aliases.obj()) { - if (!v.is_str()) { - // FIXME: Send warning - std::println("Invalid modifier aliases value"); - continue; - } else { - auto const to_parse = v.str() + "-a"; - std::println( - "Sending alias value to parse: {}", to_parse); - auto const kc_opt = KeyCombo::parse(to_parse, {}); - if (kc_opt) { - auto const mod_opt = kc_opt->modifiers; - this->modifier_aliases[k] = mod_opt; - } else { - // FIXME: Send warning - std::println( - "Invalid modifier aliases value format"); - } - } - } - } - - if (e["keybindings"].is_arr()) { - auto const &keybindings = e["keybindings"].arr(); - this->keybindings.clear(); - - for (auto const &v : keybindings) { - if (v.is_obj()) { - auto const &o = v.obj(); - if (!o.contains("bind") || !o.contains("action") - || !o.at("bind").is_str() - || !o.at("action").is_call()) { - // FIXME: Send warning - std::println("Invalid keybinding object format"); - continue; - } - - auto const combo_opt = KeyCombo::parse( - o.at("bind").str(), this->modifier_aliases); - if (!combo_opt) { - // FIXME: Send warning - std::println("Invalid keybinding combo"); - continue; - } - - auto const &combo = *combo_opt; - - this->keybindings.push_back({ - .combo = combo, - .action = o.at("action"), - }); - } else { - // FIXME: Send warning - } - } - } - - return; - } catch (std::filesystem::filesystem_error const &e) { - (void)e; - } catch (std::exception &e) { - (void)e; - std::println("Exception for config file: {}", e.what()); - } - } - throw std::runtime_error("Unable to find a valid configuration file!"); -} - -} // LunarWM::Config diff --git a/src/LunarWM.c b/src/LunarWM.c new file mode 100644 index 0000000..5e287db --- /dev/null +++ b/src/LunarWM.c @@ -0,0 +1,1572 @@ +#include "LunarWM.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vec.h" + +void toplevel_commit_notify(struct wl_listener *l, void *) +{ + auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), commit); + + if (tl->xdg_toplevel->base->initial_commit) { + wlr_xdg_toplevel_set_size(tl->xdg_toplevel, 0, 0); + return; + } + + LunarWM_Toplevel_update(tl); +} + +void toplevel_destroy_notify(struct wl_listener *l, void *) +{ + auto *tl = wl_container_of(l, (LunarWM_Toplevel *)(NULL), destroy); + + for (size_t i = 0; i < vector_size(tl->server->wayland.v_toplevels); i++) { + if (tl == tl->server->wayland.v_toplevels[i]) { + vector_remove(tl->server->wayland.v_toplevels, i); + break; + } + } + LunarWM_Toplevel_destroy(tl); + free(tl); +} + +bool LunarWM_Toplevel_init( + LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg) +{ + tl->server = wm; + tl->xdg_toplevel = xdg; + tl->surface = xdg->base->surface; + + assert(tl->surface); + + tl->commit.notify = toplevel_commit_notify; + wl_signal_add(&tl->surface->events.commit, &tl->commit); + + tl->destroy.notify = toplevel_destroy_notify; + wl_signal_add(&tl->surface->events.destroy, &tl->destroy); + + return true; +} + +bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this) +{ + wl_list_remove(&this->commit.link); + wl_list_remove(&this->destroy.link); + if (this->locked_buffer != nullptr) { + wlr_buffer_unlock(this->locked_buffer); + } + return true; +} + +bool LunarWM_Toplevel_update(LunarWM_Toplevel *this) +{ + this->texture = wlr_surface_get_texture(this->surface); + struct wlr_client_buffer *cl_buf = this->surface->buffer; + if ((this->texture == nullptr) || (cl_buf == nullptr)) { + return false; + } + + if ((this->locked_buffer != nullptr) + && this->locked_buffer != &cl_buf->base) { + wlr_buffer_unlock(this->locked_buffer); + this->locked_buffer = nullptr; + } + if (this->locked_buffer == nullptr) { + this->locked_buffer = wlr_buffer_lock(&cl_buf->base); + } + + wlr_gles2_texture_get_attribs(this->texture, &this->attribs); + this->gles_texture = gles2_get_texture(this->texture); + if (this->gles_texture == nullptr) { + return false; + } + + this->rl_texture.id = (unsigned int)this->attribs.tex; + this->rl_texture.width = (int)this->texture->width; + this->rl_texture.height = (int)this->texture->height; + this->rl_texture.mipmaps = 1; + this->rl_texture.format = this->gles_texture->has_alpha + ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + : PIXELFORMAT_UNCOMPRESSED_R8G8B8; + + SetTextureFilter(this->rl_texture, TEXTURE_FILTER_BILINEAR); + SetTextureWrap(this->rl_texture, TEXTURE_WRAP_CLAMP); + + return true; +} + +static void Keyboard_modifiers_notify(struct wl_listener *listener, void *) +{ + auto *kbd + = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), modifiers); + wlr_seat_set_keyboard(kbd->server->wayland.seat, kbd->wlr_keyboard); + wlr_seat_keyboard_notify_modifiers( + kbd->server->wayland.seat, &kbd->wlr_keyboard->modifiers); +} + +static void Keyboard_key_notify(struct wl_listener *listener, void *data) +{ + auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), key); + auto *server = kbd->server; + auto *event = (struct wlr_keyboard_key_event *)data; + struct wlr_seat *seat = server->wayland.seat; + + uint32_t const keycode = event->keycode + 8; + xkb_keysym_t const *syms = nullptr; + int const nsyms + = xkb_state_key_get_syms(kbd->wlr_keyboard->xkb_state, keycode, &syms); + xkb_keysym_t const keysym + = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); + + bool const handled = false; + uint32_t const modifiers = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); + if (server->wayland.session && event->state == WL_KEYBOARD_KEY_STATE_PRESSED + && keysym >= XKB_KEY_XF86Switch_VT_1 + && keysym <= XKB_KEY_XF86Switch_VT_12) { + unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; + wlr_session_change_vt(server->wayland.session, vt); + return; + } + if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + if ((modifiers & WLR_MODIFIER_LOGO) && syms[XKB_KEY_Q]) { + LunarWM_terminate(server); + } + } + + if (!handled) { + wlr_seat_set_keyboard(seat, kbd->wlr_keyboard); + wlr_seat_keyboard_notify_key( + seat, event->time_msec, event->keycode, event->state); + } +} + +static void Keyboard_destroy_notify(struct wl_listener *listener, void *) +{ + auto *kbd = wl_container_of(listener, (LunarWM_Keyboard *)(NULL), destroy); + wl_list_remove(&kbd->modifiers.link); + wl_list_remove(&kbd->key.link); + wl_list_remove(&kbd->destroy.link); + wl_list_remove(&kbd->link); + free(kbd); +} + +static void new_input_listener_notify(struct wl_listener *listener, void *data) +{ + LunarWM *wm = wl_container_of(listener, wm, wayland.new_input_listener); + auto *dev = (struct wlr_input_device *)data; + if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { + struct wlr_keyboard *wlr_keyboard = wlr_keyboard_from_input_device(dev); + + LunarWM_Keyboard *keyboard = calloc(1, sizeof(*keyboard)); + keyboard->server = wm; + keyboard->wlr_keyboard = wlr_keyboard; + + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + struct xkb_keymap *keymap = xkb_keymap_new_from_names( + context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS); + + wlr_keyboard_set_keymap(wlr_keyboard, keymap); + xkb_keymap_unref(keymap); + xkb_context_unref(context); + wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); + + keyboard->modifiers.notify = Keyboard_modifiers_notify; + wl_signal_add(&wlr_keyboard->events.modifiers, &keyboard->modifiers); + + keyboard->key.notify = Keyboard_key_notify; + wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); + + keyboard->destroy.notify = Keyboard_destroy_notify; + wl_signal_add(&dev->events.destroy, &keyboard->destroy); + + wlr_seat_set_keyboard(wm->wayland.seat, keyboard->wlr_keyboard); + + wl_list_insert(&wm->wayland.keyboards, &keyboard->link); + } else if (dev->type == WLR_INPUT_DEVICE_POINTER) { + wlr_cursor_attach_input_device(wm->wayland.cursor, dev); + } + + uint32_t caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&wm->wayland.keyboards)) { + caps |= WL_SEAT_CAPABILITY_KEYBOARD; + } + assert(wm->wayland.seat); + wlr_seat_set_capabilities(wm->wayland.seat, caps); +} + +static void new_xdg_popup_listener_notify(struct wl_listener *, void *) +{ + // FIXME: Add support for popups +} + +static void new_xdg_toplevel_listener_notify( + struct wl_listener *listener, void *data) +{ + LunarWM *wm + = wl_container_of(listener, wm, wayland.new_xdg_toplevel_listener); + auto *xdg_tl = (struct wlr_xdg_toplevel *)data; + + LunarWM_Toplevel *tl = (LunarWM_Toplevel *)calloc(1, sizeof(*tl)); + if (!tl) { + wlr_log(WLR_ERROR, "oom"); + return; + } + + if (LunarWM_Toplevel_init(tl, wm, xdg_tl)) { + vector_add(&wm->wayland.v_toplevels, tl); + } else { + wlr_log(WLR_ERROR, "Failed to initialize Toplevel."); + free(tl); + } +} + +static bool init_wayland(LunarWM *this) +{ + wlr_log_init(WLR_DEBUG, nullptr); + + this->wayland.v_toplevels = vector_create(); + + this->wayland.display = wl_display_create(); + if (this->wayland.display == nullptr) { + return false; + wlr_log(WLR_ERROR, "Failed to create wayland display"); + } + + this->wayland.event_loop = wl_display_get_event_loop(this->wayland.display); + if (this->wayland.event_loop == nullptr) { + wlr_log(WLR_ERROR, "Failed to get wayland event loop"); + return false; + } + + this->wayland.backend + = wlr_backend_autocreate(this->wayland.event_loop, nullptr); + if (this->wayland.backend == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots backend"); + return false; + } + + setenv("WLR_RENDERER", "gles2", 1); + this->wayland.renderer = wlr_renderer_autocreate(this->wayland.backend); + if (this->wayland.renderer == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots renderer"); + return false; + } + + this->wayland.session = wlr_session_create(this->wayland.event_loop); + if (this->wayland.session == nullptr) { + wlr_log(WLR_ERROR, "Failed to create session"); + return false; + } + + this->wayland.egl = wlr_gles2_renderer_get_egl(this->wayland.renderer); + if (this->wayland.egl == nullptr) { + wlr_log(WLR_ERROR, "Failed to get egl information from renderer"); + return false; + } + + this->wayland.egl_display = wlr_egl_get_display(this->wayland.egl); + this->wayland.egl_context = wlr_egl_get_context(this->wayland.egl); + this->wayland.egl_config = EGL_NO_CONFIG_KHR; + + if (!wlr_renderer_init_wl_display( + this->wayland.renderer, this->wayland.display)) { + wlr_log( + WLR_ERROR, "Failed to initialize renderer with wayland display"); + return false; + } + + this->wayland.allocator = wlr_allocator_autocreate( + this->wayland.backend, this->wayland.renderer); + if (this->wayland.allocator == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots allocator"); + } + + this->wayland.compositor = wlr_compositor_create( + this->wayland.display, 5, this->wayland.renderer); + if (this->wayland.compositor == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots compositor"); + } + + this->wayland.subcompositor + = wlr_subcompositor_create(this->wayland.display); + if (this->wayland.subcompositor == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots subcompositor"); + } + + this->wayland.data_device_manager + = wlr_data_device_manager_create(this->wayland.display); + if (this->wayland.data_device_manager == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots data device manager"); + } + + this->wayland.cursor = wlr_cursor_create(); + + wl_list_init(&this->wayland.keyboards); + + this->wayland.new_input_listener.notify = new_input_listener_notify; + wl_signal_add(&this->wayland.backend->events.new_input, + &this->wayland.new_input_listener); + + this->wayland.seat = wlr_seat_create(this->wayland.display, "seat0"); + if (this->wayland.seat == nullptr) { + wlr_log(WLR_ERROR, "Failed to create wlroots seat"); + return false; + } + + this->wayland.xdg_shell = wlr_xdg_shell_create(this->wayland.display, 3); + + this->wayland.new_xdg_toplevel_listener.notify + = new_xdg_toplevel_listener_notify; + wl_signal_add(&this->wayland.xdg_shell->events.new_toplevel, + &this->wayland.new_xdg_toplevel_listener); + + this->wayland.new_xdg_popup_listener.notify = new_xdg_popup_listener_notify; + wl_signal_add(&this->wayland.xdg_shell->events.new_popup, + &this->wayland.new_xdg_popup_listener); + + return true; +} + +static uint32_t get_swapchain_image( + LunarWM *this, int swapchain_images_i, uint32_t index) +{ + return this->xr.swapchain_images[swapchain_images_i].a_imgs[index].image; +} + +static bool init_xr(LunarWM *this) +{ + XrResult res = XR_SUCCESS; + + XrApplicationInfo app_info = { + .applicationVersion = 1, + .engineVersion = 1, + .apiVersion = XR_CURRENT_API_VERSION, + }; + strncpy((char *)app_info.applicationName, "LunarWM", + XR_MAX_APPLICATION_NAME_SIZE); + strncpy( + (char *)app_info.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE); + + char const *instance_extensions[] = { + XR_EXT_DEBUG_UTILS_EXTENSION_NAME, + XR_MNDX_EGL_ENABLE_EXTENSION_NAME, + XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, + XR_EXT_HAND_TRACKING_EXTENSION_NAME, + }; + char const **v_active_instance_extensions = vector_create(); + + uint32_t extension_properties_count = 0; + XrExtensionProperties *extension_properties; + if (xrEnumerateInstanceExtensionProperties( + nullptr, 0, &extension_properties_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR instance extension properties (count)"); + return false; + } + extension_properties + = malloc(sizeof(*extension_properties) * extension_properties_count); + for (uint32_t i = 0; i < extension_properties_count; ++i) { + extension_properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; + extension_properties[i].next = NULL; + } + + if (xrEnumerateInstanceExtensionProperties(nullptr, + extension_properties_count, &extension_properties_count, + extension_properties) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR instance extension properties"); + return false; + } + + for (size_t i = 0; i < ARRAY_SZ(instance_extensions); i++) { + char const *requested_instance_extension = instance_extensions[i]; + bool found = false; + for (int j = 0; j < extension_properties_count; j++) { + if (strcmp(requested_instance_extension, + extension_properties[j].extensionName) + != 0) { + continue; + } + + vector_add( + &v_active_instance_extensions, requested_instance_extension); + found = true; + break; + } + + if (!found) { + wlr_log(WLR_ERROR, "Failed to find OpenXR instance extension: %s", + requested_instance_extension); + return false; + } + } + + { + XrInstanceCreateInfo const ci = { + .type = XR_TYPE_INSTANCE_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .applicationInfo = app_info, + .enabledApiLayerCount = 0, + .enabledApiLayerNames = nullptr, + .enabledExtensionCount + = (uint32_t)vector_size(v_active_instance_extensions), + .enabledExtensionNames = v_active_instance_extensions, + }; + + XrInstance instance = nullptr; + if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR instance"); + return false; + } + this->xr.instance = instance; + + res = xrGetInstanceProcAddr(this->xr.instance, "xrCreateHandTrackerEXT", + (PFN_xrVoidFunction *)&this->xr.CreateHandTrackerEXT); + if (res != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get proc addr xrCreateHandTrackerEXT"); + return false; + } + res = xrGetInstanceProcAddr(this->xr.instance, + "xrDestroyHandTrackerEXT", + (PFN_xrVoidFunction *)&this->xr.DestroyHandTrackerEXT); + if (res != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get proc addr xrDestroyHandTrackerEXT"); + return false; + } + res = xrGetInstanceProcAddr(this->xr.instance, "xrLocateHandJointsEXT", + (PFN_xrVoidFunction *)&this->xr.LocateHandJointsEXT); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get proc addr xrLocateHandJointsEXT"); + return false; + } + } + + { + XrSystemGetInfo gi = { + .type = XR_TYPE_SYSTEM_GET_INFO, + .next = nullptr, + }; + + XrFormFactor const factors[2] = { + XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, + XR_FORM_FACTOR_HANDHELD_DISPLAY, + }; + for (size_t i = 0; i < ARRAY_SZ(factors); i++) { + auto factor = factors[i]; + + gi.formFactor = factor; + XrSystemId system_id = 0; + if (xrGetSystem(this->xr.instance, &gi, &system_id) == XR_SUCCESS) { + this->xr.system_id = system_id; + break; + } + } + + if (!this->xr.system_id) { + wlr_log(WLR_ERROR, "Failed to find valid form factor"); + return false; + } + } + + { + XrSystemProperties system_props = { + .type = XR_TYPE_SYSTEM_PROPERTIES, + .next = &this->xr.hand_tracking_system_properties, + }; + res = xrGetSystemProperties( + this->xr.instance, this->xr.system_id, &system_props); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "xrGetSystemProperties failed: %d", res); + return false; + } + } + + XrGraphicsRequirementsOpenGLESKHR reqs = { + .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, + .next = nullptr, + }; + PFN_xrGetOpenGLESGraphicsRequirementsKHR + xrGetOpenGLESGraphicsRequirementsKHR + = nullptr; + xrGetInstanceProcAddr(this->xr.instance, + "xrGetOpenGLESGraphicsRequirementsKHR", + (PFN_xrVoidFunction *)&xrGetOpenGLESGraphicsRequirementsKHR); + if (xrGetOpenGLESGraphicsRequirementsKHR( + this->xr.instance, this->xr.system_id, &reqs) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get GLES graphics requirements"); + return false; + } + printf("OpenGL ES range: %d.%d.%d - %d.%d.%d\n", + XR_VERSION_MAJOR(reqs.minApiVersionSupported), + XR_VERSION_MINOR(reqs.minApiVersionSupported), + XR_VERSION_PATCH(reqs.minApiVersionSupported), + XR_VERSION_MAJOR(reqs.maxApiVersionSupported), + XR_VERSION_MINOR(reqs.maxApiVersionSupported), + XR_VERSION_PATCH(reqs.maxApiVersionSupported)); + + glEnable(GL_DEBUG_OUTPUT_KHR); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + + { + XrGraphicsBindingEGLMNDX gbind = { + .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, + .next = nullptr, + .getProcAddress = eglGetProcAddress, + .display = this->wayland.egl_display, + .config = this->wayland.egl_config, + .context = this->wayland.egl_context, + }; + + XrSessionCreateInfo const ci = { + .type = XR_TYPE_SESSION_CREATE_INFO, + .next = &gbind, + .createFlags = 0, + .systemId = this->xr.system_id, + }; + + if (xrCreateSession(this->xr.instance, &ci, &this->xr.session) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR session"); + return false; + } + } + + // Swapchain time! + XrViewConfigurationType *a_view_config_types; + uint32_t view_config_types_count = 0; + { + if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, + 0, &view_config_types_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to get amount of OpenXR view configurations"); + return false; + } + a_view_config_types + = malloc(sizeof(*a_view_config_types) * view_config_types_count); + for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { + this->xr.a_view_configuration_views[i].type + = XR_TYPE_VIEW_CONFIGURATION_VIEW; + this->xr.a_view_configuration_views[i].next = NULL; + } + if (xrEnumerateViewConfigurations(this->xr.instance, this->xr.system_id, + view_config_types_count, &view_config_types_count, + a_view_config_types) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate OpenXR view configurations"); + return false; + } + } + + { + bool found = false; + for (size_t i = 0; i < view_config_types_count; i++) { + if (a_view_config_types[i] + == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { + found = true; + break; + } + } + if (!found) { + wlr_log(WLR_ERROR, + "XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO not present"); + return false; + } + } + + { + if (xrEnumerateViewConfigurationViews(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + 0, &this->xr.view_configuration_views_count, nullptr) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to get amount of OpenXR view configuration views"); + return false; + } + this->xr.a_view_configuration_views + = malloc(sizeof(*this->xr.a_view_configuration_views) + * this->xr.view_configuration_views_count); + for (uint32_t i = 0; i < this->xr.view_configuration_views_count; ++i) { + this->xr.a_view_configuration_views[i].type + = XR_TYPE_VIEW_CONFIGURATION_VIEW; + this->xr.a_view_configuration_views[i].next = NULL; + } + if (xrEnumerateViewConfigurationViews(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + this->xr.view_configuration_views_count, + &this->xr.view_configuration_views_count, + this->xr.a_view_configuration_views) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, + "Failed to enumerate OpenXR view configuration views"); + return false; + } + } + + int64_t *a_swapchain_formats; + uint32_t a_swapchain_formats_count = 0; + { + if (xrEnumerateSwapchainFormats( + this->xr.session, 0, &a_swapchain_formats_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get amount of OpenXR swapchain formats"); + return false; + } + a_swapchain_formats + = malloc(sizeof(*a_swapchain_formats) * a_swapchain_formats_count); + if (xrEnumerateSwapchainFormats(this->xr.session, + a_swapchain_formats_count, &a_swapchain_formats_count, + a_swapchain_formats) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to enumerate OpenXR swapchain formats"); + return false; + } + } + + { + bool found = false; + for (size_t i = 0; i < a_swapchain_formats_count; i++) { + if (a_swapchain_formats[i] == GL_DEPTH_COMPONENT16) { + found = true; + break; + } + } + if (!found) { + wlr_log( + WLR_ERROR, "Failed to find satisfying depth swapchain format"); + return false; + } + } + + auto const swapchain_format_depth = GL_DEPTH_COMPONENT16; + auto const swapchain_format_color = GL_SRGB8_ALPHA8; + + { + bool found = false; + for (size_t i = 0; i < a_swapchain_formats_count; i++) { + if (a_swapchain_formats[i] == GL_SRGB8_ALPHA8) { + found = true; + break; + } + } + if (!found) { + wlr_log( + WLR_ERROR, "Failed to find satisfying color swapchain format"); + return false; + } + } + + uint32_t const view_count = this->xr.view_configuration_views_count; + uint32_t const eyeW + = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; + uint32_t const eyeH + = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; + uint32_t const bufW = eyeW * view_count; + + if (!this->xr.swapchains.v_color) { + this->xr.swapchains.v_color = vector_create(); + } + if (!this->xr.swapchains.v_depth) { + this->xr.swapchains.v_depth = vector_create(); + } + + vector_add(&this->xr.swapchains.v_color, (LunarWM_SwapchainInfo) {}); + vector_add(&this->xr.swapchains.v_depth, (LunarWM_SwapchainInfo) {}); + + { + auto *color_sc = &this->xr.swapchains.v_color[0]; + auto *depth_sc = &this->xr.swapchains.v_depth[0]; + auto *vcv = &this->xr.a_view_configuration_views[0]; + + { // Create color swapchain + XrSwapchainCreateInfo const ci = { + .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT + | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, + .format = swapchain_format_color, + .sampleCount = vcv->recommendedSwapchainSampleCount, + .width = bufW, + .height = eyeH, + .faceCount = 1, + .arraySize = 1, + .mipCount = 1, + }; + color_sc->swapchain_format = ci.format; + + if (xrCreateSwapchain(this->xr.session, &ci, &color_sc->swapchain) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR color swapchain"); + return false; + } + } + + { // Create depth swapchain + XrSwapchainCreateInfo const ci = { + .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT + | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + .format = swapchain_format_depth, + .sampleCount = vcv->recommendedSwapchainSampleCount, + .width = bufW, + .height = eyeH, + .faceCount = 1, + .arraySize = 1, + .mipCount = 1, + }; + depth_sc->swapchain_format = ci.format; + + if (xrCreateSwapchain(this->xr.session, &ci, &depth_sc->swapchain) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR depth swapchain"); + return false; + } + } + + // Enumerate swapchain images + { // Color + this->xr.swapchain_images[0].handle = color_sc->swapchain; + if (xrEnumerateSwapchainImages(color_sc->swapchain, 0, + &this->xr.swapchain_images[0].a_imgs_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get Color Swapchain Images count."); + return false; + } + this->xr.swapchain_images[0].a_imgs + = malloc(sizeof(*this->xr.swapchain_images[0].a_imgs) + * this->xr.swapchain_images[0].a_imgs_count); + for (uint32_t i = 0; i < this->xr.swapchain_images[0].a_imgs_count; + ++i) { + this->xr.swapchain_images[0].a_imgs[i].type + = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + this->xr.swapchain_images[0].a_imgs[i].next = NULL; + } + if (xrEnumerateSwapchainImages(color_sc->swapchain, + this->xr.swapchain_images[0].a_imgs_count, + &this->xr.swapchain_images[0].a_imgs_count, + (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[0] + .a_imgs) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate color swapchain images."); + return false; + } + } + { // Depth + this->xr.swapchain_images[1].handle = depth_sc->swapchain; + if (xrEnumerateSwapchainImages(depth_sc->swapchain, 0, + &this->xr.swapchain_images[1].a_imgs_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get depth Swapchain Images count."); + return false; + } + this->xr.swapchain_images[1].a_imgs + = malloc(sizeof(*this->xr.swapchain_images[1].a_imgs) + * this->xr.swapchain_images[1].a_imgs_count); + for (uint32_t i = 0; i < this->xr.swapchain_images[1].a_imgs_count; + ++i) { + this->xr.swapchain_images[1].a_imgs[i].type + = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR; + this->xr.swapchain_images[1].a_imgs[i].next = NULL; + } + if (xrEnumerateSwapchainImages(depth_sc->swapchain, + this->xr.swapchain_images[1].a_imgs_count, + &this->xr.swapchain_images[1].a_imgs_count, + (XrSwapchainImageBaseHeader *)this->xr.swapchain_images[1] + .a_imgs) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to enumerate depth swapchain images."); + return false; + } + } + + // Get image views + for (uint32_t j = 0; j < this->xr.swapchain_images[0].a_imgs_count; + j++) { + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + + GLenum const attachment = GL_COLOR_ATTACHMENT0; + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, + GL_TEXTURE_2D, get_swapchain_image(this, 0, j), 0); + + GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (result != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create color framebuffer"); + return false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (!color_sc->v_image_views) { + color_sc->v_image_views = vector_create(); + } + vector_add(&color_sc->v_image_views, framebuffer); + } + + for (uint32_t j = 0; j < this->xr.swapchain_images[1].a_imgs_count; + j++) { + GLuint framebuffer = 0; + glGenFramebuffers(1, &framebuffer); + + GLenum const attachment = GL_DEPTH_ATTACHMENT; + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, + GL_TEXTURE_2D, get_swapchain_image(this, 1, j), 0); + + GLenum const result = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER); + if (result != GL_FRAMEBUFFER_COMPLETE) { + wlr_log(WLR_ERROR, "Failed to create depth framebuffer"); + return false; + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (!depth_sc->v_image_views) { + depth_sc->v_image_views = vector_create(); + } + vector_add(&depth_sc->v_image_views, framebuffer); + } + } + + assert(this->xr.instance); + assert(this->xr.system_id); + + XrEnvironmentBlendMode *a_environment_blend_modes; + uint32_t a_environment_blend_modes_count = 0; + { // Get available blend modes + if (xrEnumerateEnvironmentBlendModes(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + 0, &a_environment_blend_modes_count, nullptr) + != XR_SUCCESS) { + wlr_log( + WLR_ERROR, "Failed to get OpenXR environment blend mode count"); + return false; + } + a_environment_blend_modes = malloc(sizeof(*a_environment_blend_modes) + * a_environment_blend_modes_count); + if (xrEnumerateEnvironmentBlendModes(this->xr.instance, + this->xr.system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + a_environment_blend_modes_count, + &a_environment_blend_modes_count, a_environment_blend_modes) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to get XR environment blend modes"); + return false; + } + } + XrEnvironmentBlendMode const requested_environment_blend_modes[] = { + XR_ENVIRONMENT_BLEND_MODE_OPAQUE, + XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, + XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, + }; + + for (size_t i = 0; i < ARRAY_SZ(requested_environment_blend_modes); i++) { + this->xr.environment_blend_mode = requested_environment_blend_modes[i]; + bool found = false; + for (size_t j = 0; j < a_environment_blend_modes_count; j++) { + if (requested_environment_blend_modes[i] + == a_environment_blend_modes[j]) { + found = true; + break; + } + } + if (found) { + break; + } + } + if (this->xr.environment_blend_mode == XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM) { + wlr_log(WLR_INFO, + "Failed to find a compatible blend mode. Defaulting to " + "XR_ENVIRONMENT_BLEND_MODE_OPAQUE."); + this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + } + + { // Reference space + XrReferenceSpaceCreateInfo const ci = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .next = nullptr, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE, + .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, + .position = { 0.0f, 0.0f, 0.0f } }, + }; + + if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.local_space) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); + return false; + } + } + + { // View reference space + XrReferenceSpaceCreateInfo const ci = { + .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, + .next = nullptr, + .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW, + .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, + .position = { 0.0f, 0.0f, 0.0f } }, + }; + + if (xrCreateReferenceSpace(this->xr.session, &ci, &this->xr.view_space) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create OpenXR reference space"); + return false; + } + } + + { // Create hand trackers + for (size_t i = 0; i < 2; i++) { + auto *hand = &this->xr.hands[i]; + + XrHandTrackerCreateInfoEXT const ci = { + .type = XR_TYPE_HAND_TRACKER_CREATE_INFO_EXT, + .hand = i == 0 ? XR_HAND_LEFT_EXT : XR_HAND_RIGHT_EXT, + .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, + }; + + res = this->xr.CreateHandTrackerEXT( + this->xr.session, &ci, &hand->hand_tracker); + if (res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to create hand tracker: %d", res); + return false; + } + } + } + + return true; +} + +bool LunarWM_init(LunarWM *this) +{ + { // Init defaults + memset(this, 0, sizeof(*this)); + this->xr.session = XR_NULL_HANDLE; + this->xr.session_state = XR_SESSION_STATE_UNKNOWN; + this->xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM; + this->xr.local_space = this->xr.view_space = XR_NULL_HANDLE; + this->xr.hand_tracking_system_properties.type + = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT; + this->renderer.camera.position = (Vector3) { 0, 0, 0 }; + this->renderer.camera.target = (Vector3) { 0, 0, 1 }; + this->renderer.camera.up = (Vector3) { 0, 1, 0 }; + this->renderer.camera.fovy = 45; + this->renderer.camera.projection = CAMERA_PERSPECTIVE; + } + + if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { + wlr_log(WLR_ERROR, "This compositor can only be ran in DRM mode."); + return false; + } + + if (!init_wayland(this)) { + wlr_log(WLR_ERROR, "Failed to initialize wlroots"); + return false; + } + + auto *draw = eglGetCurrentSurface(EGL_DRAW); + auto *read = eglGetCurrentSurface(EGL_READ); + if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, + EGL_NO_SURFACE, this->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return false; + } + + if (!init_xr(this)) { + wlr_log(WLR_ERROR, "Failed to initialize OpenXR"); + return false; + } + + wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); + InitWindow(0, 0, ""); + + if (eglMakeCurrent( + this->wayland.egl_display, draw, read, this->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return false; + } + + this->initialized = true; + + // FIXME: Cleanup. + + return true; +} + +void LunarWM_destroy(LunarWM *this) +{ + // FIXME: Cleanup. +} + +void LunarWM_terminate(LunarWM *this) +{ + wlr_log(WLR_INFO, "Stopping compositor"); + this->running = false; +} + +static void poll_events_xr(LunarWM *this) +{ + XrEventDataBuffer event_data = { + .type = XR_TYPE_EVENT_DATA_BUFFER, + }; + + while (xrPollEvent(this->xr.instance, &event_data) == XR_SUCCESS) { + switch (event_data.type) { + case XR_TYPE_EVENT_DATA_EVENTS_LOST: { + auto *el = (XrEventDataEventsLost *)&event_data; + wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); + break; + } + case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { + auto *ilp = (XrEventDataInstanceLossPending *)&event_data; + wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", + ilp->lossTime); + this->xr.session_running = false; + this->running = false; + break; + } + case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { + auto *ipc = (XrEventDataInteractionProfileChanged *)&event_data; + wlr_log(WLR_INFO, + "OPENXR: Interaction Profile changed for Session: " + "%p", + ipc->session); + if (ipc->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataInteractionProfileChanged for " + "unknown Session"); + break; + } + break; + } + case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { + auto *scp = (XrEventDataReferenceSpaceChangePending *)&event_data; + wlr_log(WLR_INFO, + "OPENXR: Reference Space Change pending for " + "Session: %p", + scp->session); + if (scp->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataReferenceSpaceChangePending for " + "unknown " + "Session"); + break; + } + break; + } + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + auto *sc = (XrEventDataSessionStateChanged *)&event_data; + if (sc->session != this->xr.session) { + wlr_log(WLR_ERROR, + "XrEventDataSessionStateChanged for unknown " + "Session"); + break; + } + + if (sc->state == XR_SESSION_STATE_READY) { + XrSessionBeginInfo bi = { .type = XR_TYPE_SESSION_BEGIN_INFO }; + bi.primaryViewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + bi.primaryViewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + if (xrBeginSession(this->xr.session, &bi) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to begin session"); + } else { + this->xr.session_running = true; + } + } + if (sc->state == XR_SESSION_STATE_STOPPING) { + if (xrEndSession(this->xr.session) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to end session"); + } + this->xr.session_running = false; + } + if (sc->state == XR_SESSION_STATE_EXITING) { + this->xr.session_running = false; + this->running = false; + } + if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { + this->xr.session_running = false; + this->running = false; + } + this->xr.session_state = sc->state; + break; + } + default: { + break; + } + } + } +} + +bool acquire_wait(XrSwapchain sc, uint32_t *idx) +{ + XrSwapchainImageAcquireInfo ai + = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO, .next = NULL }; + if (xrAcquireSwapchainImage(sc, &ai, idx) != XR_SUCCESS) { + return false; + } + + XrSwapchainImageWaitInfo wi = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, + .next = NULL, + .timeout = XR_INFINITE_DURATION }; + return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; +} + +static inline Matrix xr_matrix(XrPosef const pose) +{ + Matrix const translation + = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z); + Matrix const rotation = QuaternionToMatrix((Quaternion) { + pose.orientation.x, + pose.orientation.y, + pose.orientation.z, + pose.orientation.w, + }); + return MatrixMultiply(rotation, translation); +} + +static inline Matrix xr_projection_matrix(XrFovf const fov) +{ + static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); + + Matrix matrix = {}; + + auto const near = (float)RL_CULL_DISTANCE_NEAR; + auto const far = (float)RL_CULL_DISTANCE_FAR; + + float const tan_angle_left = tanf(fov.angleLeft); + float const tan_angle_right = tanf(fov.angleRight); + + float const tan_angle_down = tanf(fov.angleDown); + float const tan_angle_up = tanf(fov.angleUp); + + float const tan_angle_width = tan_angle_right - tan_angle_left; + float const tan_angle_height = tan_angle_up - tan_angle_down; + + matrix.m0 = 2 / tan_angle_width; + matrix.m4 = 0; + matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width; + matrix.m12 = 0; + + matrix.m1 = 0; + matrix.m5 = 2 / tan_angle_height; + matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height; + matrix.m13 = 0; + + matrix.m2 = 0; + matrix.m6 = 0; + matrix.m10 = -(far + near) / (far - near); + matrix.m14 = -(far * (near + near)) / (far - near); + + matrix.m3 = 0; + matrix.m7 = 0; + matrix.m11 = -1; + matrix.m15 = 0; + + return matrix; +} + +static void DrawBillboardNoShear( + Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint) +{ + Rectangle const src = { 0, 0, tex.width, tex.height }; + + Vector2 const size = { scale * fabsf(src.width / src.height), scale }; + Vector2 const origin = { size.x * 0.5f, size.y * 0.5f }; + + DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint); +} + +void render_3d(LunarWM *this, float /*dt*/) +{ + DrawGrid(10, 1); + + for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { + auto *tl = this->wayland.v_toplevels[i]; + + LunarWM_Toplevel_update(tl); + DrawBillboardNoShear(this->renderer.camera, tl->rl_texture, + (Vector3) { 0, 1, -1.4f }, 1.0f, WHITE); + } + + for (int h = 0; h < 2; ++h) { + auto *handInfo = &this->xr.hands[h]; + for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { + auto const *jl = &handInfo->joint_locations[k]; // NOLINT + Vector3 const pos = { + jl->pose.position.x, + jl->pose.position.y, + jl->pose.position.z, + }; + DrawSphere(pos, jl->radius, RED); + } + } +} + +static bool render_layer(LunarWM *this, LunarWM_RenderLayerInfo *info, float dt) +{ + auto const view_count = (uint32_t)this->xr.view_configuration_views_count; + assert(view_count == 2); + + XrView views[2] = {}; + for (int i = 0; i < ARRAY_SZ(views); i++) { + views[i] = (XrView) { + .type = XR_TYPE_VIEW, + }; + } + + XrViewLocateInfo locInfo = { + .type = XR_TYPE_VIEW_LOCATE_INFO, + .viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, + .displayTime = info->predicted_display_time, + .space = this->xr.local_space, + }; + + XrViewState viewState = { .type = XR_TYPE_VIEW_STATE }; + uint32_t located = 0; + if (xrLocateViews( + this->xr.session, &locInfo, &viewState, view_count, &located, views) + != XR_SUCCESS + || located != view_count) { + wlr_log(WLR_ERROR, "Failed to locate views"); + return false; + } + + // acquire swapchain images + auto *color_sc = &this->xr.swapchains.v_color[0]; + auto *depth_sc = &this->xr.swapchains.v_depth[0]; + + uint32_t col_idx = 0; + uint32_t dep_idx = 0; + if (!acquire_wait(color_sc->swapchain, &col_idx) + || !acquire_wait(depth_sc->swapchain, &dep_idx)) { + wlr_log(WLR_ERROR, "Swap-chain acquire failed"); + return false; + } + + GLuint color_tex = get_swapchain_image(this, 0, col_idx); + GLuint depth_tex = get_swapchain_image(this, 1, dep_idx); + + // build FBO + if (this->renderer.fbo == 0u) { + glGenFramebuffers(1, &this->renderer.fbo); + } + glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo); + rlFramebufferAttach(this->renderer.fbo, color_tex, + RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0); + rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, + RL_ATTACHMENT_TEXTURE2D, 0); + assert(rlFramebufferComplete(this->renderer.fbo)); + + uint32_t const eyeW + = this->xr.a_view_configuration_views[0].recommendedImageRectWidth; + uint32_t const eyeH + = this->xr.a_view_configuration_views[0].recommendedImageRectHeight; + + this->renderer.tmp_rt = (RenderTexture2D) { + .id = this->renderer.fbo, + .texture = { color_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, + .depth = { depth_tex, (int)eyeW * view_count, (int)eyeH, 1, -1 }, + }; + + // head-space view matrix (matches rlOpenXR) + XrSpaceLocation headLoc = { .type = XR_TYPE_SPACE_LOCATION }; + xrLocateSpace(this->xr.view_space, this->xr.local_space, + info->predicted_display_time, &headLoc); + auto const headView = MatrixInvert(xr_matrix(headLoc.pose)); + + // per-eye projection + view-offset + Matrix const projR = xr_projection_matrix(views[0].fov); + Matrix const projL = xr_projection_matrix(views[1].fov); + Matrix const viewOffL = MatrixMultiply(xr_matrix(views[0].pose), headView); + Matrix const viewOffR = MatrixMultiply(xr_matrix(views[1].pose), headView); + + // draw + BeginTextureMode(this->renderer.tmp_rt); + + rlEnableStereoRender(); + rlSetMatrixProjectionStereo(projR, projL); // right, left (yes) + rlSetMatrixViewOffsetStereo(viewOffR, viewOffL); + + glViewport(0, 0, (GLsizei)eyeW * view_count, (GLsizei)eyeH); + ClearBackground((Color) { 0, 0, 10, 255 }); + + 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; + } + } + + BeginMode3D(this->renderer.camera); + { + render_3d(this, dt); + } + EndMode3D(); + + rlDisableStereoRender(); + EndTextureMode(); + + // 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 * eyeW; + auto *pv = &info->layer_projection_views[i]; + pv->pose = views[i].pose; + pv->fov = views[i].fov; + pv->subImage.swapchain = color_sc->swapchain; + pv->subImage.imageRect.offset = (XrOffset2Di) { .x = xOff, .y = 0 }; + pv->subImage.imageRect.extent + = (XrExtent2Di) { .width = eyeW, .height = eyeH }; + 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; + + return true; +} + +void LunarWM_run(LunarWM *this) +{ + assert(this); + assert(this->initialized); + + if (!wlr_backend_start(this->wayland.backend)) { + wlr_log(WLR_ERROR, "Failed to start backend"); + return; + } + + auto const *socket = wl_display_add_socket_auto(this->wayland.display); + if (socket == nullptr) { + wlr_log(WLR_ERROR, "Failed to add wayland socket to display"); + return; + } + + setenv("WAYLAND_DISPLAY", socket, 1); + wlr_log(LOG_INFO, "Running on WAYLAND_DISPLAY=%s", socket); + + this->running = true; + + struct timespec last, now; + clock_gettime(CLOCK_MONOTONIC, &last); + while (this->running) { + clock_gettime(CLOCK_MONOTONIC, &now); + float dt = (now.tv_sec - last.tv_sec) + + (now.tv_nsec - last.tv_nsec) / 1000000000.0f; + last = now; + + for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) { + LunarWM_Toplevel *tl = this->wayland.v_toplevels[i]; + if (tl->surface) { + wlr_surface_send_frame_done(tl->surface, &now); + } + } + + wl_display_flush_clients(this->wayland.display); + wl_event_loop_dispatch(this->wayland.event_loop, 0); + + auto *draw = eglGetCurrentSurface(EGL_DRAW); + auto *read = eglGetCurrentSurface(EGL_READ); + if (eglMakeCurrent(this->wayland.egl_display, EGL_NO_SURFACE, + EGL_NO_SURFACE, this->wayland.egl_context) + == EGL_FALSE) { + wlr_log(WLR_ERROR, "Failed to eglMakeCurrent"); + return; + } + + WindowShouldClose(); + BeginDrawing(); + + poll_events_xr(this); + + if (!this->xr.session_running) { + EndDrawing(); + continue; + } + + XrFrameState frame_state = { + .type = XR_TYPE_FRAME_STATE, + }; + XrFrameWaitInfo const frame_wait_info = { + .type = XR_TYPE_FRAME_WAIT_INFO, + .next = nullptr, + }; + if (xrWaitFrame(this->xr.session, &frame_wait_info, &frame_state) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to wait for OpenXR frame"); + return; + } + + XrFrameBeginInfo const frame_begin_info = { + .type = XR_TYPE_FRAME_BEGIN_INFO, + .next = nullptr, + }; + XrResult res = xrBeginFrame(this->xr.session, &frame_begin_info); + if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to begin the OpenXR Frame: %d", res); + return; + } + + if (this->xr.hand_tracking_system_properties.supportsHandTracking) { + XrActionStateGetInfo const si = { + .type = XR_TYPE_ACTION_STATE_GET_INFO, + }; + for (size_t i = 0; i < 2; i++) { + LunarWM_Hand *hand = &this->xr.hands[i]; + bool const unobstructed = true; + + XrHandJointsMotionRangeInfoEXT mri = { + .type = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT, + }; + if (unobstructed) { + mri.handJointsMotionRange + = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; + } else { + mri.handJointsMotionRange + = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; + } + + XrHandJointsLocateInfoEXT const li = { + .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, + .next = &mri, + .baseSpace = this->xr.local_space, + .time = frame_state.predictedDisplayTime, + }; + + XrHandJointLocationsEXT hji = { + .type = XR_TYPE_HAND_JOINT_LOCATIONS_EXT, + .jointCount = XR_HAND_JOINT_COUNT_EXT, + .jointLocations = hand->joint_locations, + }; + + if (this->xr.LocateHandJointsEXT(hand->hand_tracker, &li, &hji) + != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to locate hand joints"); + return; + } + } + } + + LunarWM_RenderLayerInfo render_layer_info = { + .predicted_display_time = frame_state.predictedDisplayTime, + .layer_projection.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, + .layer_projection.next = nullptr, + }; + bool const session_active + = (this->xr.session_state == XR_SESSION_STATE_SYNCHRONIZED + || this->xr.session_state == XR_SESSION_STATE_VISIBLE + || this->xr.session_state == XR_SESSION_STATE_FOCUSED); + if (session_active && (frame_state.shouldRender != 0u)) { + auto rendered = render_layer(this, &render_layer_info, dt); + if (rendered) { + render_layer_info.layers[render_layer_info.layers_count] + = (XrCompositionLayerBaseHeader *)&render_layer_info + .layer_projection; + } + } + + XrFrameEndInfo const frame_end_info = { + .type = XR_TYPE_FRAME_END_INFO, + .displayTime = frame_state.predictedDisplayTime, + .environmentBlendMode = this->xr.environment_blend_mode, + .layerCount = render_layer_info.layers_count, + .layers + = (XrCompositionLayerBaseHeader const **)render_layer_info.layers, + }; + if (xrEndFrame(this->xr.session, &frame_end_info) != XR_SUCCESS) { + wlr_log(WLR_ERROR, "Failed to end OpenXR frame"); + return; + } + + EndDrawing(); + } +} diff --git a/src/LunarWM.cppm b/src/LunarWM.cppm deleted file mode 100644 index 9e0b89c..0000000 --- a/src/LunarWM.cppm +++ /dev/null @@ -1,1818 +0,0 @@ -module; - -// NOLINTBEGIN -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -} - -#include -#include -#include -// NOLINTEND - -#include "dhos_config.h" - -export module LunarWM.LunarWM; - -import std; - -import LunarWM.Math; -import LunarWM.Util; -import LunarWM.Config; - -using Clock = std::chrono::high_resolution_clock; - -namespace LunarWM { - -static auto xr_projection_matrix(XrFovf const &fov) -> Matrix -{ - static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR); - - Matrix matrix {}; - - auto const near = static_cast(RL_CULL_DISTANCE_NEAR); - auto const far = static_cast(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 auto xr_matrix(XrPosef const &pose) -> Matrix -{ - 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 rotation * translation; -} - -enum class SwapchainType : std::uint8_t { - Color, - Depth, -}; - -struct SwapchainInfo { - XrSwapchain swapchain { XR_NULL_HANDLE }; - std::int64_t swapchain_format {}; - std::vector image_views; -}; - -export struct LunarWM { - LunarWM() = default; - ~LunarWM(); - - LunarWM(LunarWM const &) = delete; - auto operator=(LunarWM const &) -> LunarWM & = delete; - - LunarWM(LunarWM const &&) = delete; - auto operator=(LunarWM const &&) -> LunarWM & = delete; - - void init(); - - void run(); - void terminate(); - -private: - struct RenderLayerInfo; - - void init_wayland(); - void init_xr(); - - void poll_events_xr(); - auto render_layer(RenderLayerInfo &info, float dt) -> bool; - void render_3d(float dt); - - bool m_initialized {}; - - struct Keyboard { - struct LunarWM *server; - - struct wl_list link; - struct wlr_keyboard *wlr_keyboard; - - struct wl_listener modifiers; - struct wl_listener key; - struct wl_listener destroy; - }; - - struct Toplevel { - Toplevel(Toplevel const &) = delete; - auto operator=(Toplevel const &) -> Toplevel & = delete; - Toplevel(Toplevel &&) = delete; - auto operator=(Toplevel &&) -> Toplevel & = delete; - - Toplevel(LunarWM *srv, wlr_xdg_toplevel *xdg); - ~Toplevel(); - - void update(); - - LunarWM *server {}; - - wl_listener commit {}; - wl_listener destroy {}; - - wlr_xdg_toplevel *xdg_toplevel {}; - wlr_surface *surface {}; - wlr_texture *texture {}; - - wlr_buffer *locked_buffer {}; - wlr_gles2_texture_attribs attribs {}; - wlr_gles2_texture *gles_texture {}; - Texture2D rl_texture {}; - }; - - struct Output { - struct LunarWM *server {}; - struct wlr_output *output {}; - struct wl_listener frame {}; - struct wl_listener destroy {}; - }; - - struct { - wl_display *display {}; - wl_event_loop *event_loop {}; - - wlr_backend *backend {}; - wlr_renderer *renderer {}; - wlr_session *session {}; - - wlr_egl *egl {}; - EGLDisplay egl_display {}; - EGLContext egl_context {}; - EGLConfig egl_config {}; - - wlr_allocator *allocator {}; - wlr_compositor *compositor {}; - wlr_subcompositor *subcompositor {}; - wlr_data_device_manager *data_device_manager {}; - - wlr_seat *seat {}; - wl_list keyboards {}; - wl_listener new_input_listener {}; - - wlr_xdg_shell *xdg_shell {}; - wl_listener new_xdg_toplevel_listener {}; - wl_listener new_xdg_popup_listener {}; - - wlr_cursor *cursor {}; - - std::vector> toplevels; - } m_wayland; - - struct { - std::optional instance; - std::optional system_id; - XrSession session { XR_NULL_HANDLE }; - XrSessionState session_state { XR_SESSION_STATE_UNKNOWN }; - struct { - std::vector color; - std::vector depth; - } swapchains; - - std::unordered_map>> - swapchain_images_map; - - std::vector view_configuration_views; - XrEnvironmentBlendMode environment_blend_mode { - XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM - }; - - XrSpace local_space { XR_NULL_HANDLE }; - XrSpace view_space { XR_NULL_HANDLE }; - - // Extensions - struct Hand { - std::array - joint_locations {}; - XrHandTrackerEXT hand_tracker {}; - }; - std::array hands; - XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties { - XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT - }; - - // Extension functions - PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT {}; - PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT {}; - PFN_xrLocateHandJointsEXT LocateHandJointsEXT {}; - - auto get_swapchain_image(XrSwapchain swapchain, uint32_t index) - -> std::uint32_t - { - return swapchain_images_map[swapchain].second[index].image; - } - } m_xr; - - struct RendererFrame { - GLuint fbo { 0 }; - uint32_t color_idx { UINT32_MAX }; - uint32_t depth_idx { UINT32_MAX }; - bool has_depth {}; - }; - - struct Renderer { - GLuint fbo {}; - RenderTexture2D tmp_rt {}; - - Camera3D camera { - .position = { 0, 0, 0 }, - .target = { 0, 0, 1 }, - .up = { 0, 1, 0 }, - .fovy = 45, - .projection = CAMERA_PERSPECTIVE, - }; - - std::array hands {}; - } m_renderer; - - struct RenderLayerInfo { - XrTime predicted_display_time; - std::vector layers; - XrCompositionLayerProjection layer_projection { - .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, - .next = nullptr, - }; - std::vector layer_projection_views; - }; - - std::chrono::time_point m_last_tick; - - Config::Configuration m_config {}; - - bool m_running {}; - bool m_session_running {}; - bool m_session_state {}; -}; - -LunarWM::Toplevel::Toplevel(LunarWM *srv, wlr_xdg_toplevel *xdg) - : server(srv) - , xdg_toplevel(xdg) - , surface(xdg->base->surface) -{ - - assert(surface); - - commit.notify = [](wl_listener *l, void *) { - auto *tl = wl_container_of(l, static_cast(nullptr), commit); - - if (tl->xdg_toplevel->base->initial_commit) { - wlr_xdg_toplevel_set_size(tl->xdg_toplevel, 0, 0); - return; - } - tl->update(); - }; - wl_signal_add(&surface->events.commit, &commit); - - destroy.notify = [](wl_listener *l, void *) { - auto *tl = wl_container_of(l, (Toplevel *)nullptr, destroy); - - auto &vec = tl->server->m_wayland.toplevels; - auto const [first, last] = std::ranges::remove_if( - vec, [tl](auto &p) { return p.get() == tl; }); - vec.erase(first, last); - }; - wl_signal_add(&surface->events.destroy, &destroy); -} - -LunarWM::Toplevel::~Toplevel() -{ - wl_list_remove(&commit.link); - wl_list_remove(&destroy.link); - if (locked_buffer != nullptr) { - wlr_buffer_unlock(locked_buffer); - } -} - -void LunarWM::Toplevel::update() -{ - texture = wlr_surface_get_texture(surface); - struct wlr_client_buffer *cl_buf = surface->buffer; - if ((texture == nullptr) || (cl_buf == nullptr)) { - return; - } - - if ((locked_buffer != nullptr) && locked_buffer != &cl_buf->base) { - wlr_buffer_unlock(locked_buffer); - locked_buffer = nullptr; - } - if (locked_buffer == nullptr) { - locked_buffer = wlr_buffer_lock(&cl_buf->base); - } - - wlr_gles2_texture_get_attribs(texture, &attribs); - gles_texture = gles2_get_texture(texture); - if (gles_texture == nullptr) { - return; - } - - rl_texture.id = static_cast(attribs.tex); - rl_texture.width = static_cast(texture->width); - rl_texture.height = static_cast(texture->height); - rl_texture.mipmaps = 1; - rl_texture.format = gles_texture->has_alpha - ? PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 - : PIXELFORMAT_UNCOMPRESSED_R8G8B8; - - SetTextureFilter(rl_texture, TEXTURE_FILTER_BILINEAR); - SetTextureWrap(rl_texture, TEXTURE_WRAP_CLAMP); -} - -void LunarWM::init() -{ - m_config.add_to_custom_lib( - "reload_config", [&](dhos::Array const &) -> dhos::Value { - m_config.load(); - return dhos::Value {}; - }); - m_config.add_to_custom_lib( - "quit_compositor", [&](dhos::Array const &) -> dhos::Value { - this->terminate(); - return dhos::Value {}; - }); - - m_config.load(); - - std::println("Config.modifier_aliases:"); - for (auto const &[k, v] : m_config.modifier_aliases) { - std::println(" - {}: {}", k, v); - } - - std::println("Config.keybindings:"); - for (auto const &kb : m_config.keybindings) { - std::println(" - {}, {}", kb.combo.modifiers, kb.combo.sym); - } - - if (getenv("DISPLAY") != nullptr // NOLINT - || getenv("WAYLAND_DISPLAY") != nullptr) { // NOLINT - throw std::runtime_error("This compositor can only be ran in DRM mode"); - } - - this->init_wayland(); - - auto *draw = eglGetCurrentSurface(EGL_DRAW); - auto *read = eglGetCurrentSurface(EGL_READ); - if (eglMakeCurrent(m_wayland.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, - m_wayland.egl_context) - == EGL_FALSE) { - throw std::runtime_error("Failed to eglMakeCurrent"); - } - - this->init_xr(); - - wlr_log(WLR_INFO, "OpenGL ES version: %s", glGetString(GL_VERSION)); - InitWindow(0, 0, ""); - - m_renderer.hands.at(0) = LoadModel("assets/hand_l.gltf"); - m_renderer.hands.at(1) = LoadModel("assets/hand_r.gltf"); - - if (eglMakeCurrent(m_wayland.egl_display, draw, read, m_wayland.egl_context) - == EGL_FALSE) { - throw std::runtime_error("Failed to eglMakeCurrent"); - } - wlr_log(WLR_INFO, "4"); - m_initialized = true; -} - -void LunarWM::init_wayland() -{ - wlr_log_init(WLR_DEBUG, nullptr); - - m_wayland.display = wl_display_create(); - if (m_wayland.display == nullptr) { - throw std::runtime_error("Failed to create wayland display"); - } - - m_wayland.event_loop = wl_display_get_event_loop(m_wayland.display); - if (m_wayland.event_loop == nullptr) { - throw std::runtime_error("Failed to get wayland event loop"); - } - - m_wayland.backend = wlr_backend_autocreate(m_wayland.event_loop, nullptr); - if (m_wayland.backend == nullptr) { - throw std::runtime_error("Failed to create wlroots backend"); - } - - setenv("WLR_RENDERER", "gles2", 1); // NOLINT - m_wayland.renderer = wlr_renderer_autocreate(m_wayland.backend); - if (m_wayland.renderer == nullptr) { - throw std::runtime_error("Failed to create wlroots renderer"); - } - - m_wayland.session = wlr_session_create(m_wayland.event_loop); - if (m_wayland.session == nullptr) { - throw std::runtime_error("Failed to create session"); - } - - m_wayland.egl = wlr_gles2_renderer_get_egl(m_wayland.renderer); - if (m_wayland.egl == nullptr) { - throw std::runtime_error("Failed to get egl information from renderer"); - } - - m_wayland.egl_display = wlr_egl_get_display(m_wayland.egl); - m_wayland.egl_context = wlr_egl_get_context(m_wayland.egl); - m_wayland.egl_config = EGL_NO_CONFIG_KHR; - - if (!wlr_renderer_init_wl_display(m_wayland.renderer, m_wayland.display)) { - throw std::runtime_error( - "Failed to initialize renderer with wayland display"); - } - - m_wayland.allocator - = wlr_allocator_autocreate(m_wayland.backend, m_wayland.renderer); - if (m_wayland.allocator == nullptr) { - throw std::runtime_error("Failed to create wlroots allocator"); - } - - m_wayland.compositor - = wlr_compositor_create(m_wayland.display, 5, m_wayland.renderer); - if (m_wayland.compositor == nullptr) { - throw std::runtime_error("Failed to create wlroots compositor"); - } - - m_wayland.subcompositor = wlr_subcompositor_create(m_wayland.display); - if (m_wayland.subcompositor == nullptr) { - throw std::runtime_error("Failed to create wlroots subcompositor"); - } - - m_wayland.data_device_manager - = wlr_data_device_manager_create(m_wayland.display); - if (m_wayland.data_device_manager == nullptr) { - throw std::runtime_error( - "Failed to create wlroots data device manager"); - } - - m_wayland.cursor = wlr_cursor_create(); - - wl_list_init(&m_wayland.keyboards); - - m_wayland.new_input_listener.notify = [](wl_listener *listener, - void *data) { - auto *wm = wl_container_of(listener, static_cast(nullptr), - m_wayland.new_input_listener); - auto *dev = reinterpret_cast(data); - if (dev->type == WLR_INPUT_DEVICE_KEYBOARD) { - struct wlr_keyboard *wlr_keyboard - = wlr_keyboard_from_input_device(dev); - - auto *keyboard = new Keyboard; - keyboard->server = wm; - keyboard->wlr_keyboard = wlr_keyboard; - - std::string const options { boost::algorithm::join( - wm->m_config.input.keyboard.xkb_options, ",") }; - - struct xkb_rule_names const rule_names { - .options = options.c_str(), - }; - - struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - struct xkb_keymap *keymap = xkb_keymap_new_from_names( - context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); - - wlr_keyboard_set_keymap(wlr_keyboard, keymap); - xkb_keymap_unref(keymap); - xkb_context_unref(context); - wlr_keyboard_set_repeat_info(wlr_keyboard, 25, 600); - - keyboard->modifiers.notify = [](struct wl_listener *listener, - void * /*data*/) { - auto *kbd = wl_container_of(listener, keyboard, modifiers); - wlr_seat_set_keyboard( - kbd->server->m_wayland.seat, kbd->wlr_keyboard); - wlr_seat_keyboard_notify_modifiers( - kbd->server->m_wayland.seat, &kbd->wlr_keyboard->modifiers); - }; - wl_signal_add( - &wlr_keyboard->events.modifiers, &keyboard->modifiers); - keyboard->key.notify = [](struct wl_listener *listener, - void *data) { - auto *kbd = wl_container_of(listener, keyboard, key); - auto *server = kbd->server; - auto *event - = reinterpret_cast(data); - struct wlr_seat *seat = server->m_wayland.seat; - - uint32_t const keycode = event->keycode + 8; - xkb_keysym_t const *syms = nullptr; - int const nsyms = xkb_state_key_get_syms( - kbd->wlr_keyboard->xkb_state, keycode, &syms); - xkb_keysym_t const keysym = xkb_state_key_get_one_sym( - kbd->wlr_keyboard->xkb_state, keycode); - - bool const handled = false; - uint32_t const modifiers - = wlr_keyboard_get_modifiers(kbd->wlr_keyboard); - if (server->m_wayland.session - && event->state == WL_KEYBOARD_KEY_STATE_PRESSED - && keysym >= XKB_KEY_XF86Switch_VT_1 - && keysym <= XKB_KEY_XF86Switch_VT_12) { - unsigned const vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; - wlr_session_change_vt(server->m_wayland.session, vt); - return; - } - if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { - for (auto const &keybind : server->m_config.keybindings) { - if ((modifiers & keybind.combo.modifiers) - && syms[keybind.combo.sym]) { - server->m_config.execute_keybind(keybind); - } - } - } - - if (!handled) { - wlr_seat_set_keyboard(seat, kbd->wlr_keyboard); - wlr_seat_keyboard_notify_key( - seat, event->time_msec, event->keycode, event->state); - } - }; - wl_signal_add(&wlr_keyboard->events.key, &keyboard->key); - keyboard->destroy.notify - = [](struct wl_listener *listener, void * /*data*/) { - auto *kbd = wl_container_of(listener, keyboard, destroy); - wl_list_remove(&kbd->modifiers.link); - wl_list_remove(&kbd->key.link); - wl_list_remove(&kbd->destroy.link); - wl_list_remove(&kbd->link); - free(kbd); // NOLINT - }; - wl_signal_add(&dev->events.destroy, &keyboard->destroy); - - wlr_seat_set_keyboard(wm->m_wayland.seat, keyboard->wlr_keyboard); - - wl_list_insert(&wm->m_wayland.keyboards, &keyboard->link); - } else if (dev->type == WLR_INPUT_DEVICE_POINTER) { - wlr_cursor_attach_input_device(wm->m_wayland.cursor, dev); - } - - std::uint32_t caps { WL_SEAT_CAPABILITY_POINTER }; - if (!wl_list_empty(&wm->m_wayland.keyboards)) { - caps |= WL_SEAT_CAPABILITY_KEYBOARD; - } - assert(wm->m_wayland.seat); - wlr_seat_set_capabilities(wm->m_wayland.seat, caps); - }; - wl_signal_add( - &m_wayland.backend->events.new_input, &m_wayland.new_input_listener); - m_wayland.seat = wlr_seat_create(m_wayland.display, "seat0"); - if (m_wayland.seat == nullptr) { - throw std::runtime_error("Failed to create wlroots seat"); - } - - m_wayland.xdg_shell = wlr_xdg_shell_create(m_wayland.display, 3); - - m_wayland.new_xdg_toplevel_listener.notify = [](wl_listener *listener, - void *data) { - auto *server { wl_container_of(listener, - static_cast(nullptr), - m_wayland.new_xdg_toplevel_listener) }; - auto *toplevel { reinterpret_cast(data) }; - - if (toplevel->title) { - wlr_log(WLR_INFO, "Got XDG toplevel title: %s", toplevel->title); - } - if (toplevel->app_id) { - wlr_log(WLR_INFO, "Got XDG toplevel app ID: %s", toplevel->app_id); - } - - server->m_wayland.toplevels.emplace_back( - std::make_unique(server, toplevel)); - }; - wl_signal_add(&m_wayland.xdg_shell->events.new_toplevel, - &m_wayland.new_xdg_toplevel_listener); - - m_wayland.new_xdg_popup_listener.notify - = [](wl_listener * /*listener*/, void * /*data*/) { - // auto *server = wl_container_of(listener, - // static_cast(nullptr), - // m_wayland.new_xdg_popup_listener); - }; - wl_signal_add(&m_wayland.xdg_shell->events.new_popup, - &m_wayland.new_xdg_popup_listener); -} - -void LunarWM::init_xr() -{ - XrResult res {}; - - XrApplicationInfo app_info { - .applicationVersion = 1, - .engineVersion = 1, - .apiVersion = XR_CURRENT_API_VERSION, - }; - strncpy(static_cast(app_info.applicationName), "LunarWM", - XR_MAX_APPLICATION_NAME_SIZE); - strncpy(static_cast(app_info.engineName), "LunarWM Engine", - XR_MAX_ENGINE_NAME_SIZE); - - std::vector instance_extensions { - XR_EXT_DEBUG_UTILS_EXTENSION_NAME, - XR_MNDX_EGL_ENABLE_EXTENSION_NAME, - XR_KHR_OPENGL_ES_ENABLE_EXTENSION_NAME, - XR_EXT_HAND_TRACKING_EXTENSION_NAME, - }; - std::vector const apiLayers; - std::vector active_instance_extensions; - std::vector activeAPILayers; - - uint32_t apiLayerCount {}; - std::vector apiLayerProperties; - if (xrEnumerateApiLayerProperties(0, &apiLayerCount, nullptr) - != XR_SUCCESS) { - throw std::runtime_error("Failed to enumerate API layer properties"); - } - apiLayerProperties.resize(apiLayerCount, { XR_TYPE_API_LAYER_PROPERTIES }); - if (xrEnumerateApiLayerProperties( - apiLayerCount, &apiLayerCount, apiLayerProperties.data()) - != XR_SUCCESS) { - throw std::runtime_error("Failed to enumerate API layer properties"); - } - - for (auto const &requestLayer : apiLayers) { - for (auto const &layerProperty : apiLayerProperties) { - if (strcmp(requestLayer.c_str(), - static_cast(layerProperty.layerName)) - != 0) { - continue; - } - activeAPILayers.push_back(requestLayer.c_str()); - break; - } - } - - uint32_t extensionCount {}; - std::vector extensionProperties; - if (xrEnumerateInstanceExtensionProperties( - nullptr, 0, &extensionCount, nullptr) - != XR_SUCCESS) { - throw std::runtime_error("Failed to enumerate OpenXR " - "instance extension properties"); - } - extensionProperties.resize( - extensionCount, { XR_TYPE_EXTENSION_PROPERTIES }); - if (xrEnumerateInstanceExtensionProperties(nullptr, extensionCount, - &extensionCount, extensionProperties.data()) - != XR_SUCCESS) { - throw std::runtime_error("Failed to enumerate OpenXR " - "instance extension properties"); - } - - for (auto &requestedInstanceExtension : instance_extensions) { - bool found {}; - for (auto &extensionProperty : extensionProperties) { - if (strcmp(requestedInstanceExtension.c_str(), - static_cast(extensionProperty.extensionName)) - != 0) { - continue; - } - active_instance_extensions.push_back( - requestedInstanceExtension.c_str()); - found = true; - break; - } - if (!found) { - throw std::runtime_error( - std::format("Failed to find OpenXR instance extension: {}", - requestedInstanceExtension)); - } - } - - { - XrInstanceCreateInfo const ci { - .type = XR_TYPE_INSTANCE_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .applicationInfo = app_info, - .enabledApiLayerCount - = static_cast(activeAPILayers.size()), - .enabledApiLayerNames = activeAPILayers.data(), - .enabledExtensionCount - = static_cast(active_instance_extensions.size()), - .enabledExtensionNames = active_instance_extensions.data(), - }; - - XrInstance instance { nullptr }; - if (xrCreateInstance(&ci, &instance) != XR_SUCCESS) { - throw std::runtime_error("Failed to create OpenXR instance"); - } - m_xr.instance = instance; - - res = xrGetInstanceProcAddr(*m_xr.instance, "xrCreateHandTrackerEXT", - reinterpret_cast(&m_xr.CreateHandTrackerEXT)); - if (res != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get proc addr xrCreateHandTrackerEXT"); - } - res = xrGetInstanceProcAddr(*m_xr.instance, "xrDestroyHandTrackerEXT", - reinterpret_cast( - &m_xr.DestroyHandTrackerEXT)); - if (res != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get proc addr xrDestroyHandTrackerEXT"); - } - res = xrGetInstanceProcAddr(*m_xr.instance, "xrLocateHandJointsEXT", - reinterpret_cast(&m_xr.LocateHandJointsEXT)); - if (res != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get proc addr xrLocateHandJointsEXT"); - } - } - - { - XrSystemGetInfo gi { - .type = XR_TYPE_SYSTEM_GET_INFO, - .next = nullptr, - }; - - std::array const factors { - XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, - XR_FORM_FACTOR_HANDHELD_DISPLAY, - }; - for (auto const factor : factors) { - gi.formFactor = factor; - XrSystemId system_id = 0; - if (xrGetSystem(*m_xr.instance, &gi, &system_id) == XR_SUCCESS) { - m_xr.system_id = system_id; - break; - } - } - - if (!m_xr.system_id) { - throw std::runtime_error("Failed to find valid form factor"); - } - } - - { - XrSystemProperties system_props { - .type = XR_TYPE_SYSTEM_PROPERTIES, - .next = static_cast(&m_xr.hand_tracking_system_properties), - }; - res = xrGetSystemProperties( - *m_xr.instance, *m_xr.system_id, &system_props); - if (res != XR_SUCCESS) { - throw std::runtime_error( - std::format("xrGetSystemProperties failed: {}", res)); - } - } - - XrGraphicsRequirementsOpenGLESKHR reqs { - .type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_ES_KHR, - .next = nullptr, - }; - PFN_xrGetOpenGLESGraphicsRequirementsKHR - xrGetOpenGLESGraphicsRequirementsKHR - = nullptr; - xrGetInstanceProcAddr(*m_xr.instance, - "xrGetOpenGLESGraphicsRequirementsKHR", - reinterpret_cast( - &xrGetOpenGLESGraphicsRequirementsKHR)); - if (xrGetOpenGLESGraphicsRequirementsKHR( - *m_xr.instance, *m_xr.system_id, &reqs) - != XR_SUCCESS) { - throw std::runtime_error("Failed to get GLES graphics requirements"); - } - std::println("OpenGL ES range: {}.{}.{} - {}.{}.{}", - XR_VERSION_MAJOR(reqs.minApiVersionSupported), - XR_VERSION_MINOR(reqs.minApiVersionSupported), - XR_VERSION_PATCH(reqs.minApiVersionSupported), - XR_VERSION_MAJOR(reqs.maxApiVersionSupported), - XR_VERSION_MINOR(reqs.maxApiVersionSupported), - XR_VERSION_PATCH(reqs.maxApiVersionSupported)); - - glEnable(GL_DEBUG_OUTPUT_KHR); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); - - { - XrGraphicsBindingEGLMNDX gbind { - .type = XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, - .next = nullptr, - .getProcAddress = eglGetProcAddress, - .display = m_wayland.egl_display, - .config = m_wayland.egl_config, - .context = m_wayland.egl_context, - }; - - XrSessionCreateInfo const ci { - .type = XR_TYPE_SESSION_CREATE_INFO, - .next = &gbind, - .createFlags = 0, - .systemId = *m_xr.system_id, - }; - - if (xrCreateSession(*m_xr.instance, &ci, &m_xr.session) != XR_SUCCESS) { - throw std::runtime_error("Failed to create OpenXR session"); - } - } - - // Swapchain time! - std::vector view_config_types; - { - std::uint32_t count {}; - if (xrEnumerateViewConfigurations( - *m_xr.instance, *m_xr.system_id, 0, &count, nullptr) - != XR_SUCCESS) { - throw std::runtime_error("Failed to get amount of " - "OpenXR view configurations"); - } - view_config_types.resize(count); - if (xrEnumerateViewConfigurations(*m_xr.instance, *m_xr.system_id, - count, &count, view_config_types.data()) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to enumerate OpenXR view configurations"); - } - } - - if (!std::ranges::contains( - view_config_types, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)) { - throw std::runtime_error("XR_VIEW_CONFIGURATION_TYPE_" - "PRIMARY_STEREO not present"); - } - - { - uint32_t count {}; - if (xrEnumerateViewConfigurationViews(*m_xr.instance, *m_xr.system_id, - XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &count, nullptr) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get amount of OpenXR view configuration " - "views"); - } - m_xr.view_configuration_views.resize( - count, { XR_TYPE_VIEW_CONFIGURATION_VIEW }); - if (xrEnumerateViewConfigurationViews(*m_xr.instance, *m_xr.system_id, - XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, count, &count, - m_xr.view_configuration_views.data()) - != XR_SUCCESS) { - throw std::runtime_error("Failed to enumerate OpenXR " - "view configuration views"); - } - } - - std::vector swapchain_formats; - { - std::uint32_t count {}; - if (xrEnumerateSwapchainFormats(m_xr.session, 0, &count, nullptr) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get amount of OpenXR swapchain formats"); - } - swapchain_formats.resize(count); - if (xrEnumerateSwapchainFormats( - m_xr.session, count, &count, swapchain_formats.data()) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to enumerate OpenXR swapchain formats"); - } - } - - std::vector const swapchain_format_depth_needles { - GL_DEPTH_COMPONENT16, - // GL_DEPTH_COMPONENT32F, - // GL_DEPTH_COMPONENT24, - }; - std::vector::const_iterator const &swapchain_format_depth_it - = boost::range::find_first_of( - swapchain_formats, swapchain_format_depth_needles); - if (swapchain_format_depth_it == swapchain_formats.end()) { - throw std::runtime_error( - "Failed to find satisfying depth swapchain format"); - } - auto const swapchain_format_depth { *swapchain_format_depth_it }; - - std::vector const swapchain_format_color_needles { - GL_SRGB8_ALPHA8 - }; - auto const &swapchain_format_color_it { boost::range::find_first_of( - swapchain_formats, swapchain_format_color_needles) }; - if (swapchain_format_color_it == swapchain_formats.end()) { - throw std::runtime_error( - "Failed to find satisfying color swapchain format"); - } - auto const swapchain_format_color { *swapchain_format_color_it }; - - auto const AllocateSwapchainImageData { - [&](XrSwapchain swapchain, SwapchainType type, uint32_t count) { - m_xr.swapchain_images_map[swapchain].first = type; - m_xr.swapchain_images_map[swapchain].second.resize( - count, { XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_ES_KHR }); - return reinterpret_cast( - m_xr.swapchain_images_map[swapchain].second.data()); - } - }; - - auto const view_count { static_cast( - m_xr.view_configuration_views.size()) }; - uint32_t const eyeW { - m_xr.view_configuration_views[0].recommendedImageRectWidth - }; - uint32_t const eyeH { - m_xr.view_configuration_views[0].recommendedImageRectHeight - }; - uint32_t const bufW { eyeW * view_count }; - - m_xr.swapchains.color.resize(1); - m_xr.swapchains.depth.resize(1); - - { - auto &color_sc { m_xr.swapchains.color[0] }; - auto &depth_sc { m_xr.swapchains.depth[0] }; - auto &vcv { m_xr.view_configuration_views[0] }; - - { // Create color swapchain - XrSwapchainCreateInfo const ci { - .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT - | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT, - .format = swapchain_format_color, - .sampleCount = vcv.recommendedSwapchainSampleCount, - .width = bufW, - .height = eyeH, - .faceCount = 1, - .arraySize = 1, - .mipCount = 1, - }; - color_sc.swapchain_format = ci.format; - - if (xrCreateSwapchain(m_xr.session, &ci, &color_sc.swapchain) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to create OpenXR color swapchain"); - } - } - - { // Create depth swapchain - XrSwapchainCreateInfo const ci { - .type = XR_TYPE_SWAPCHAIN_CREATE_INFO, - .next = nullptr, - .createFlags = 0, - .usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT - | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, - .format = swapchain_format_depth, - .sampleCount = vcv.recommendedSwapchainSampleCount, - .width = bufW, - .height = eyeH, - .faceCount = 1, - .arraySize = 1, - .mipCount = 1, - }; - depth_sc.swapchain_format = ci.format; - - if (xrCreateSwapchain(m_xr.session, &ci, &depth_sc.swapchain) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to create OpenXR color swapchain"); - } - } - - // Enumerate swapchain images - uint32_t color_swapchain_image_count { 0 }; - if (xrEnumerateSwapchainImages( - color_sc.swapchain, 0, &color_swapchain_image_count, nullptr) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get Color Swapchain Images count."); - } - XrSwapchainImageBaseHeader *color_swapchain_images { - AllocateSwapchainImageData(color_sc.swapchain, SwapchainType::Color, - color_swapchain_image_count) - }; - if (xrEnumerateSwapchainImages(color_sc.swapchain, - color_swapchain_image_count, &color_swapchain_image_count, - color_swapchain_images) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to enumerate Color Swapchain Images."); - } - - uint32_t depth_swapchain_image_count {}; - res = xrEnumerateSwapchainImages( - depth_sc.swapchain, 0, &depth_swapchain_image_count, nullptr); - if (res != XR_SUCCESS) { - throw std::runtime_error(std::format( - "Failed to get Depth Swapchain Images count. {}", res)); - } - - XrSwapchainImageBaseHeader *depth_swapchain_images { - AllocateSwapchainImageData(depth_sc.swapchain, SwapchainType::Depth, - depth_swapchain_image_count) - }; - res = xrEnumerateSwapchainImages(depth_sc.swapchain, - depth_swapchain_image_count, &depth_swapchain_image_count, - depth_swapchain_images); - if (res != XR_SUCCESS) { - throw std::runtime_error( - "Failed to enumerate Depth Swapchain Images."); - } - - // Get image views - for (uint32_t j {}; j < color_swapchain_image_count; j++) { - GLuint framebuffer {}; - glGenFramebuffers(1, &framebuffer); - - GLenum const attachment { GL_COLOR_ATTACHMENT0 }; - - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, - GL_TEXTURE_2D, m_xr.get_swapchain_image(color_sc.swapchain, j), - 0); - - GLenum const result { glCheckFramebufferStatus( - GL_DRAW_FRAMEBUFFER) }; - if (result != GL_FRAMEBUFFER_COMPLETE) { - throw std::runtime_error("Failed to create color framebuffer"); - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - color_sc.image_views.push_back(framebuffer); - } - for (uint32_t j {}; j < depth_swapchain_image_count; j++) { - GLuint framebuffer {}; - glGenFramebuffers(1, &framebuffer); - - GLenum const attachment { GL_DEPTH_ATTACHMENT }; - - glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, - GL_TEXTURE_2D, m_xr.get_swapchain_image(depth_sc.swapchain, j), - 0); - - GLenum const result { glCheckFramebufferStatus( - GL_DRAW_FRAMEBUFFER) }; - if (result != GL_FRAMEBUFFER_COMPLETE) { - throw std::runtime_error("Failed to create depth framebuffer"); - } - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - depth_sc.image_views.push_back(framebuffer); - } - } - - if (!m_xr.instance) { - throw std::runtime_error( - "OpenXR instance is not initialized for some reason"); - } - if (!m_xr.system_id) { - throw std::runtime_error( - "OpenXR system ID is not initialized for some reason"); - } - - std::vector environment_blend_modes; - { // Get available blend modes - std::uint32_t count {}; - if (xrEnumerateEnvironmentBlendModes(*m_xr.instance, *m_xr.system_id, - XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &count, nullptr) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get OpenXR environment blend mode " - "count"); - } - environment_blend_modes.resize(count); - if (xrEnumerateEnvironmentBlendModes(*m_xr.instance, *m_xr.system_id, - XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, count, &count, - environment_blend_modes.data()) - != XR_SUCCESS) { - throw std::runtime_error( - "Failed to get XR environment blend modes"); - } - } - std::vector const - requested_environment_blend_modes { - XR_ENVIRONMENT_BLEND_MODE_OPAQUE, - XR_ENVIRONMENT_BLEND_MODE_ADDITIVE, - XR_ENVIRONMENT_BLEND_MODE_MAX_ENUM, - }; - - for (auto const &bm : requested_environment_blend_modes) { - m_xr.environment_blend_mode = bm; - if (std::ranges::contains(environment_blend_modes, bm)) { - break; - } - } - if (m_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."); - m_xr.environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; - } - - { // Reference space - XrReferenceSpaceCreateInfo const ci { - .type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO, - .next = nullptr, - .referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE, - .poseInReferenceSpace = { .orientation = { 0.0f, 0.0f, 0.0f, 1.0f }, - .position = { 0.0f, 0.0f, 0.0f } }, - }; - - if (xrCreateReferenceSpace(m_xr.session, &ci, &m_xr.local_space) - != XR_SUCCESS) { - throw std::runtime_error("Failed to create OpenXR reference space"); - } - } - - { // 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(m_xr.session, &ci, &m_xr.view_space) - != XR_SUCCESS) { - throw std::runtime_error("Failed to create OpenXR reference space"); - } - } - - { // Create hand trackers - for (std::size_t i = 0; i < m_xr.hands.size(); i++) { - auto &hand = m_xr.hands.at(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 = m_xr.CreateHandTrackerEXT( - m_xr.session, &ci, &hand.hand_tracker); - if (res != XR_SUCCESS) { - throw std::runtime_error( - std::format("Failed to create hand tracker: {}", res)); - } - } - } -} - -void LunarWM::poll_events_xr() -{ - XrEventDataBuffer event_data { XR_TYPE_EVENT_DATA_BUFFER }; - auto XrPollEvents { [&]() -> bool { - event_data = { .type = XR_TYPE_EVENT_DATA_BUFFER }; - return xrPollEvent(*m_xr.instance, &event_data) == XR_SUCCESS; - } }; - - while (XrPollEvents()) { - switch (event_data.type) { - case XR_TYPE_EVENT_DATA_EVENTS_LOST: { - auto *el { reinterpret_cast(&event_data) }; - wlr_log(WLR_INFO, "OPENXR: Events Lost: %d", el->lostEventCount); - break; - } - case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { - auto *ilp { reinterpret_cast( - &event_data) }; - wlr_log(WLR_INFO, "OPENXR: Instance Loss Pending at: %ld", - ilp->lossTime); - m_session_running = false; - m_running = false; - break; - } - case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { - auto *ipc { - reinterpret_cast( - &event_data) - }; - wlr_log(WLR_INFO, - "OPENXR: Interaction Profile changed for Session: " - "%p", - ipc->session); - if (ipc->session != m_xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataInteractionProfileChanged for " - "unknown Session"); - break; - } - break; - } - case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { - auto *scp { - reinterpret_cast( - &event_data) - }; - wlr_log(WLR_INFO, - "OPENXR: Reference Space Change pending for " - "Session: %p", - scp->session); - if (scp->session != m_xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataReferenceSpaceChangePending for " - "unknown " - "Session"); - break; - } - break; - } - case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { - auto *sc { reinterpret_cast( - &event_data) }; - if (sc->session != m_xr.session) { - wlr_log(WLR_ERROR, - "XrEventDataSessionStateChanged for unknown " - "Session"); - break; - } - - if (sc->state == XR_SESSION_STATE_READY) { - XrSessionBeginInfo bi { XR_TYPE_SESSION_BEGIN_INFO }; - bi.primaryViewConfigurationType - = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - bi.primaryViewConfigurationType - = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - if (xrBeginSession(m_xr.session, &bi) != XR_SUCCESS) { - throw std::runtime_error("Failed to begin session"); - } - m_session_running = true; - } - if (sc->state == XR_SESSION_STATE_STOPPING) { - if (xrEndSession(m_xr.session) != XR_SUCCESS) { - throw std::runtime_error("Failed to end session"); - } - m_session_running = false; - } - if (sc->state == XR_SESSION_STATE_EXITING) { - m_session_running = false; - m_running = false; - } - if (sc->state == XR_SESSION_STATE_LOSS_PENDING) { - m_session_running = false; - m_running = false; - } - m_xr.session_state = sc->state; - break; - } - default: { - break; - } - } - } -} - -inline constexpr std::array kHandJointMap = { - /* 0 PALM */ 25, - /* 1 WRIST */ 0, - /* 2 THUMB_METACARPAL */ 1, - /* 3 THUMB_PROXIMAL */ 2, - /* 4 THUMB_DISTAL */ 3, - /* 5 THUMB_TIP */ 4, - /* 6 INDEX_METACARPAL */ 5, - /* 7 INDEX_PROXIMAL */ 6, - /* 8 INDEX_INTERMEDIATE */ 7, - /* 9 INDEX_DISTAL */ 8, - /* 10 INDEX_TIP */ 9, - /* 11 MIDDLE_METACARPAL */ 10, - /* 12 MIDDLE_PROXIMAL */ 11, - /* 13 MIDDLE_INTERMEDIATE */ 12, - /* 14 MIDDLE_DISTAL */ 13, - /* 15 MIDDLE_TIP */ 14, - /* 16 RING_METACARPAL */ 15, - /* 17 RING_PROXIMAL */ 16, - /* 18 RING_INTERMEDIATE */ 17, - /* 19 RING_DISTAL */ 18, - /* 20 RING_TIP */ 19, - /* 21 LITTLE_METACARPAL */ 20, - /* 22 LITTLE_PROXIMAL */ 21, - /* 23 LITTLE_INTERMEDIATE */ 22, - /* 24 LITTLE_DISTAL */ 23, - /* 25 LITTLE_TIP */ 24, -}; - -[[nodiscard]] -constexpr auto static openxr_joint_to_model(std::uint32_t xrIndex) noexcept - -> std::optional -{ - if (xrIndex < kHandJointMap.size()) { - return kHandJointMap.at(xrIndex); - } - - return std::nullopt; -} - -auto LunarWM::render_layer(RenderLayerInfo &info, float dt) -> bool -{ - // locate views - auto const view_count { static_cast( - m_xr.view_configuration_views.size()) }; - std::vector views(view_count, { XR_TYPE_VIEW }); - - XrViewLocateInfo locInfo { XR_TYPE_VIEW_LOCATE_INFO }; - locInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; - locInfo.displayTime = info.predicted_display_time; - locInfo.space = m_xr.local_space; - - XrViewState viewState { XR_TYPE_VIEW_STATE }; - std::uint32_t located {}; - if (xrLocateViews(m_xr.session, &locInfo, &viewState, view_count, &located, - views.data()) - != XR_SUCCESS - || located != view_count) { - wlr_log(WLR_ERROR, "Failed to locate views"); - return false; - } - - // acquire swapchain images - auto &color_sc { m_xr.swapchains.color[0] }; - auto &depth_sc { m_xr.swapchains.depth[0] }; - - auto acquire_wait { [](XrSwapchain sc, std::uint32_t &idx) -> bool { - XrSwapchainImageAcquireInfo const ai { - XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO - }; - if (xrAcquireSwapchainImage(sc, &ai, &idx) != XR_SUCCESS) { - return false; - } - XrSwapchainImageWaitInfo wi { XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO }; - wi.timeout = XR_INFINITE_DURATION; - return xrWaitSwapchainImage(sc, &wi) == XR_SUCCESS; - } }; - - std::uint32_t colIdx {}; - std::uint32_t depIdx {}; - if (!acquire_wait(color_sc.swapchain, colIdx) - || !acquire_wait(depth_sc.swapchain, depIdx)) { - wlr_log(WLR_ERROR, "Swap-chain acquire failed"); - return false; - } - - GLuint color_tex { m_xr.get_swapchain_image(color_sc.swapchain, colIdx) }; - GLuint depth_tex { m_xr.get_swapchain_image(depth_sc.swapchain, depIdx) }; - - // build FBO - if (m_renderer.fbo == 0u) { - glGenFramebuffers(1, &m_renderer.fbo); - } - glBindFramebuffer(GL_FRAMEBUFFER, m_renderer.fbo); - rlFramebufferAttach(m_renderer.fbo, color_tex, RL_ATTACHMENT_COLOR_CHANNEL0, - RL_ATTACHMENT_TEXTURE2D, 0); - rlFramebufferAttach(m_renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH, - RL_ATTACHMENT_TEXTURE2D, 0); - assert(rlFramebufferComplete(m_renderer.fbo)); - - std::uint32_t const eyeW { - m_xr.view_configuration_views[0].recommendedImageRectWidth - }; - std::uint32_t const eyeH { - m_xr.view_configuration_views[0].recommendedImageRectHeight - }; - - m_renderer.tmp_rt = { .id = m_renderer.fbo, - .texture = { color_tex, static_cast(eyeW * view_count), - static_cast(eyeH), 1, -1 }, - .depth = { depth_tex, static_cast(eyeW * view_count), - static_cast(eyeH), 1, -1 } }; - - // head-space view matrix (matches rlOpenXR) - XrSpaceLocation headLoc { XR_TYPE_SPACE_LOCATION }; - xrLocateSpace(m_xr.view_space, m_xr.local_space, - info.predicted_display_time, &headLoc); - Matrix const headView { MatrixInvert(xr_matrix(headLoc.pose)) }; - - // per-eye projection + view-offset - Matrix const projR { xr_projection_matrix(views[0].fov) }; - Matrix const projL { xr_projection_matrix(views[1].fov) }; - Matrix const viewOffL { MatrixMultiply( - xr_matrix(views[0].pose), headView) }; - Matrix const viewOffR { MatrixMultiply( - xr_matrix(views[1].pose), headView) }; - - { - static ModelAnimation anim = {}; - if (anim.framePoses == nullptr) { - anim.boneCount = m_renderer.hands.at(0).boneCount; - anim.frameCount = 1; - anim.bones = m_renderer.hands.at(0).bones; - anim.framePoses - = static_cast(MemAlloc(sizeof(Transform *))); - anim.framePoses[0] = // NOLINT - static_cast( - MemAlloc(sizeof(Transform) * anim.boneCount)); - } - - for (int h = 0; h < 2; ++h) { - auto &handInfo = m_xr.hands.at(h); - - for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { - auto const &pose - = m_xr.hands.at(h).joint_locations[k].pose; // NOLINT - auto const &jl = handInfo.joint_locations[k]; // NOLINT - - Vector3 const pos { - jl.pose.position.x, - jl.pose.position.y, - jl.pose.position.z, - }; - - auto const v_w = openxr_joint_to_model(k); - if (!v_w) { - throw std::runtime_error("Invalid OpenXR joint index"); - } - - anim.framePoses[0][*v_w] = // NOLINT - { - .translation = pos, - .rotation = { - jl.pose.orientation.x, - jl.pose.orientation.y, - jl.pose.orientation.z, - jl.pose.orientation.w, - }, - .scale = { 1,1,1 } - }; - } - - UpdateModelAnimation(m_renderer.hands.at(h), anim, 0); - } - } - - // draw - BeginTextureMode(m_renderer.tmp_rt); - - rlEnableStereoRender(); - rlSetMatrixProjectionStereo(projR, projL); // right, left (yes) - rlSetMatrixViewOffsetStereo(viewOffR, viewOffL); - - glViewport(0, 0, static_cast(eyeW * view_count), - static_cast(eyeH)); - ClearBackground({ 0, 0, 10, 255 }); - - for (int i = 0; i < 1; i++) { - XrTime const time = info.predicted_display_time; - - XrSpaceLocation view_location { XR_TYPE_SPACE_LOCATION }; - XrResult const result = xrLocateSpace( - m_xr.view_space, m_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; - m_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 }); - m_renderer.camera.target = m_renderer.camera.position + forward; - m_renderer.camera.up = up; - } - } - - BeginMode3D(m_renderer.camera); - { - render_3d(dt); - } - EndMode3D(); - - rlDisableStereoRender(); - EndTextureMode(); - - // release swapchain images - XrSwapchainImageReleaseInfo const ri { - XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO - }; - xrReleaseSwapchainImage(color_sc.swapchain, &ri); - xrReleaseSwapchainImage(depth_sc.swapchain, &ri); - - // fill projection layer - info.layer_projection_views.resize( - view_count, { XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW }); - - for (std::uint32_t i {}; i < view_count; ++i) { - int32_t const xOff { static_cast(i) - * static_cast(eyeW) }; - 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 = { .x = xOff, .y = 0 }; - pv.subImage.imageRect.extent = { .width = static_cast(eyeW), - .height = static_cast(eyeH) }; - pv.subImage.imageArrayIndex = 0; - } - - info.layer_projection = { - .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, - .layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT - | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT, - .space = m_xr.local_space, - .viewCount = view_count, - .views = info.layer_projection_views.data(), - }; - - info.layers.clear(); - info.layers.push_back(reinterpret_cast( - &info.layer_projection)); - - return true; -} - -LunarWM::~LunarWM() -{ - if (m_xr.local_space != XR_NULL_HANDLE) { - xrDestroySpace(m_xr.local_space); - } - - for (size_t i {}; i < m_xr.swapchains.color.size(); i++) { - auto &color_sc { m_xr.swapchains.color[i] }; - auto &depth_sc { m_xr.swapchains.depth[i] }; - - for (auto &iv : color_sc.image_views) { - glDeleteFramebuffers(1, &iv); - } - for (auto &iv : depth_sc.image_views) { - glDeleteFramebuffers(1, &iv); - } - - xrDestroySwapchain(color_sc.swapchain); - xrDestroySwapchain(depth_sc.swapchain); - } - - if (m_xr.session != XR_NULL_HANDLE) { - xrDestroySession(m_xr.session); - - for (auto const &hand : m_xr.hands) { - m_xr.DestroyHandTrackerEXT(hand.hand_tracker); - } - } - - if (m_xr.instance) { - xrDestroyInstance(*m_xr.instance); - } - - wl_list_remove(&m_wayland.keyboards); - - wl_display_destroy_clients(m_wayland.display); - if (m_wayland.allocator != nullptr) { - wlr_allocator_destroy(m_wayland.allocator); - } - if (m_wayland.renderer != nullptr) { - wlr_renderer_destroy(m_wayland.renderer); - } - if (m_wayland.backend != nullptr) { - wlr_backend_destroy(m_wayland.backend); - } - if (m_wayland.display != nullptr) { - wl_display_destroy(m_wayland.display); - } -} - -void LunarWM::run() -{ - if (!wlr_backend_start(m_wayland.backend)) { - throw std::runtime_error("Failed to start backend"); - } - - auto const *socket { wl_display_add_socket_auto(m_wayland.display) }; - if (socket == nullptr) { - throw std::runtime_error("Failed to add wayland socket to display"); - } - - setenv("WAYLAND_DISPLAY", socket, 1); // NOLINT - wlr_log(WLR_INFO, "Running compositor on WAYLAND_DISPLAY=%s", socket); - - m_running = true; - while (m_running) { - auto now { Clock::now() }; - float const dt { - std::chrono::duration(now - m_last_tick).count() - }; - m_last_tick = now; - - wl_display_flush_clients(m_wayland.display); - wl_event_loop_dispatch(m_wayland.event_loop, 0); - - auto *draw { eglGetCurrentSurface(EGL_DRAW) }; - auto *read { eglGetCurrentSurface(EGL_READ) }; - if (eglMakeCurrent(m_wayland.egl_display, EGL_NO_SURFACE, - EGL_NO_SURFACE, m_wayland.egl_context) - == EGL_FALSE) { - throw std::runtime_error("Failed to eglMakeCurrent"); - } - - poll_events_xr(); - - XrFrameState frame_state { - .type = XR_TYPE_FRAME_STATE, - }; - XrFrameWaitInfo const frame_wait_info { - .type = XR_TYPE_FRAME_WAIT_INFO, - .next = nullptr, - }; - if (xrWaitFrame(m_xr.session, &frame_wait_info, &frame_state) - != XR_SUCCESS) { - throw std::runtime_error("Failed to wait for OpenXR frame"); - } - - XrFrameBeginInfo const frame_begin_info { - .type = XR_TYPE_FRAME_BEGIN_INFO, - .next = nullptr, - }; - XrResult res { xrBeginFrame(m_xr.session, &frame_begin_info) }; - if (res != XR_FRAME_DISCARDED && res != XR_SUCCESS) { - throw std::runtime_error( - std::format("Failed to begin the OpenXR Frame: {}", res)); - } - - if (static_cast( - m_xr.hand_tracking_system_properties.supportsHandTracking)) { - XrActionStateGetInfo const si { .type - = XR_TYPE_ACTION_STATE_GET_INFO }; - for (auto &hand : m_xr.hands) { - bool const unobstructed { true }; - - XrHandJointsMotionRangeInfoEXT mri { .type - = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT }; - if (unobstructed) { - mri.handJointsMotionRange - = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; - } else { - mri.handJointsMotionRange - = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; - } - - XrHandJointsLocateInfoEXT const li { - .type = XR_TYPE_HAND_JOINTS_LOCATE_INFO_EXT, - .next = static_cast(&mri), - .baseSpace = m_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.data(), - }; - - if (m_xr.LocateHandJointsEXT(hand.hand_tracker, &li, &hji) - != XR_SUCCESS) { - throw std::runtime_error("Failed to locate hand joints"); - } - } - } - - WindowShouldClose(); - - RenderLayerInfo render_layer_info { - .predicted_display_time = frame_state.predictedDisplayTime, - }; - bool const session_active { ( - m_xr.session_state == XR_SESSION_STATE_SYNCHRONIZED - || m_xr.session_state == XR_SESSION_STATE_VISIBLE - || m_xr.session_state == XR_SESSION_STATE_FOCUSED) }; - if (session_active && (frame_state.shouldRender != 0u)) { - auto rendered { this->render_layer(render_layer_info, dt) }; - if (rendered) { - render_layer_info.layers.push_back( - reinterpret_cast( - &render_layer_info.layer_projection)); - } - } - - XrFrameEndInfo const frame_end_info { - .type = XR_TYPE_FRAME_END_INFO, - .displayTime = frame_state.predictedDisplayTime, - .environmentBlendMode = m_xr.environment_blend_mode, - .layerCount - = static_cast(render_layer_info.layers.size()), - .layers = render_layer_info.layers.data(), - }; - if (xrEndFrame(m_xr.session, &frame_end_info) != XR_SUCCESS) { - throw std::runtime_error("Failed to end OpenXR frame"); - } - - BeginDrawing(); - EndDrawing(); - - if (eglMakeCurrent( - m_wayland.egl_display, draw, read, m_wayland.egl_context) - == EGL_FALSE) { - throw std::runtime_error("Failed to eglMakeCurrent"); - } - - struct timespec n {}; - if (clock_gettime(CLOCK_MONOTONIC, &n) != 0) { - throw std::runtime_error( - std::format("Failed call to gettime. errno={} ({})", errno, - strerror(errno))); // NOLINT - } - for (auto const &tl : m_wayland.toplevels) { - if (tl->surface != nullptr) { - wlr_surface_send_frame_done(tl->surface, &n); - } - } - } -} - -static void DrawBillboardNoShear(Camera3D const &cam, Texture2D tex, - Vector3 pos, float scale = 1.0f, Color tint = WHITE) -{ - Rectangle const src { 0, 0, static_cast(tex.width), - static_cast(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 LunarWM::render_3d(float /*dt*/) -{ - DrawGrid(10, 1); - - for (auto const &tl : m_wayland.toplevels) { - tl->update(); - DrawBillboardNoShear( - m_renderer.camera, tl->rl_texture, { 0, 1, -1.4f }, 1.0f); - } - - for (int h = 0; h < 2; ++h) { - auto &handInfo = m_xr.hands.at(h); - for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) { - auto const &jl = handInfo.joint_locations[k]; // NOLINT - Vector3 const pos { - jl.pose.position.x, - jl.pose.position.y, - jl.pose.position.z, - }; - DrawSphere(pos, jl.radius, RED); - } - - // DrawModel(m_renderer.hands.at(h), { 0, 0, 0 }, 1.0f, - // WHITE); - } -} - -void LunarWM::terminate() -{ - wlr_log(WLR_INFO, "Stopping compositor"); - m_running = false; -} - -} // namespace LunarWM diff --git a/src/LunarWM.h b/src/LunarWM.h new file mode 100644 index 0000000..7473d5a --- /dev/null +++ b/src/LunarWM.h @@ -0,0 +1,173 @@ +#ifndef LUNAR_WM_H +#define LUNAR_WM_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common.h" + +struct LunarWM; + +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_listener commit; + struct wl_listener destroy; + + struct wlr_xdg_toplevel *xdg_toplevel; + struct wlr_surface *surface; + struct wlr_texture *texture; + + struct wlr_buffer *locked_buffer; + struct wlr_gles2_texture_attribs attribs; + struct wlr_gles2_texture *gles_texture; + Texture2D rl_texture; +} LunarWM_Toplevel; + +bool LunarWM_Toplevel_init( + LunarWM_Toplevel *tl, struct LunarWM *wm, struct wlr_xdg_toplevel *xdg); +bool LunarWM_Toplevel_destroy(LunarWM_Toplevel *this); + +bool LunarWM_Toplevel_update(LunarWM_Toplevel *this); + +typedef struct { + XrSwapchain swapchain; + int64_t swapchain_format; + GLuint *v_image_views; +} LunarWM_SwapchainInfo; + +typedef struct { + XrSwapchain handle; + XrSwapchainImageOpenGLESKHR *a_imgs; + uint32_t a_imgs_count; +} LunarWM_SwapchainImagesEntry; + +typedef struct { + XrHandJointLocationEXT joint_locations[XR_HAND_JOINT_COUNT_EXT]; + XrHandTrackerEXT hand_tracker; +} LunarWM_Hand; + +typedef struct { + XrTime predicted_display_time; + XrCompositionLayerProjection layer_projection; + XrCompositionLayerBaseHeader *layers[10]; + uint32_t layers_count; + XrCompositionLayerProjectionView + layer_projection_views[10]; // Hopefully we dont have more than 10. + uint32_t layer_projection_views_count; +} LunarWM_RenderLayerInfo; + +typedef struct LunarWM { + struct { + struct wl_display *display; + struct wl_event_loop *event_loop; + + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_session *session; + + struct wlr_egl *egl; + EGLDisplay egl_display; + EGLContext egl_context; + EGLConfig egl_config; + + struct wlr_allocator *allocator; + struct wlr_compositor *compositor; + struct wlr_subcompositor *subcompositor; + struct wlr_data_device_manager *data_device_manager; + + struct wlr_seat *seat; + struct wl_list keyboards; + struct wl_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_cursor *cursor; + + LunarWM_Toplevel **v_toplevels; + } wayland; + + struct { + XrInstance instance; + XrSystemId system_id; + XrSession session; + XrSessionState session_state; + struct { + LunarWM_SwapchainInfo *v_color; + LunarWM_SwapchainInfo *v_depth; + } swapchains; + LunarWM_SwapchainImagesEntry + swapchain_images[2]; // 0 is color, 1 is depth + XrViewConfigurationView *a_view_configuration_views; + uint32_t view_configuration_views_count; + XrEnvironmentBlendMode environment_blend_mode; + XrSpace local_space, view_space; + LunarWM_Hand hands[2]; + XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties; + + PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT; + PFN_xrDestroyHandTrackerEXT DestroyHandTrackerEXT; + PFN_xrLocateHandJointsEXT LocateHandJointsEXT; + + bool session_running; + } xr; + + struct { + GLuint fbo; + RenderTexture2D tmp_rt; + Camera3D camera; + } renderer; + + bool initialized; + bool running; +} LunarWM; + +bool LunarWM_init(LunarWM *wm); +void LunarWM_destroy(LunarWM *this); + +void LunarWM_terminate(LunarWM *this); +void LunarWM_run(LunarWM *this); + +extern LunarWM g_wm; + +#endif // LUNAR_WM_H diff --git a/src/Math.cppm b/src/Math.cppm deleted file mode 100644 index a5c1815..0000000 --- a/src/Math.cppm +++ /dev/null @@ -1,224 +0,0 @@ -export module LunarWM.Math; - -import std; - -export namespace LunarWM::Math { - -template -requires std::is_arithmetic_v struct Vec2 { - template - requires std::is_arithmetic_v - auto operator+(Vec2 const &other) -> Vec2 - { - return { x + other.x, y + other.y }; - } - template - requires std::is_arithmetic_v - auto operator-(Vec2 const &other) -> Vec2 - { - return { x - other.x, y - other.y }; - } - template - requires std::is_arithmetic_v - auto operator*(Vec2 const &other) -> Vec2 - { - return { x * other.x, y * other.x }; - } - template - requires std::is_arithmetic_v auto operator*(T scalar) -> Vec2 - { - return { x * scalar, y * scalar }; - } - template - requires std::is_arithmetic_v auto operator/(T scalar) -> Vec2 - { - return { x / scalar, y / scalar }; - } - template - requires std::is_arithmetic_v auto operator-() -> Vec2 - { - return { -x, -y }; - } - auto length() const -> T { return std::sqrt(x * x + y * y); } - auto lengthSquared() const -> T { return x * x + y * y; } - auto normalized() const -> Vec2 - { - T len = length(); - if (len == T(0)) { - return { T(0), T(0) }; - } - return *this / len; - } - - T x, y; -}; - -template -requires std::is_arithmetic_v -auto operator*(T scalar, Vec2 const &v) -> Vec2 -{ - return { v.x * scalar, v.y * scalar }; -} - -template -requires std::is_arithmetic_v struct Rect { - Rect(Vec2 pos, Vec2 size) - : pos(pos) - , size(size) - { - } - Rect(T x, T y, T w, T h) - : pos({ x, y }) - , size({ w, h }) - { - } - - auto x() -> T & { return pos.x; } - auto y() -> T & { return pos.y; } - auto w() -> T & { return size.x; } - auto h() -> T & { return size.y; } - - auto x() const -> T { return pos.x; } - auto y() const -> T { return pos.y; } - auto w() const -> T { return size.x; } - auto h() const -> T { return size.y; } - - auto left() const -> T { return x(); } - auto right() const -> T { return x() + w(); } - auto top() const -> T { return y(); } - auto bottom() const -> T { return y() + h(); } - - auto left() -> T & { return x(); } - auto top() -> T & { return y(); } - - Vec2 pos, size; -}; - -template -requires std::is_arithmetic_v struct Box { - template - requires std::is_arithmetic_v auto operator[](U const index) -> Vec2 & - { - if (index < 0 || index > 1) { - throw std::out_of_range("A box only has two points"); - } - return m_data[index]; - } - auto first() -> Vec2 & { return m_data[0]; } - auto second() -> Vec2 & { return m_data[1]; } - auto x0() -> T & { return m_data[0].x; } - auto y0() -> T & { return m_data[0].y; } - auto x1() -> T & { return m_data[1].x; } - auto y1() -> T & { return m_data[1].y; } - auto left() -> T & - { - if (x0() < x1()) { - return x0(); - } - return x1(); - } - auto right() -> T & - { - if (x0() > x1()) { - return x0(); - } - return x1(); - } - auto top() -> T & - { - if (y0() < y1()) { - return y0(); - } - return y1(); - } - auto bottom() -> T & - { - if (y0() > y1()) { - return y0(); - } - return y1(); - } - - Box() = default; - explicit Box(Rect rect) - { - this->m_data[0] = rect.pos; - this->m_data[1] = rect.pos + rect.size; - } - -private: - std::array, 2> m_data = {}; -}; - -template -requires std::is_arithmetic_v -auto subtract_rect(Math::Rect const &src, Math::Rect const &clip) - -> std::vector> -{ - std::vector> result; - - auto sx = src.x(); - auto sy = src.y(); - auto sw = src.w(); - auto sh = src.h(); - - auto cx = clip.x(); - auto cy = clip.y(); - auto cw = clip.w(); - auto ch = clip.h(); - - T s_right = sx + sw; - T s_bottom = sy + sh; - - T c_right = cx + cw; - T c_bottom = cy + ch; - - // No overlap → keep src - if (c_right <= sx || cx >= s_right || c_bottom <= sy || cy >= s_bottom) { - result.push_back(src); - return result; - } - - // Top piece - if (cy > sy) { - result.emplace_back(sx, sy, sw, cy - sy); - } - - // Bottom piece - if (c_bottom < s_bottom) { - result.emplace_back(sx, c_bottom, sw, s_bottom - c_bottom); - } - - // Middle pieces left and right of clip - T middle_top = std::max(sy, cy); - T middle_bottom = std::min(s_bottom, c_bottom); - T middle_height = middle_bottom - middle_top; - - if (middle_height > 0) { - // Left piece - if (cx > sx) { - result.emplace_back(sx, middle_top, cx - sx, middle_height); - } - - // Right piece - if (c_right < s_right) { - result.emplace_back( - c_right, middle_top, s_right - c_right, middle_height); - } - } - - return result; -} - -template -requires std::is_arithmetic_v struct Viewport { - Rect rect; - Vec2 depth_limits; -}; - -template constexpr auto deg2rad(T degrees) -> T -{ - return degrees * (std::numbers::pi / 180.0); -} - -} // namespace LunarWM::Math diff --git a/src/Util.cppm b/src/Util.cppm deleted file mode 100644 index 066b5e9..0000000 --- a/src/Util.cppm +++ /dev/null @@ -1,676 +0,0 @@ -module; - -// NOLINTBEGIN -#include -// NOLINTEND - -export module LunarWM.Util; - -import std; - -namespace std { -template<> struct formatter { - template constexpr auto parse(ParseContext &ctx) - { - return ctx.begin(); - } - - static constexpr auto to_string(XrResult r) -> std::string_view - { - switch (r) { - case XR_FRAME_DISCARDED: - return "XR_FRAME_DISCARDED"; - case XR_ERROR_VALIDATION_FAILURE: - return "XR_ERROR_VALIDATION_FAILURE: The function usage was " - "invalid in " - "some way."; - case XR_ERROR_RUNTIME_FAILURE: - return "XR_ERROR_RUNTIME_FAILURE: The runtime failed to handle the " - "function in an unexpected way that is not covered by " - "another " - "error result."; - case XR_ERROR_OUT_OF_MEMORY: - return "XR_ERROR_OUT_OF_MEMORY: A memory allocation has failed."; - case XR_ERROR_API_VERSION_UNSUPPORTED: - return "XR_ERROR_API_VERSION_UNSUPPORTED: The runtime does not " - "support " - "the requested API version."; - case XR_ERROR_INITIALIZATION_FAILED: - return "XR_ERROR_INITIALIZATION_FAILED: Initialization of object " - "could " - "not be completed."; - case XR_ERROR_FUNCTION_UNSUPPORTED: - return "XR_ERROR_FUNCTION_UNSUPPORTED: The requested function was " - "not " - "found or is otherwise unsupported."; - case XR_ERROR_FEATURE_UNSUPPORTED: - return "XR_ERROR_FEATURE_UNSUPPORTED: The requested feature is not " - "supported."; - case XR_ERROR_EXTENSION_NOT_PRESENT: - return "XR_ERROR_EXTENSION_NOT_PRESENT: A requested extension is " - "not " - "supported."; - case XR_ERROR_LIMIT_REACHED: - return "XR_ERROR_LIMIT_REACHED: The runtime supports no more of " - "the " - "requested resource."; - case XR_ERROR_SIZE_INSUFFICIENT: - return "XR_ERROR_SIZE_INSUFFICIENT: The supplied size was smaller " - "than " - "required."; - case XR_ERROR_HANDLE_INVALID: - return "XR_ERROR_HANDLE_INVALID: A supplied object handle was " - "invalid."; - case XR_ERROR_INSTANCE_LOST: - return "XR_ERROR_INSTANCE_LOST: The XrInstance was lost or could " - "not be " - "found. It will need to be destroyed and optionally " - "recreated."; - case XR_ERROR_SESSION_RUNNING: - return "XR_ERROR_SESSION_RUNNING: The session is already running."; - case XR_ERROR_SESSION_NOT_RUNNING: - return "XR_ERROR_SESSION_NOT_RUNNING: The session is not yet " - "running."; - case XR_ERROR_SESSION_LOST: - return "XR_ERROR_SESSION_LOST: The XrSession was lost. It will " - "need to " - "be destroyed and optionally recreated."; - case XR_ERROR_SYSTEM_INVALID: - return "XR_ERROR_SYSTEM_INVALID: The provided XrSystemId was " - "invalid."; - case XR_ERROR_PATH_INVALID: - return "XR_ERROR_PATH_INVALID: The provided XrPath was not valid."; - case XR_ERROR_PATH_COUNT_EXCEEDED: - return "XR_ERROR_PATH_COUNT_EXCEEDED: The maximum number of " - "supported " - "semantic paths has been reached."; - case XR_ERROR_PATH_FORMAT_INVALID: - return "XR_ERROR_PATH_FORMAT_INVALID: The semantic path character " - "format " - "is invalid."; - case XR_ERROR_PATH_UNSUPPORTED: - return "XR_ERROR_PATH_UNSUPPORTED: The semantic path is " - "unsupported."; - case XR_ERROR_LAYER_INVALID: - return "XR_ERROR_LAYER_INVALID: The layer was NULL or otherwise " - "invalid."; - case XR_ERROR_LAYER_LIMIT_EXCEEDED: - return "XR_ERROR_LAYER_LIMIT_EXCEEDED: The number of specified " - "layers is " - "greater than the supported number."; - case XR_ERROR_SWAPCHAIN_RECT_INVALID: - return "XR_ERROR_SWAPCHAIN_RECT_INVALID: The image rect was " - "negatively " - "sized or otherwise invalid."; - case XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED: - return "XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED: The image format is " - "not " - "supported by the runtime or platform."; - case XR_ERROR_ACTION_TYPE_MISMATCH: - return "XR_ERROR_ACTION_TYPE_MISMATCH: The API used to retrieve an " - "action’s state does not match the action’s type."; - case XR_ERROR_SESSION_NOT_READY: - return "XR_ERROR_SESSION_NOT_READY: The session is not in the " - "ready " - "state."; - case XR_ERROR_SESSION_NOT_STOPPING: - return "XR_ERROR_SESSION_NOT_STOPPING: The session is not in the " - "stopping state."; - case XR_ERROR_TIME_INVALID: - return "XR_ERROR_TIME_INVALID: The provided XrTime was zero, " - "negative, " - "or out of range."; - case XR_ERROR_REFERENCE_SPACE_UNSUPPORTED: - return "XR_ERROR_REFERENCE_SPACE_UNSUPPORTED: The specified " - "reference " - "space is not supported by the runtime or system."; - case XR_ERROR_FILE_ACCESS_ERROR: - return "XR_ERROR_FILE_ACCESS_ERROR: The file could not be " - "accessed."; - case XR_ERROR_FILE_CONTENTS_INVALID: - return "XR_ERROR_FILE_CONTENTS_INVALID: The file’s contents were " - "invalid."; - case XR_ERROR_FORM_FACTOR_UNSUPPORTED: - return "XR_ERROR_FORM_FACTOR_UNSUPPORTED: The specified form " - "factor is " - "not supported by the current runtime or platform."; - case XR_ERROR_FORM_FACTOR_UNAVAILABLE: - return "XR_ERROR_FORM_FACTOR_UNAVAILABLE: The specified form " - "factor is " - "supported, but the device is currently not available, e.g. " - "not " - "plugged in or powered off."; - case XR_ERROR_API_LAYER_NOT_PRESENT: - return "XR_ERROR_API_LAYER_NOT_PRESENT: A requested API layer is " - "not " - "present or could not be loaded."; - case XR_ERROR_CALL_ORDER_INVALID: - return "XR_ERROR_CALL_ORDER_INVALID: The call was made without " - "having " - "made a previously required call."; - case XR_ERROR_GRAPHICS_DEVICE_INVALID: - return "XR_ERROR_GRAPHICS_DEVICE_INVALID: The given graphics " - "device is " - "not in a valid state. The graphics device could be lost or " - "initialized without meeting graphics requirements."; - case XR_ERROR_POSE_INVALID: - return "XR_ERROR_POSE_INVALID: The supplied pose was invalid with " - "respect to the requirements."; - case XR_ERROR_INDEX_OUT_OF_RANGE: - return "XR_ERROR_INDEX_OUT_OF_RANGE: The supplied index was " - "outside the " - "range of valid indices."; - case XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED: - return "XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED: The " - "specified view " - "configuration type is not supported by the runtime or " - "platform."; - case XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED: - return "XR_ERROR_ENVIRONMENT_BLEND_MODE_UNSUPPORTED: The specified " - "environment blend mode is not supported by the runtime or " - "platform."; - case XR_ERROR_NAME_DUPLICATED: - return "XR_ERROR_NAME_DUPLICATED: The name provided was a " - "duplicate of " - "an already-existing resource."; - case XR_ERROR_NAME_INVALID: - return "XR_ERROR_NAME_INVALID: The name provided was invalid."; - case XR_ERROR_ACTIONSET_NOT_ATTACHED: - return "XR_ERROR_ACTIONSET_NOT_ATTACHED: A referenced action set " - "is not " - "attached to the session."; - case XR_ERROR_ACTIONSETS_ALREADY_ATTACHED: - return "XR_ERROR_ACTIONSETS_ALREADY_ATTACHED: The session already " - "has " - "attached action sets."; - case XR_ERROR_LOCALIZED_NAME_DUPLICATED: - return "XR_ERROR_LOCALIZED_NAME_DUPLICATED: The localized name " - "provided " - "was a duplicate of an already-existing resource."; - case XR_ERROR_LOCALIZED_NAME_INVALID: - return "XR_ERROR_LOCALIZED_NAME_INVALID: The localized name " - "provided was " - "invalid."; - case XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING: - return "XR_ERROR_GRAPHICS_REQUIREMENTS_CALL_MISSING: The " - "xrGetGraphicsRequirements* call was not made before " - "calling " - "xrCreateSession."; - case XR_ERROR_RUNTIME_UNAVAILABLE: - return "XR_ERROR_RUNTIME_UNAVAILABLE: The loader was unable to " - "find or " - "load a runtime."; - case XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED: - return "XR_ERROR_EXTENSION_DEPENDENCY_NOT_ENABLED: One or more of " - "the " - "extensions being enabled has dependency on extensions that " - "are " - "not enabled."; - case XR_ERROR_PERMISSION_INSUFFICIENT: - return "XR_ERROR_PERMISSION_INSUFFICIENT: Insufficient " - "permissions. This " - "error is included for use by vendor extensions. The " - "precise " - "definition of XR_ERROR_PERMISSION_INSUFFICIENT and actions " - "possible by the developer or user to resolve it can vary " - "by " - "platform, extension or function. The developer should " - "refer to " - "the documentation of the function that returned the error " - "code " - "and extension it was defined."; - case XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR: - return "XR_ERROR_ANDROID_THREAD_SETTINGS_ID_INVALID_KHR: " - "xrSetAndroidApplicationThreadKHR failed as thread id is " - "invalid. " - "(Added by the XR_KHR_android_thread_settings extension)"; - case XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR: - return "XR_ERROR_ANDROID_THREAD_SETTINGS_FAILURE_KHR: " - "xrSetAndroidApplicationThreadKHR failed setting the thread " - "attributes/priority. (Added by the " - "XR_KHR_android_thread_settings extension)"; - case XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT: - return "XR_ERROR_CREATE_SPATIAL_ANCHOR_FAILED_MSFT: Spatial anchor " - "could " - "not be created at that location. (Added by the " - "XR_MSFT_spatial_anchor extension)"; - case XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_MSFT: - return "XR_ERROR_SECONDARY_VIEW_CONFIGURATION_TYPE_NOT_ENABLED_" - "MSFT: The " - "secondary view configuration was not enabled when creating " - "the " - "session. (Added by the " - "XR_MSFT_secondary_view_configuration " - "extension)"; - case XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT: - return "XR_ERROR_CONTROLLER_MODEL_KEY_INVALID_MSFT: The controller " - "model " - "key is invalid. (Added by the XR_MSFT_controller_model " - "extension)"; - case XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT: - return "XR_ERROR_REPROJECTION_MODE_UNSUPPORTED_MSFT: The " - "reprojection " - "mode is not supported. (Added by the " - "XR_MSFT_composition_layer_reprojection extension)"; - case XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT: - return "XR_ERROR_COMPUTE_NEW_SCENE_NOT_COMPLETED_MSFT: Compute new " - "scene " - "not completed. (Added by the XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT: - return "XR_ERROR_SCENE_COMPONENT_ID_INVALID_MSFT: Scene component " - "id " - "invalid. (Added by the XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT: - return "XR_ERROR_SCENE_COMPONENT_TYPE_MISMATCH_MSFT: Scene " - "component " - "type mismatch. (Added by the XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT: - return "XR_ERROR_SCENE_MESH_BUFFER_ID_INVALID_MSFT: Scene mesh " - "buffer id " - "invalid. (Added by the XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT: - return "XR_ERROR_SCENE_COMPUTE_FEATURE_INCOMPATIBLE_MSFT: Scene " - "compute " - "feature incompatible. (Added by the " - "XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT: - return "XR_ERROR_SCENE_COMPUTE_CONSISTENCY_MISMATCH_MSFT: Scene " - "compute " - "consistency mismatch. (Added by the " - "XR_MSFT_scene_understanding " - "extension)"; - case XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB: - return "XR_ERROR_DISPLAY_REFRESH_RATE_UNSUPPORTED_FB: The display " - "refresh rate is not supported by the platform. (Added by " - "the " - "XR_FB_display_refresh_rate extension)"; - case XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB: - return "XR_ERROR_COLOR_SPACE_UNSUPPORTED_FB: The color space is " - "not " - "supported by the runtime. (Added by the XR_FB_color_space " - "extension)"; - case XR_ERROR_SPACE_COMPONENT_NOT_SUPPORTED_FB: - return "XR_ERROR_SPACE_COMPONENT_NOT_SUPPORTED_FB: The component " - "type is " - "not supported for this space. (Added by the " - "XR_FB_spatial_entity " - "extension)"; - case XR_ERROR_SPACE_COMPONENT_NOT_ENABLED_FB: - return "XR_ERROR_SPACE_COMPONENT_NOT_ENABLED_FB: The required " - "component " - "is not enabled for this space. (Added by the " - "XR_FB_spatial_entity extension)"; - case XR_ERROR_SPACE_COMPONENT_STATUS_PENDING_FB: - return "XR_ERROR_SPACE_COMPONENT_STATUS_PENDING_FB: A request to " - "set the " - "component’s status is currently pending. (Added by the " - "XR_FB_spatial_entity extension)"; - case XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB: - return "XR_ERROR_SPACE_COMPONENT_STATUS_ALREADY_SET_FB: The " - "component is " - "already set to the requested value. (Added by the " - "XR_FB_spatial_entity extension)"; - case XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB: - return "XR_ERROR_UNEXPECTED_STATE_PASSTHROUGH_FB: The object state " - "is " - "unexpected for the issued command. (Added by the " - "XR_FB_passthrough extension)"; - case XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB: - return "XR_ERROR_FEATURE_ALREADY_CREATED_PASSTHROUGH_FB: Trying to " - "create an MR feature when one was already created and only " - "one " - "instance is allowed. (Added by the XR_FB_passthrough " - "extension)"; - case XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB: - return "XR_ERROR_FEATURE_REQUIRED_PASSTHROUGH_FB: Requested " - "functionality requires a feature to be created first. " - "(Added by " - "the XR_FB_passthrough extension)"; - case XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB: - return "XR_ERROR_NOT_PERMITTED_PASSTHROUGH_FB: Requested " - "functionality " - "is not permitted - application is not allowed to perform " - "the " - "requested operation. (Added by the XR_FB_passthrough " - "extension)"; - case XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB: - return "XR_ERROR_INSUFFICIENT_RESOURCES_PASSTHROUGH_FB: There were " - "insufficient resources available to perform an operation. " - "(Added " - "by the XR_FB_passthrough extension)"; - case XR_ERROR_UNKNOWN_PASSTHROUGH_FB: - return "XR_ERROR_UNKNOWN_PASSTHROUGH_FB: Unknown Passthrough error " - "(no " - "further details provided). (Added by the XR_FB_passthrough " - "extension)"; - case XR_ERROR_RENDER_MODEL_KEY_INVALID_FB: - return "XR_ERROR_RENDER_MODEL_KEY_INVALID_FB: The model key is " - "invalid. " - "(Added by the XR_FB_render_model extension)"; - case XR_ERROR_MARKER_NOT_TRACKED_VARJO: - return "XR_ERROR_MARKER_NOT_TRACKED_VARJO: Marker tracking is " - "disabled " - "or the specified marker is not currently tracked. (Added " - "by the " - "XR_VARJO_marker_tracking extension)"; - case XR_ERROR_MARKER_ID_INVALID_VARJO: - return "XR_ERROR_MARKER_ID_INVALID_VARJO: The specified marker ID " - "is not " - "valid. (Added by the XR_VARJO_marker_tracking extension)"; - case XR_ERROR_MARKER_DETECTOR_PERMISSION_DENIED_ML: - return "XR_ERROR_MARKER_DETECTOR_PERMISSION_DENIED_ML: The " - "com.magicleap.permission.MARKER_TRACKING permission was " - "denied. " - "(Added by the XR_ML_marker_understanding extension)"; - case XR_ERROR_MARKER_DETECTOR_LOCATE_FAILED_ML: - return "XR_ERROR_MARKER_DETECTOR_LOCATE_FAILED_ML: The specified " - "marker " - "could not be located spatially. (Added by the " - "XR_ML_marker_understanding extension)"; - case XR_ERROR_MARKER_DETECTOR_INVALID_DATA_QUERY_ML: - return "XR_ERROR_MARKER_DETECTOR_INVALID_DATA_QUERY_ML: The marker " - "queried does not contain data of the requested type. " - "(Added by " - "the XR_ML_marker_understanding extension)"; - case XR_ERROR_MARKER_DETECTOR_INVALID_CREATE_INFO_ML: - return "XR_ERROR_MARKER_DETECTOR_INVALID_CREATE_INFO_ML: " - "createInfo " - "contains mutually exclusive parameters, such as setting " - "XR_MARKER_DETECTOR_CORNER_REFINE_METHOD_APRIL_TAG_ML with " - "XR_MARKER_TYPE_ARUCO_ML. (Added by the " - "XR_ML_marker_understanding extension)"; - case XR_ERROR_MARKER_INVALID_ML: - return "XR_ERROR_MARKER_INVALID_ML: The marker id passed to the " - "function " - "was invalid. (Added by the XR_ML_marker_understanding " - "extension)"; - case XR_ERROR_LOCALIZATION_MAP_INCOMPATIBLE_ML: - return "XR_ERROR_LOCALIZATION_MAP_INCOMPATIBLE_ML: The " - "localization map " - "being imported is not compatible with current OS or mode. " - "(Added " - "by the XR_ML_localization_map extension)"; - case XR_ERROR_LOCALIZATION_MAP_UNAVAILABLE_ML: - return "XR_ERROR_LOCALIZATION_MAP_UNAVAILABLE_ML: The localization " - "map " - "requested is not available. (Added by the " - "XR_ML_localization_map " - "extension)"; - case XR_ERROR_LOCALIZATION_MAP_FAIL_ML: - return "XR_ERROR_LOCALIZATION_MAP_FAIL_ML: The map localization " - "service " - "failed to fulfill the request, retry later. (Added by the " - "XR_ML_localization_map extension)"; - case XR_ERROR_LOCALIZATION_MAP_IMPORT_EXPORT_PERMISSION_DENIED_ML: - return "XR_ERROR_LOCALIZATION_MAP_IMPORT_EXPORT_PERMISSION_DENIED_" - "ML: " - "The com.magicleap.permission.SPACE_IMPORT_EXPORT " - "permission was " - "denied. (Added by the XR_ML_localization_map extension)"; - case XR_ERROR_LOCALIZATION_MAP_PERMISSION_DENIED_ML: - return "XR_ERROR_LOCALIZATION_MAP_PERMISSION_DENIED_ML: The " - "com.magicleap.permission.SPACE_MANAGER permission was " - "denied. " - "(Added by the XR_ML_localization_map extension)"; - case XR_ERROR_LOCALIZATION_MAP_ALREADY_EXISTS_ML: - return "XR_ERROR_LOCALIZATION_MAP_ALREADY_EXISTS_ML: The map being " - "imported already exists in the system. (Added by the " - "XR_ML_localization_map extension)"; - case XR_ERROR_LOCALIZATION_MAP_CANNOT_EXPORT_CLOUD_MAP_ML: - return "XR_ERROR_LOCALIZATION_MAP_CANNOT_EXPORT_CLOUD_MAP_ML: The " - "map " - "localization service cannot export cloud based maps. " - "(Added by " - "the XR_ML_localization_map extension)"; - case XR_ERROR_SPATIAL_ANCHORS_PERMISSION_DENIED_ML: - return "XR_ERROR_SPATIAL_ANCHORS_PERMISSION_DENIED_ML: The " - "com.magicleap.permission.SPATIAL_ANCHOR permission was not " - "granted. (Added by the XR_ML_spatial_anchors extension)"; - case XR_ERROR_SPATIAL_ANCHORS_NOT_LOCALIZED_ML: - return "XR_ERROR_SPATIAL_ANCHORS_NOT_LOCALIZED_ML: Operation " - "failed " - "because the system is not localized into a localization " - "map. " - "(Added by the XR_ML_spatial_anchors extension)"; - case XR_ERROR_SPATIAL_ANCHORS_OUT_OF_MAP_BOUNDS_ML: - return "XR_ERROR_SPATIAL_ANCHORS_OUT_OF_MAP_BOUNDS_ML: Operation " - "failed " - "because it is performed outside of the localization map. " - "(Added " - "by the XR_ML_spatial_anchors extension)"; - case XR_ERROR_SPATIAL_ANCHORS_SPACE_NOT_LOCATABLE_ML: - return "XR_ERROR_SPATIAL_ANCHORS_SPACE_NOT_LOCATABLE_ML: Operation " - "failed because the space referenced cannot be located. " - "(Added by " - "the XR_ML_spatial_anchors extension)"; - case XR_ERROR_SPATIAL_ANCHORS_ANCHOR_NOT_FOUND_ML: - return "XR_ERROR_SPATIAL_ANCHORS_ANCHOR_NOT_FOUND_ML: The anchor " - "references was not found. (Added by the " - "XR_ML_spatial_anchors_storage extension)"; - case XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT: - return "XR_ERROR_SPATIAL_ANCHOR_NAME_NOT_FOUND_MSFT: A spatial " - "anchor " - "was not found associated with the spatial anchor name " - "provided " - "(Added by the XR_MSFT_spatial_anchor_persistence " - "extension)"; - case XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT: - return "XR_ERROR_SPATIAL_ANCHOR_NAME_INVALID_MSFT: The spatial " - "anchor " - "name provided was not valid (Added by the " - "XR_MSFT_spatial_anchor_persistence extension)"; - case XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB: - return "XR_ERROR_SPACE_MAPPING_INSUFFICIENT_FB: Anchor import from " - "cloud " - "or export from device failed. (Added by the " - "XR_FB_spatial_entity_sharing extension)"; - case XR_ERROR_SPACE_LOCALIZATION_FAILED_FB: - return "XR_ERROR_SPACE_LOCALIZATION_FAILED_FB: Anchors were " - "downloaded " - "from the cloud but failed to be imported/aligned on the " - "device. " - "(Added by the XR_FB_spatial_entity_sharing extension)"; - case XR_ERROR_SPACE_NETWORK_TIMEOUT_FB: - return "XR_ERROR_SPACE_NETWORK_TIMEOUT_FB: Timeout occurred while " - "waiting for network request to complete. (Added by the " - "XR_FB_spatial_entity_sharing extension)"; - case XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB: - return "XR_ERROR_SPACE_NETWORK_REQUEST_FAILED_FB: The network " - "request " - "failed. (Added by the XR_FB_spatial_entity_sharing " - "extension)"; - case XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB: - return "XR_ERROR_SPACE_CLOUD_STORAGE_DISABLED_FB: Cloud storage is " - "required for this operation but is currently disabled. " - "(Added by " - "the XR_FB_spatial_entity_sharing extension)"; - case XR_ERROR_PASSTHROUGH_COLOR_LUT_BUFFER_SIZE_MISMATCH_META: - return "XR_ERROR_PASSTHROUGH_COLOR_LUT_BUFFER_SIZE_MISMATCH_META: " - "The " - "provided data buffer did not match the required size. " - "(Added by " - "the XR_META_passthrough_color_lut extension)"; - case XR_ERROR_RENDER_MODEL_ID_INVALID_EXT: - return "XR_ERROR_RENDER_MODEL_ID_INVALID_EXT: The render model ID " - "is " - "invalid. (Added by the XR_EXT_render_model extension)"; - case XR_ERROR_RENDER_MODEL_ASSET_UNAVAILABLE_EXT: - return "XR_ERROR_RENDER_MODEL_ASSET_UNAVAILABLE_EXT: The render " - "model " - "asset is unavailable. (Added by the XR_EXT_render_model " - "extension)"; - case XR_ERROR_RENDER_MODEL_GLTF_EXTENSION_REQUIRED_EXT: - return "XR_ERROR_RENDER_MODEL_GLTF_EXTENSION_REQUIRED_EXT: A glTF " - "extension is required. (Added by the XR_EXT_render_model " - "extension)"; - case XR_ERROR_NOT_INTERACTION_RENDER_MODEL_EXT: - return "XR_ERROR_NOT_INTERACTION_RENDER_MODEL_EXT: The provided " - "XrRenderModelEXT was not created from a XrRenderModelIdEXT " - "from " - "[XR_EXT_interaction_render_model] (Added by the " - "XR_EXT_interaction_render_model extension)"; - case XR_ERROR_HINT_ALREADY_SET_QCOM: - return "XR_ERROR_HINT_ALREADY_SET_QCOM: Tracking optimization hint " - "is " - "already set for the domain. (Added by the " - "XR_QCOM_tracking_optimization_settings extension)"; - case XR_ERROR_NOT_AN_ANCHOR_HTC: - return "XR_ERROR_NOT_AN_ANCHOR_HTC: The provided space is valid " - "but not " - "an anchor. (Added by the XR_HTC_anchor extension)"; - case XR_ERROR_SPATIAL_ENTITY_ID_INVALID_BD: - return "XR_ERROR_SPATIAL_ENTITY_ID_INVALID_BD: The spatial entity " - "id is " - "invalid. (Added by the XR_BD_spatial_sensing extension)"; - case XR_ERROR_SPATIAL_SENSING_SERVICE_UNAVAILABLE_BD: - return "XR_ERROR_SPATIAL_SENSING_SERVICE_UNAVAILABLE_BD: The " - "spatial " - "sensing service is unavailable. (Added by the " - "XR_BD_spatial_sensing extension)"; - case XR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_ENTITY_BD: - return "XR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_ENTITY_BD: The spatial " - "entity " - "does not support anchor. (Added by the " - "XR_BD_spatial_sensing " - "extension)"; - case XR_ERROR_SCENE_CAPTURE_FAILURE_BD: - return "XR_ERROR_SCENE_CAPTURE_FAILURE_BD: The scene capture is " - "failed, " - "for example exiting abnormally. (Added by the " - "XR_BD_spatial_scene extension)"; - case XR_ERROR_SPACE_NOT_LOCATABLE_EXT: - return "XR_ERROR_SPACE_NOT_LOCATABLE_EXT: The space passed to the " - "function was not locatable. (Added by the " - "XR_EXT_plane_detection " - "extension)"; - case XR_ERROR_PLANE_DETECTION_PERMISSION_DENIED_EXT: - return "XR_ERROR_PLANE_DETECTION_PERMISSION_DENIED_EXT: The " - "permission " - "for this resource was not granted. (Added by the " - "XR_EXT_plane_detection extension)"; - case XR_ERROR_FUTURE_PENDING_EXT: - return "XR_ERROR_FUTURE_PENDING_EXT: Returned by completion " - "function to " - "indicate future is not ready. (Added by the XR_EXT_future " - "extension)"; - case XR_ERROR_FUTURE_INVALID_EXT: - return "XR_ERROR_FUTURE_INVALID_EXT: Returned by completion " - "function to " - "indicate future is not valid. (Added by the XR_EXT_future " - "extension)"; - case XR_ERROR_SYSTEM_NOTIFICATION_PERMISSION_DENIED_ML: - return "XR_ERROR_SYSTEM_NOTIFICATION_PERMISSION_DENIED_ML: The " - "com.magicleap.permission.SYSTEM_NOTIFICATION permission " - "was not " - "granted. (Added by the XR_ML_system_notifications " - "extension)"; - case XR_ERROR_SYSTEM_NOTIFICATION_INCOMPATIBLE_SKU_ML: - return "XR_ERROR_SYSTEM_NOTIFICATION_INCOMPATIBLE_SKU_ML: " - "Incompatible " - "SKU detected. (Added by the XR_ML_system_notifications " - "extension)"; - case XR_ERROR_WORLD_MESH_DETECTOR_PERMISSION_DENIED_ML: - return "XR_ERROR_WORLD_MESH_DETECTOR_PERMISSION_DENIED_ML: The " - "world " - "mesh detector permission was not granted. (Added by the " - "XR_ML_world_mesh_detection extension)"; - case XR_ERROR_WORLD_MESH_DETECTOR_SPACE_NOT_LOCATABLE_ML: - return "XR_ERROR_WORLD_MESH_DETECTOR_SPACE_NOT_LOCATABLE_ML: At " - "the time " - "of the call the runtime was unable to locate the space and " - "cannot fulfill your request. (Added by the " - "XR_ML_world_mesh_detection extension)"; - case XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META: - return "XR_ERROR_COLOCATION_DISCOVERY_NETWORK_FAILED_META: The " - "network " - "request failed. (Added by the XR_META_colocation_discovery " - "extension)"; - case XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META: - return "XR_ERROR_COLOCATION_DISCOVERY_NO_DISCOVERY_METHOD_META: " - "The " - "runtime does not have any methods available to perform " - "discovery. (Added by the XR_META_colocation_discovery " - "extension)"; - case XR_ERROR_SPACE_GROUP_NOT_FOUND_META: - return "XR_ERROR_SPACE_GROUP_NOT_FOUND_META: The group UUID was " - "not " - "found within the runtime (Added by the " - "XR_META_spatial_entity_group_sharing extension)"; - case XR_ERROR_SPATIAL_CAPABILITY_UNSUPPORTED_EXT: - return "XR_ERROR_SPATIAL_CAPABILITY_UNSUPPORTED_EXT: The specified " - "spatial capability is not supported by the runtime or the " - "system. (Added by the XR_EXT_spatial_entity extension)"; - case XR_ERROR_SPATIAL_ENTITY_ID_INVALID_EXT: - return "XR_ERROR_SPATIAL_ENTITY_ID_INVALID_EXT: The specified " - "spatial " - "entity id is invalid or an entity with that id does not " - "exist in " - "the environment. (Added by the XR_EXT_spatial_entity " - "extension)"; - case XR_ERROR_SPATIAL_BUFFER_ID_INVALID_EXT: - return "XR_ERROR_SPATIAL_BUFFER_ID_INVALID_EXT: The specified " - "spatial " - "buffer id is invalid or does not exist in the spatial " - "snapshot " - "being used to query for the buffer data. (Added by the " - "XR_EXT_spatial_entity extension)"; - case XR_ERROR_SPATIAL_COMPONENT_UNSUPPORTED_FOR_CAPABILITY_EXT: - return "XR_ERROR_SPATIAL_COMPONENT_UNSUPPORTED_FOR_CAPABILITY_EXT: " - "The " - "specified spatial component is not supported by the " - "runtime or " - "the system for the given capability. (Added by the " - "XR_EXT_spatial_entity extension)"; - case XR_ERROR_SPATIAL_CAPABILITY_CONFIGURATION_INVALID_EXT: - return "XR_ERROR_SPATIAL_CAPABILITY_CONFIGURATION_INVALID_EXT: The " - "specified spatial capability configuration is invalid. " - "(Added by " - "the XR_EXT_spatial_entity extension)"; - case XR_ERROR_SPATIAL_COMPONENT_NOT_ENABLED_EXT: - return "XR_ERROR_SPATIAL_COMPONENT_NOT_ENABLED_EXT: The specified " - "spatial component is not enabled for the spatial context. " - "(Added " - "by the XR_EXT_spatial_entity extension)"; - case XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_UNSUPPORTED_EXT: - return "XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_UNSUPPORTED_EXT: The " - "specified spatial persistence scope is not supported by " - "the " - "runtime or the system. (Added by the " - "XR_EXT_spatial_persistence " - "extension)"; - case XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_INCOMPATIBLE_EXT: - return "XR_ERROR_SPATIAL_PERSISTENCE_SCOPE_INCOMPATIBLE_EXT: THe " - "scope " - "configured for the persistence context is incompatible for " - "the " - "current spatial entity. (Added by the " - "XR_EXT_spatial_persistence_operations extension)"; - default: - return ""; - } - } - - template auto format(XrResult r, FmtCtx &ctx) const - { - if (spec == 'd') { - return format_to(ctx.out(), "{}", static_cast(r)); - } - - std::string_view str = std::formatter::to_string(r); - if (!str.empty()) { - return format_to(ctx.out(), "{}", str); - } - - return format_to(ctx.out(), "<0x{:x}>", static_cast(r)); - } - -private: - char spec { 's' }; -}; - -} // namespace std diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..0e00b91 --- /dev/null +++ b/src/common.h @@ -0,0 +1,6 @@ +#ifndef COMMON_H +#define COMMON_H + +#define ARRAY_SZ(arr) (sizeof(arr) / sizeof(*arr)) + +#endif // COMMON_H diff --git a/src/dhos_config.cpp b/src/dhos_config.cpp deleted file mode 100644 index 344576d..0000000 --- a/src/dhos_config.cpp +++ /dev/null @@ -1,705 +0,0 @@ -#include "dhos_config.h" - -// NOLINTBEGIN - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dhos { - -// environment shared across parser and evaluation -struct Environment { - Environment(Environment *p = nullptr) - : parent { p } - { - } - std::unordered_map tbl; - Environment *parent; - bool contains(std::string const &k) const - { - return tbl.contains(k) || (parent && parent->contains(k)); - } - Value &operator[](std::string const &k) - { - if (tbl.contains(k)) - return tbl.at(k); - if (parent) - return (*parent)[k]; - throw std::runtime_error(std::format("Undefined variable: {}", k)); - } -}; - -namespace { -// env, lexer, parser - -enum class Tok { - End, - Id, - Num, - Str, - Path, - LPar, - RPar, - LBr, - RBr, - LBrk, - RBrk, - Eq, - Dot, - Comma -}; - -std::string Tok_str(Tok t) -{ - if (t == Tok::End) - return "End"; - else if (t == Tok::Id) - return "Id"; - else if (t == Tok::Num) - return "Num"; - else if (t == Tok::Str) - return "Str"; - else if (t == Tok::Path) - return "Path"; - else if (t == Tok::LPar) - return "LPar"; - else if (t == Tok::RPar) - return "RPar"; - else if (t == Tok::LBr) - return "LBr"; - else if (t == Tok::RBr) - return "RBr"; - else if (t == Tok::LBrk) - return "LBrk"; - else if (t == Tok::RBrk) - return "RBrk"; - else if (t == Tok::Eq) - return "Eq"; - else if (t == Tok::Dot) - return "Dot"; - else if (t == Tok::Comma) - return "Comma"; - else - return "?"; -} - -struct T { - Tok k; - std::string t; -}; - -class Lex { -public: - explicit Lex(std::string_view s) - : src { s } - { - } - T next(); - -private: - T id(); - T num(); - T str(); - T path(); - void skip(); - std::string_view src; - std::size_t pos {}; -}; - -T Lex::id() -{ - std::size_t s = pos; - while (pos < src.size() - && (std::isalnum((unsigned char)src[pos]) || src[pos] == '_')) - ++pos; - return { Tok::Id, std::string { src.substr(s, pos - s) } }; -} -T Lex::num() -{ - std::size_t s = pos; - while (pos < src.size() - && (std::isdigit((unsigned char)src[pos]) || src[pos] == '_')) - ++pos; - if (pos < src.size() && src[pos] == '.') { - ++pos; - while (pos < src.size() - && (std::isdigit((unsigned char)src[pos]) || src[pos] == '_')) - ++pos; - } - return { Tok::Num, std::string { src.substr(s, pos - s) } }; -} -T Lex::str() -{ - char q = src[pos++]; - std::string o; - while (pos < src.size() && src[pos] != q) - o.push_back(src[pos++]); - if (pos >= src.size()) - throw std::runtime_error("Unterminated string literal"); - ++pos; - return { Tok::Str, o }; -} - -static bool is_path_ch(char c) -{ - return std::isalnum((unsigned char)c) || c == '_' || c == '.' || c == '/' - || c == '-' || c == ':'; -} - -T Lex::path() -{ - std::string out; - while (pos < src.size()) { - char c = src[pos]; - if (c == '\\') { - ++pos; - if (pos < src.size()) { - out.push_back(src[pos]); - ++pos; - } else { - out.push_back('\\'); - } - } else if (is_path_ch(c)) { - out.push_back(c); - ++pos; - } else { - break; - } - } - return { Tok::Path, out }; -} - -void Lex::skip() -{ - while (pos < src.size()) { - if (std::isspace((unsigned char)src[pos])) { - ++pos; - continue; - } - if (src[pos] == '#') { - while (pos < src.size() && src[pos] != '\n') - ++pos; - continue; - } - break; - } -} -T Lex::next() -{ - skip(); - if (pos >= src.size()) - return { Tok::End, "" }; - char c = src[pos]; - - if (c == '/' // "/foo" - || (c == '.' && pos + 1 < src.size() && src[pos + 1] == '/') // "./foo" - || (c == '.' && pos + 2 < src.size() && src[pos + 1] == '.' - && src[pos + 2] == '/') // "../foo" - || (std::isalpha((unsigned char)c) && pos + 1 < src.size() - && src[pos + 1] == ':')) // "C:/foo" - return path(); - - switch (c) { - case '(': - ++pos; - return { Tok::LPar, "(" }; - case ')': - ++pos; - return { Tok::RPar, ")" }; - case '{': - ++pos; - return { Tok::LBr, "{" }; - case '}': - ++pos; - return { Tok::RBr, "}" }; - case '[': - ++pos; - return { Tok::LBrk, "[" }; - case ']': - ++pos; - return { Tok::RBrk, "]" }; - case '=': - ++pos; - return { Tok::Eq, "=" }; - case '.': - ++pos; - return { Tok::Dot, "." }; - case ',': - ++pos; - return { Tok::Comma, "," }; - case '"': - case '\'': - return str(); - case 0: - return { Tok::End, "" }; - default: - if (std::isdigit((unsigned char)c)) - return num(); - if (std::isalpha((unsigned char)c) || c == '_') - return id(); - std::println("Invalid character: {} ({:d})", c, c); - throw std::runtime_error(std::format("Invalid character: {}", c)); - } -} - -// parser -class Parser { -public: - Parser(std::string_view s, std::shared_ptr e) - : lx { s } - , env { std::move(e) } - { - adv(); - } - Value parse() { return val(); } - -private: - Lex lx; - T cur; - std::shared_ptr env; - void adv() { cur = lx.next(); } - bool acc(Tok k) - { - if (cur.k == k) { - adv(); - return true; - } - return false; - } - void exp(Tok k) - { - if (!acc(k)) { - std::println("Expected: {}, Got: {}", Tok_str(k), Tok_str(cur.k)); - throw std::runtime_error( - std::format("Syntax error: expected {}, got {}", Tok_str(k), - Tok_str(cur.k))); - } - } - bool begins(Tok k) - { - return k == Tok::Path || k == Tok::Id || k == Tok::Num || k == Tok::Str - || k == Tok::LBr || k == Tok::LBrk || k == Tok::LPar; - } - std::string id() - { - std::string s = cur.t; - exp(Tok::Id); - return s; - } - - Value array() - { - exp(Tok::LBrk); - Array a; - while (cur.k != Tok::RBrk) { - a.push_back(val()); - acc(Tok::Comma); - } - exp(Tok::RBrk); - return Value { std::move(a), env }; - } - Value block() - { - exp(Tok::LBr); - Object o; - while (cur.k != Tok::RBr) { - if (acc(Tok::LPar)) { - Value inner = val(); - exp(Tok::RPar); - o.insert(inner.obj().begin(), inner.obj().end()); - continue; - } - std::string k = id(); - exp(Tok::Eq); - o[k] = val(); - } - exp(Tok::RBr); - return Value { std::move(o), env }; - } - Value func() - { - exp(Tok::Id); - std::vector params; - while (cur.k == Tok::Id) - params.push_back(id()); - exp(Tok::Eq); - Value body = val(); - auto f = std::make_shared(); - f->params = params; - f->body = std::make_shared(body); - return Value { std::move(f), env }; - } - Value call() - { - std::string name = id(); - std::vector args; - while (begins(cur.k)) - args.emplace_back(val()); - auto c = std::make_shared(); - c->name = name; - c->args = std::move(args); - return Value { std::move(c), env }; - } - Value val() - { - switch (cur.k) { - case Tok::Id: - if (cur.t == "fn") { - return func(); - } - if (cur.t == "nil") { - adv(); - return Value { env }; - } - { - std::string idn = cur.t; - adv(); - - // make the base AST node - auto node = std::make_shared(); - node->recv = nullptr; // top‐level call - node->name = std::move(idn); - - // consume any “.field” chains - while (acc(Tok::Dot)) { - auto parent = node; - std::string fld = id(); - auto mnode = std::make_shared(); - mnode->recv = std::make_shared( - parent, env); // wrap the old AST - mnode->name = std::move(fld); - node = mnode; - } - - // then any args - while (begins(cur.k)) { - node->args.push_back(val()); - } - - return Value { node, env }; - } - case Tok::Num: { - std::string s = cur.t; - s.erase(std::remove(s.begin(), s.end(), '_'), s.end()); - double n; - if (static_cast( - std::from_chars(s.data(), s.data() + s.size(), n).ec)) - throw; - adv(); - return Value { n, env }; - } - case Tok::Str: { - Value v { cur.t, env }; - adv(); - return v; - } - case Tok::Path: { - Value v { std::filesystem::path { cur.t }, env }; - adv(); - return v; - } - case Tok::LBrk: - return array(); - case Tok::LBr: - return block(); - case Tok::LPar: - adv(); - { - Value v = val(); - exp(Tok::RPar); - return v; - } - default: - throw std::runtime_error( - std::format("Unexpected token: {}", Tok_str(cur.k))); - } - } -}; - -// built-ins -Value join(Array const &a) -{ - Array r; - for (auto &v : a) { - if (!v.is_arr()) - throw; - r.insert(r.end(), v.arr().begin(), v.arr().end()); - } - return r; -} -Value list_rng(Array const &a) -{ - if (a.size() != 3 || !a[0].is_num() || !a[1].is_num() || !a[2].is_num()) - throw; - double s = a[0].num(), e = a[1].num(), st = a[2].num(); - if (!st) - throw; - Array r; - if (st > 0) - for (double x = s; x <= e; x += st) - r.emplace_back(x); - else - for (double x = s; x >= e; x += st) - r.emplace_back(x); - return r; -} -Value imp(Array const &a) -{ - if (a.empty()) - throw; - std::ifstream f; - if (a[0].is_str()) { - f = std::ifstream { a[0].str() }; - } else if (a[0].is_path()) { - f = std::ifstream { a[0].path() }; - } else { - throw; - } - if (!f) - throw; - std::stringstream b; - b << f.rdbuf(); - std::istringstream in { b.str() }; - return parse_config(in); -} - -// serializer -void w(std::ostream &o, Value const &v) -{ - if (v.is_nil()) { - o << "nil"; - return; - } - if (v.is_num()) { - o << v.num(); - return; - } - if (v.is_str()) { - o << '"' << v.str() << '"'; - return; - } - if (v.is_path()) { - o << v.path().generic_string(); - return; - } - if (v.is_arr()) { - o << "["; - bool f = true; - for (auto &x : v.arr()) { - if (!f) - o << ","; - w(o, x); - f = false; - } - o << "]"; - return; - } - if (v.is_obj()) { - o << "{"; - bool f = true; - for (auto &[k, x] : v.obj()) { - if (!f) - o << ","; - o << k << "="; - w(o, x); - f = false; - } - o << "}"; - return; - } - o << "fn"; // prints literal token for functions -} - -} // namespace - -// public helpers -Value eval(Value const &v, Object const *extra_lib) -{ - static Object base = make_default_lib(); - Object merged = merge_lib(base, extra_lib); - - if (v.is_call()) { - auto const &c = v.call(); - Value fn; - - // receiver or global - if (c.recv && !c.recv->is_nil()) { - Value recv = eval(*c.recv, extra_lib); - fn = recv[c.name]; - } else { - if (auto it = merged.find(c.name); it != merged.end()) - fn = it->second; - else - fn = (*v.env)[c.name]; - } - - // args - Array argv; - argv.reserve(c.args.size()); - for (auto &a : c.args) - argv.push_back(eval(a, extra_lib)); - - // call - if (auto b = std::get_if(&fn.data)) - return (*b)(argv); - - if (auto fp = std::get_if>(&fn.data)) { - auto child = std::make_shared(fn.env.get()); - for (size_t i = 0; i < (*fp)->params.size(); ++i) - child->tbl[(*fp)->params[i]] - = i < argv.size() ? argv[i] : Value(child); - - Value body = *(*fp)->body; - body.env = child; - return eval(body, extra_lib); - } - - return fn; - } - - // not a call - if (!v.is_fn()) - return v; - - if (auto fp = std::get_if>(&v.data)) - return *(*fp)->body; - - return v; // builtin with no args -} - -// auto-evaluating operator[] impl -static void materialise(Value &v) -{ - auto r = eval(v, nullptr); - if (!r.is_fn() && !r.is_call()) - v = std::move(r); -} - -Value &Value::operator[](std::string const &k) -{ - materialise(*this); - if (is_nil()) - return *this; - if (!is_obj()) - throw std::logic_error("[] on non-object"); - - Value &child = obj()[k]; - materialise(child); - return child; -} - -Value &Value::operator[](std::size_t i) -{ - materialise(*this); - if (is_nil()) - return *this; - if (!is_arr()) - throw std::logic_error("[] on non-array"); - if (i >= arr().size()) - throw std::out_of_range("index"); - - Value &child = arr()[i]; - materialise(child); - return child; -} - -Value const &Value::operator[](std::string const &k) const -{ - return const_cast(this)->operator[](k); -} -Value const &Value::operator[](std::size_t i) const -{ - return const_cast(this)->operator[](i); -} - -// parse/write -Value parse_config(std::istream &in) -{ - std::stringstream b; - b << in.rdbuf(); - std::string src = b.str(); - auto env = std::make_shared(); - env->tbl = make_default_lib(); - Parser p { src, env }; - return p.parse(); -} -inline void resolve_paths(Value &v, std::filesystem::path const &base) -{ - if (v.is_path()) { - auto &p = v.path(); - if (p.is_relative()) - p = base / p; - return; - } - if (v.is_arr()) { - for (auto &x : v.arr()) - resolve_paths(x, base); - return; - } - if (v.is_obj()) { - for (auto &[_, x] : v.obj()) - resolve_paths(x, base); - return; - } - if (v.is_call()) { - for (auto &x : v.call().args) - resolve_paths(x, base); - } - if (std::holds_alternative>(v.data)) { - resolve_paths( - *std::get>(v.data)->body, base); - } -} - -Value parse_config(std::filesystem::path const &f) -{ - std::ifstream in { f }; - if (!in) - throw std::runtime_error( - std::format("Failed to open configuration file: {}", f.string())); - Value v = parse_config(in); - resolve_paths(v, f.parent_path()); - return v; -} -void write_config(Value const &v, std::filesystem::path const &o) -{ - std::ofstream f { o }; - if (!f) - throw std::runtime_error( - std::format("Failed to write configuration file: {}", o.string())); - w(f, v); -} - -// default lib -Object make_default_lib(); -Object merge_lib(Object const &base, Object const *extra) -{ - Object out = base; - if (extra) - out.insert(extra->begin(), extra->end()); - return out; -} - -auto get_env_tbl(Environment *env) -> std::unordered_map & -{ - return env->tbl; -} - -Object make_default_lib() -{ - Object l; - l["join"] = Builtin { join }; - l["list_from_range"] = Builtin { list_rng }; - l["import"] = Builtin { imp }; - return l; -} - -} // namespace dhos - -// NOLINTEND diff --git a/src/dhos_config.h b/src/dhos_config.h deleted file mode 100644 index 141a6c4..0000000 --- a/src/dhos_config.h +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once - -// NOLINTBEGIN - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dhos { - -struct Environment; -struct Value; - -using Array = std::vector; -using Object = std::unordered_map; -using Builtin = std::function; - -struct Value { - using EnvPtr = std::shared_ptr; - - struct Function { - std::vector params; - std::shared_ptr body; - }; - struct Call { - std::string name; - Array args; - std::shared_ptr recv; - }; - using Data = std::variant, - Builtin, std::shared_ptr>; - Data data {}; - EnvPtr env {}; - - Value() = default; - explicit Value(EnvPtr e) - : env { std::move(e) } - { - } - Value(double n, EnvPtr e = {}) - : data { n } - , env { std::move(e) } - { - } - Value(std::string const &s, EnvPtr e = {}) - : data { s } - , env { std::move(e) } - { - } - Value(char const *s, EnvPtr e = {}) - : data { std::string { s } } - , env { std::move(e) } - { - } - Value(std::filesystem::path const &p, EnvPtr e = {}) - : data { p } - , env { std::move(e) } - { - } - Value(Array a, EnvPtr e = {}) - : data { std::move(a) } - , env { std::move(e) } - { - } - Value(Object o, EnvPtr e = {}) - : data { std::move(o) } - , env { std::move(e) } - { - } - Value(std::shared_ptr f, EnvPtr e = {}) - : data { std::move(f) } - , env { std::move(e) } - { - } - Value(Builtin b, EnvPtr e = {}) - : data { std::move(b) } - , env { std::move(e) } - { - } - Value(std::shared_ptr c, EnvPtr e = {}) - : data { std::move(c) } - , env { std::move(e) } - { - } - - bool is_nil() const { return std::holds_alternative(data); } - bool is_num() const { return std::holds_alternative(data); } - bool is_str() const { return std::holds_alternative(data); } - bool is_path() const - { - return std::holds_alternative(data); - } - bool is_arr() const { return std::holds_alternative(data); } - bool is_obj() const { return std::holds_alternative(data); } - bool is_fn() const - { - return std::holds_alternative>(data) - || std::holds_alternative(data); - } - bool is_call() const - { - return std::holds_alternative>(data); - } - - double num() const { return std::get(data); } - std::string const &str() const { return std::get(data); } - Array &arr() { return std::get(data); } - Array const &arr() const { return std::get(data); } - Object &obj() { return std::get(data); } - Object const &obj() const { return std::get(data); } - std::filesystem::path &path() - { - return std::get(data); - } - std::filesystem::path const &path() const - { - return std::get(data); - } - Call &call() { return *std::get>(data); } - Call const &call() const { return *std::get>(data); } - - // auto-evaluating nlohmann-style access - Value &operator[](std::string const &k); - Value &operator[](std::size_t i); - Value const &operator[](std::string const &k) const; - Value const &operator[](std::size_t i) const; -}; - -// pure evaluation helper (never mutates its argument) -Value eval(Value const &v, Object const *extra_lib = nullptr); - -Value parse_config(std::istream &in); -Value parse_config(std::filesystem::path const &f); -void write_config(Value const &v, std::filesystem::path const &out); - -Object make_default_lib(); -Object merge_lib(Object const &base, Object const *extra); - -auto get_env_tbl(Environment *env) -> std::unordered_map &; - -} // namespace dhos - -// NOLINTEND diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fe91853 --- /dev/null +++ b/src/main.c @@ -0,0 +1,16 @@ +#include +#include + +#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); +} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 3b0afeb..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// NOLINTBEGIN -#include -#include - -#include -#include -// NOLINTEND - -import std; - -import LunarWM.LunarWM; - -static std::unique_ptr g_comp; - -auto main() noexcept -> int // NOLINT(bugprone-exception-escape) -{ - CPPTRACE_TRY - { - g_comp = std::make_unique(); - g_comp->init(); - assert( - std::signal(SIGINT, [](int) { g_comp->terminate(); }) != SIG_ERR); - g_comp->run(); - } - CPPTRACE_CATCH(std::exception const &e) - { - std::println(std::cerr, "Uncaught exception: {}", e.what()); - cpptrace::from_current_exception().print(); - } -} diff --git a/src/vec.c b/src/vec.c new file mode 100644 index 0000000..4848040 --- /dev/null +++ b/src/vec.c @@ -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 + +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 ©_h->data; +} \ No newline at end of file diff --git a/src/vec.h b/src/vec.h new file mode 100644 index 0000000..8011a05 --- /dev/null +++ b/src/vec.h @@ -0,0 +1,127 @@ +/* +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 +#define typeof(T) std::remove_reference::type>::type +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +// 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 */