diff --git a/CMakeLists.txt b/CMakeLists.txt index 5108037..27f91b5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,11 +59,13 @@ 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 ) target_link_libraries(${PROJECT_NAME} PUBLIC @@ -80,7 +82,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC ) 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" + 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" ) # Wayland protocol codegen diff --git a/flake.lock b/flake.lock index 79b8d92..10df781 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751011381, - "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", + "lastModified": 1752950548, + "narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", + "rev": "c87b95e25065c028d31a94f06a62927d18763fdf", "type": "github" }, "original": { diff --git a/src/Config.cppm b/src/Config.cppm new file mode 100644 index 0000000..4623de3 --- /dev/null +++ b/src/Config.cppm @@ -0,0 +1,109 @@ +module; + +#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")) { + 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")) { + std::filesystem::path const home_path(home); + paths.push_back(home_path / ".config/lunarwm/lunarwm.dcfg"); + } + + return paths; +} + +export namespace LunarWM::Config { + +struct Configuration { + struct Keybind { + uint32_t modifiers; + xkb_keysym_t sym; + dhos::Value action; + }; + + struct { + struct { + std::vector xkb_options; + } keyboard {}; + } input {}; + + 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 &name, dhos::Builtin fn) { + this->custom_lib[name] = dhos::Value{dhos::Builtin{fn}}; + } + + void load() { this->load(default_configuration_paths()); } + void load(std::vector const &paths); +}; + +void Configuration::load(std::vector const &paths) +{ + for (auto const &path : paths) { + try { + auto e = dhos::eval(dhos::parse_config(path), this->custom_lib); + if (!e.is_obj()) { + throw std::runtime_error("Top level is not an object!"); + } + + 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 + } + } + } + } + } + } catch (std::filesystem::filesystem_error const &e) { + (void)e; + } catch (std::exception &e) { + (void)e; + } + } + throw std::runtime_error("Unable to find a valid configuration file!"); +} + +} // LunarWM::Config diff --git a/src/LunarWM.cppm b/src/LunarWM.cppm index ec654bd..5081f2d 100644 --- a/src/LunarWM.cppm +++ b/src/LunarWM.cppm @@ -19,8 +19,8 @@ module; #include extern "C" { -#include #include +#include #include #include #include @@ -44,6 +44,7 @@ import std; import LunarWM.Math; import LunarWM.Util; +import LunarWM.Config; using Clock = std::chrono::high_resolution_clock; @@ -305,11 +306,14 @@ private: // Extensions struct Hand { - std::array joint_locations {}; + std::array + joint_locations {}; XrHandTrackerEXT hand_tracker {}; }; std::array hands; - XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties {XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT}; + XrSystemHandTrackingPropertiesEXT hand_tracking_system_properties { + XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT + }; // Extension functions PFN_xrCreateHandTrackerEXT CreateHandTrackerEXT {}; @@ -433,6 +437,8 @@ private: std::chrono::time_point m_last_tick; + Config::Configuration config {}; + bool m_running {}; bool m_session_running {}; bool m_session_state {}; @@ -440,9 +446,10 @@ private: void LunarWM::init() { - //if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) { // NOLINT - // throw std::runtime_error("This compositor can only be ran in DRM mode"); - //} + // if (getenv("DISPLAY") != nullptr || getenv("WAYLAND_DISPLAY") != nullptr) + // { // NOLINT throw std::runtime_error("This compositor can only be ran in + // DRM mode"); + // } this->init_wayland(); @@ -556,9 +563,11 @@ void LunarWM::init_wayland() keyboard->server = wm; keyboard->wlr_keyboard = wlr_keyboard; + struct xkb_rule_names const rule_names {}; + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); struct xkb_keymap *keymap = xkb_keymap_new_from_names( - context, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS); + context, &rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS); wlr_keyboard_set_keymap(wlr_keyboard, keymap); xkb_keymap_unref(keymap); @@ -584,15 +593,19 @@ void LunarWM::init_wayland() struct wlr_seat *seat = server->m_wayland.seat; uint32_t const keycode = event->keycode + 8; - xkb_keysym_t const keysym = xkb_state_key_get_one_sym(kbd->wlr_keyboard->xkb_state, keycode); xkb_keysym_t const *syms = nullptr; - // int const nsyms = xkb_state_key_get_syms( - // kbd->wlr_keyboard->xkb_state, keycode, &syms); + 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) { + 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; @@ -603,7 +616,6 @@ void LunarWM::init_wayland() if (syms[XKB_KEY_Escape]) { kbd->server->terminate(); } - // NOLINTEND } @@ -784,17 +796,24 @@ void LunarWM::init_xr() } m_xr.instance = instance; - res = xrGetInstanceProcAddr(*m_xr.instance, "xrCreateHandTrackerEXT", reinterpret_cast(&m_xr.CreateHandTrackerEXT)); + 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"); + throw std::runtime_error( + "Failed to get proc addr xrCreateHandTrackerEXT"); } - res = xrGetInstanceProcAddr(*m_xr.instance, "xrDestroyHandTrackerEXT", reinterpret_cast(&m_xr.DestroyHandTrackerEXT)); + 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"); + throw std::runtime_error( + "Failed to get proc addr xrDestroyHandTrackerEXT"); } - res = xrGetInstanceProcAddr(*m_xr.instance, "xrLocateHandJointsEXT", reinterpret_cast(&m_xr.LocateHandJointsEXT)); + 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"); + throw std::runtime_error( + "Failed to get proc addr xrLocateHandJointsEXT"); } } @@ -827,9 +846,11 @@ void LunarWM::init_xr() .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); + res = xrGetSystemProperties( + *m_xr.instance, *m_xr.system_id, &system_props); if (res != XR_SUCCESS) { - throw std::runtime_error(std::format("xrGetSystemProperties failed: {}", res)); + throw std::runtime_error( + std::format("xrGetSystemProperties failed: {}", res)); } } @@ -1214,9 +1235,11 @@ void LunarWM::init_xr() .handJointSet = XR_HAND_JOINT_SET_DEFAULT_EXT, }; - res = m_xr.CreateHandTrackerEXT(m_xr.session, &ci, &hand.hand_tracker); + 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)); + throw std::runtime_error( + std::format("Failed to create hand tracker: {}", res)); } } } @@ -1323,16 +1346,16 @@ void LunarWM::poll_events_xr() 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, + /* 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, @@ -1351,8 +1374,8 @@ inline constexpr std::array kHandJointMap = { }; [[nodiscard]] -constexpr auto -openxr_joint_to_model(std::uint32_t xrIndex) noexcept -> std::optional +constexpr auto static openxr_joint_to_model(std::uint32_t xrIndex) noexcept + -> std::optional { if (xrIndex < kHandJointMap.size()) { return kHandJointMap.at(xrIndex); @@ -1450,21 +1473,24 @@ auto LunarWM::render_layer(RenderLayerInfo &info, float dt) -> bool { static ModelAnimation anim = {}; - if(anim.framePoses == nullptr) { - anim.boneCount = m_renderer.hands.at(0).boneCount; + 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.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)); + static_cast( + MemAlloc(sizeof(Transform) * anim.boneCount)); } - for(int h = 0; h < 2; ++h) { - auto &handInfo = m_xr.hands.at(h); + 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) { - const auto &pose = m_xr.hands.at(h).joint_locations[k].pose; // NOLINT - const auto &jl = handInfo.joint_locations[k]; // NOLINT + 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, @@ -1663,16 +1689,21 @@ void LunarWM::run() 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 }; + 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 }; + XrHandJointsMotionRangeInfoEXT mri { .type + = XR_TYPE_HAND_JOINTS_MOTION_RANGE_INFO_EXT }; if (unobstructed) { - mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; + mri.handJointsMotionRange + = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT; } else { - mri.handJointsMotionRange = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; + mri.handJointsMotionRange + = XR_HAND_JOINTS_MOTION_RANGE_CONFORMING_TO_CONTROLLER_EXT; } XrHandJointsLocateInfoEXT const li { @@ -1688,7 +1719,8 @@ void LunarWM::run() .jointLocations = hand.joint_locations.data(), }; - if (m_xr.LocateHandJointsEXT(hand.hand_tracker, &li, &hji) != XR_SUCCESS) { + if (m_xr.LocateHandJointsEXT(hand.hand_tracker, &li, &hji) + != XR_SUCCESS) { throw std::runtime_error("Failed to locate hand joints"); } } @@ -1763,18 +1795,16 @@ void LunarWM::render_3d(float /*dt*/) { DrawGrid(10, 1); - for(auto const &tl : m_wayland.toplevels) { + for (auto const &tl : m_wayland.toplevels) { tl->update(); - DrawBillboardNoShear(m_renderer.camera, - tl->rl_texture, - { 0, 1, -1.4f }, - 1.0f); + 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) { - const auto &jl = handInfo.joint_locations[k]; // NOLINT + 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, @@ -1783,7 +1813,7 @@ void LunarWM::render_3d(float /*dt*/) DrawSphere(pos, jl.radius, RED); } - DrawModel(m_renderer.hands.at(h), { 0, 0, 0 }, 1.0f, WHITE); + // DrawModel(m_renderer.hands.at(h), { 0, 0, 0 }, 1.0f, WHITE); } } diff --git a/src/Math.cppm b/src/Math.cppm index 2a464ea..a5c1815 100644 --- a/src/Math.cppm +++ b/src/Math.cppm @@ -1,5 +1,3 @@ -module; - export module LunarWM.Math; import std; diff --git a/src/dhos_config.cpp b/src/dhos_config.cpp new file mode 100644 index 0000000..9569ec5 --- /dev/null +++ b/src/dhos_config.cpp @@ -0,0 +1,652 @@ +#include "dhos_config.h" + +// NOLINTBEGIN + +#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 { + +// 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; +} + +// 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 }; + } + return call(); + 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); +} +Object make_default_lib() +{ + Object l; + l["join"] = Builtin { join }; + l["list_from_range"] = Builtin { list_rng }; + l["import"] = Builtin { imp }; + return l; +} + +// 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) +{ + if (v.is_call()) { + auto const &c = v.call(); + if (!v.env) + throw std::runtime_error( + std::format("Function call with no environment: {}", c.name)); + Value cal = (*v.env)[c.name]; + Array args; + for (auto &a : c.args) + args.push_back(eval(a, extra_lib)); + if (auto b = std::get_if(&cal.data)) + return (*b)(args); + if (auto fp + = std::get_if>(&cal.data)) { + Object lib; + return *(*fp)->body; + } + return cal; + } + + if (!v.is_fn()) + return v; // already concrete + + static Object base = make_default_lib(); + Object lib = merge_lib(base, extra_lib); + + 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); +} + +} // namespace dhos + +// NOLINTEND diff --git a/src/dhos_config.h b/src/dhos_config.h new file mode 100644 index 0000000..0f6a0e7 --- /dev/null +++ b/src/dhos_config.h @@ -0,0 +1,143 @@ +#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; + }; + 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); + +} // namespace dhos + +// NOLINTEND