commit c93ab29a907bf80f5de5ec158e02fc06293e757b Author: Slendi Date: Sun Jun 29 15:57:38 2025 +0300 Initial commit Signed-off-by: Slendi diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a872546 --- /dev/null +++ b/.clang-format @@ -0,0 +1,28 @@ +--- +UseTab: ForIndentation +TabWidth: 4 +IndentWidth: 4 +ColumnLimit: 80 + +AlignEscapedNewlines: DontAlign +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +BasedOnStyle: WebKit +BraceWrapping: + AfterFunction: true +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: true +BreakConstructorInitializers: BeforeComma +IndentPPDirectives: AfterHash +IndentRequiresClause: false +InsertNewlineAtEOF: true +LineEnding: LF +NamespaceIndentation: None +QualifierAlignment: Right +RemoveSemicolon: true +RequiresClausePosition: WithFollowing +RequiresExpressionIndentation: OuterScope +SpaceAfterTemplateKeyword: false + +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0840a8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.cache +[Bb]uild* + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..607baf8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,54 @@ +cmake_minimum_required(VERSION 3.12) + +project(lunarwm VERSION 0.0.1 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +add_compile_options(-Wall -Wextra -Wno-missing-field-initializers) + +add_compile_definitions( + XR_USE_PLATFORM_XLIB + XR_USE_GRAPHICS_API_OPENGL + WLR_USE_UNSTABLE +) + + +list(INSERT CMAKE_MODULE_PATH 0 + ${CMAKE_CURRENT_SOURCE_DIR}/cmake +) + +find_package(OpenGL REQUIRED) +find_package(OpenXR REQUIRED) +find_package(glm REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(Wlroots REQUIRED wlroots-0.19) +pkg_check_modules(X11 REQUIRED x11) +pkg_check_modules(wayland REQUIRED wayland-server wayland-protocols) + +include_directories( + ${wayland_INCLUDE_DIRS} + ${X11_INCLUDE_DIRS} + ${GLM_INCLUDE_DIRS} + ${Wlroots_INCLUDE_DIRS} +) + +set(SOURCES + src/wlr/backend.cpp + src/wlr/output.cpp + + src/compositor.cpp + src/main.cpp +) + +add_executable(${PROJECT_NAME} ${SOURCES}) + +target_link_libraries(${PROJECT_NAME} + OpenGL::GL + openxr_loader + ${wayland_LIBRARIES} + ${X11_LIBRARIES} + ${GLM_LIBRARIES} + ${Wlroots_LIBRARIES} +) diff --git a/cmake/FindWlroots.cmake b/cmake/FindWlroots.cmake new file mode 100644 index 0000000..07797b5 --- /dev/null +++ b/cmake/FindWlroots.cmake @@ -0,0 +1,10 @@ +find_package(PkgConfig) +pkg_check_modules(PC_WLR QUIET wlroots) +find_path(WLR_INCLUDE_DIRS NAMES wlr/config.h HINTS ${PC_WLR_INCLUDE_DIRS}) +find_library(WLR_LIBRARIES NAMES wlroots HINTS ${PC_WLR_LIBRARY_DIRS}) + +set(WLR_DEFINITIONS ${PC_WLR_CFLAGS_OTHER}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(wlr DEFAULT_MSG WLR_LIBRARIES WLR_INCLUDE_DIRS) +mark_as_advanced(WLR_LIBRARIES WLR_INCLUDE_DIRS) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c57137d --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1751011381, + "narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7781a2d --- /dev/null +++ b/flake.nix @@ -0,0 +1,65 @@ +{ + description = "LunarWM is a VR-based Wayland compositor"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + system = system; + config.cudaSupport = true; + config.allowUnfree = true; + }; + in + { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + pkg-config + cmake + ninja + clang-tools + lldb + + # For wlroots + libxkbcommon + libdrm + xorg.libxcb + pixman + libgbm + vulkan-loader + lcms2 + seatd + libdisplay-info + libliftoff + libinput + xorg.xcbutilrenderutil + xorg.xcbutilwm + xorg.xcbutilerrors + + wlroots + libffi + wayland + wayland-scanner + wayland-protocols + + openxr-loader + libGL + glm + xorg.libX11 + xorg.libXau + xorg.libXdmcp + ]; + }; + } + ); +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..45a8ac3 --- /dev/null +++ b/src/common.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#define OPENXR_CHECK(fn_call, ...) \ + do { \ + XrResult result = fn_call(__VA_ARGS__); \ + if (result != XR_SUCCESS) { \ + std::array msg {}; \ + xrResultToString(nullptr, result, msg.data()); \ + throw std::runtime_error( \ + std::format("OpenXR call '{}' failed (code {}): {}", #fn_call, \ + static_cast(result), msg.data())); \ + } \ + } while (0) diff --git a/src/compositor.cpp b/src/compositor.cpp new file mode 100644 index 0000000..7be2109 --- /dev/null +++ b/src/compositor.cpp @@ -0,0 +1,259 @@ +#include "compositor.h" + +#include "common.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +void Compositor::create_instance() +{ + // Wow, can't believe this compositor uses AI! + XrApplicationInfo ai { + .applicationName = {}, + .applicationVersion = 1, + .engineName = {}, + .engineVersion = 1, + .apiVersion = XR_API_VERSION_1_0, + }; + + std::strncpy( + ai.applicationName, "LunarWM", XR_MAX_APPLICATION_NAME_SIZE - 1); + std::strncpy(ai.engineName, "LunarWM Engine", XR_MAX_ENGINE_NAME_SIZE - 1); + + std::vector const instance_extensions { + XR_EXT_DEBUG_UTILS_EXTENSION_NAME, + XR_KHR_OPENGL_ENABLE_EXTENSION_NAME, + }; + + std::vector extension_props; + { // Populate extension_props + uint32_t len = 0; + OPENXR_CHECK( + xrEnumerateInstanceExtensionProperties, nullptr, 0, &len, nullptr); + extension_props.resize(len, { XR_TYPE_EXTENSION_PROPERTIES }); + OPENXR_CHECK(xrEnumerateInstanceExtensionProperties, nullptr, len, &len, + extension_props.data()); + } + + // Check that we have all our requested extensions available + for (auto const& requested_instance_extension : instance_extensions) { + bool found = false; + + for (auto const& eprop : extension_props) { + if (strcmp(requested_instance_extension, eprop.extensionName) + == 0) { + found = true; + break; + } + } + + if (!found) { + throw std::runtime_error(std::format( + "Failed to find extension: {}", requested_instance_extension)); + } + } + + { // Create our instance + XrInstanceCreateInfo ci { + .type = XR_TYPE_INSTANCE_CREATE_INFO, + .next = nullptr, + .createFlags = 0, + .applicationInfo = ai, + .enabledApiLayerCount = 0, + .enabledApiLayerNames = nullptr, + .enabledExtensionCount + = static_cast(instance_extensions.size()), + .enabledExtensionNames = instance_extensions.data(), + }; + + OPENXR_CHECK(xrCreateInstance, &ci, &m_xr_instance); + } +} + +void Compositor::destroy_instance() +{ + OPENXR_CHECK(xrDestroyInstance, m_xr_instance); +} + +void Compositor::create_session() +{ + XrSystemId system_id; + + { // Get system ID. + XrSystemGetInfo system_gi { XR_TYPE_SYSTEM_GET_INFO }; + system_gi.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; + OPENXR_CHECK(xrGetSystem, m_xr_instance, &system_gi, &system_id); + + // NOTE: Might be useful later. + XrSystemProperties system_properties = { XR_TYPE_SYSTEM_PROPERTIES }; + OPENXR_CHECK(xrGetSystemProperties, m_xr_instance, system_id, + &system_properties); + } + + { // Get GLX graphics requirements + PFN_xrGetOpenGLGraphicsRequirementsKHR xr_get_gl_req = nullptr; + xrGetInstanceProcAddr(m_xr_instance, + "xrGetOpenGLGraphicsRequirementsKHR", + reinterpret_cast(&xr_get_gl_req)); + + XrGraphicsRequirementsOpenGLKHR gl_req { + XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR + }; + OPENXR_CHECK(xr_get_gl_req, m_xr_instance, system_id, &gl_req); + + std::println("OpenGL requirements: min=0x{:X}, max=0x{:X}", + gl_req.minApiVersionSupported, gl_req.maxApiVersionSupported); + } + + XrGraphicsBindingOpenGLXlibKHR graphics_binding {}; + + { // Xlib stuff + Display* display = XOpenDisplay(nullptr); + if (!display) + throw std::runtime_error("Failed to open X display"); + + int screen = DefaultScreen(display); + + static int visual_attribs[] = { + GLX_X_RENDERABLE, + True, + GLX_DRAWABLE_TYPE, + GLX_WINDOW_BIT, + GLX_RENDER_TYPE, + GLX_RGBA_BIT, + GLX_X_VISUAL_TYPE, + GLX_TRUE_COLOR, + GLX_RED_SIZE, + 8, + GLX_GREEN_SIZE, + 8, + GLX_BLUE_SIZE, + 8, + GLX_DEPTH_SIZE, + 24, + GLX_DOUBLEBUFFER, + True, + None, + }; + + int fbcount; + GLXFBConfig* fbc + = glXChooseFBConfig(display, screen, visual_attribs, &fbcount); + if (!fbc) + throw std::runtime_error("No GLXFBConfig found"); + + // just grab first config + GLXFBConfig best_fbc = fbc[0]; + XFree(fbc); + + XVisualInfo* vi = glXGetVisualFromFBConfig(display, best_fbc); + if (!vi) + throw std::runtime_error("Failed to get XVisualInfo"); + + Window root = RootWindow(display, screen); + + XSetWindowAttributes swa; + swa.colormap = XCreateColormap(display, root, vi->visual, AllocNone); + swa.event_mask = ExposureMask | KeyPressMask; + + Window win = XCreateWindow(display, root, 0, 0, 800, 600, 0, vi->depth, + InputOutput, vi->visual, CWColormap | CWEventMask, &swa); + XMapWindow(display, win); + XStoreName(display, win, "LunarWM OpenXR"); + + GLXContext glc = glXCreateNewContext( + display, best_fbc, GLX_RGBA_TYPE, nullptr, True); + if (!glc) + throw std::runtime_error("Failed to create GLX context"); + + if (!glXMakeContextCurrent(display, win, win, glc)) + throw std::runtime_error("glXMakeContextCurrent failed"); + + // fill in OpenXR graphics binding + graphics_binding.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR; + graphics_binding.next = nullptr; + graphics_binding.xDisplay = display; + graphics_binding.visualid = vi->visualid; + graphics_binding.glxFBConfig = best_fbc; + graphics_binding.glxDrawable = win; + graphics_binding.glxContext = glc; + } + + { // Create session + XrSessionCreateInfo ci { XR_TYPE_SESSION_CREATE_INFO }; + ci.next = &graphics_binding; + ci.systemId = system_id; + ci.createFlags = 0; + OPENXR_CHECK(xrCreateSession, m_xr_instance, &ci, &m_session); + } +} + +void Compositor::destroy_session() +{ + OPENXR_CHECK(xrDestroySession, m_session); +} + +void Compositor::poll_events() +{ + XrEventDataBuffer event_data { XR_TYPE_EVENT_DATA_BUFFER }; + auto xr_poll_events = [&]() -> bool { + event_data = { XR_TYPE_EVENT_DATA_BUFFER }; + return xrPollEvent(m_xr_instance, &event_data) == XR_SUCCESS; + }; + + while (xr_poll_events()) { + switch (event_data.type) { + case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { + XrEventDataSessionStateChanged* session_state_changed + = reinterpret_cast( + &event_data); + if (session_state_changed->session != m_session) { + std::println(std::cerr, + "XrEventDataSessionStateChanged for unknown Session"); + break; + } + if (session_state_changed->state == XR_SESSION_STATE_READY) { + XrSessionBeginInfo session_begin_info { + XR_TYPE_SESSION_BEGIN_INFO + }; + session_begin_info.primaryViewConfigurationType + = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + OPENXR_CHECK(xrBeginSession, m_session, &session_begin_info); + m_session_running = true; + } + if (session_state_changed->state == XR_SESSION_STATE_STOPPING) { + OPENXR_CHECK(xrEndSession, m_session); + m_session_running = false; + } + if (session_state_changed->state == XR_SESSION_STATE_EXITING + || session_state_changed->state + == XR_SESSION_STATE_LOSS_PENDING) { + m_session_running = false; + m_running = false; + } + m_session_state = session_state_changed->state; + break; + } + default: + break; + } + } +} + +void Compositor::run() +{ + while (m_running) { + this->poll_events(); + + if (m_session_running) { + // TODO: Render frame + } + } +} diff --git a/src/compositor.h b/src/compositor.h new file mode 100644 index 0000000..20b7498 --- /dev/null +++ b/src/compositor.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +struct Compositor { + Compositor() + { + this->create_instance(); + this->create_session(); + } + + ~Compositor() + { + this->destroy_session(); + this->destroy_instance(); + } + + void run(); + void stop() { m_running = false; } + +private: + void create_instance(); + void destroy_instance(); + + void create_session(); + void destroy_session(); + + void poll_events(); + + bool m_running = true; + + // OpenXR related stuff + XrInstance m_xr_instance; + bool m_session_running = false; + XrSession m_session = {}; + XrSessionState m_session_state = XR_SESSION_STATE_UNKNOWN; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..6175d44 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,16 @@ +#include "compositor.h" + +#include +#include +#include + +std::unique_ptr g_c; + +int main(void) +{ + g_c = std::make_unique(); + std::signal(SIGINT, [](int) { g_c->stop(); }); + g_c->run(); + + std::println("bai bai~!"); +} diff --git a/src/wlr/backend.cpp b/src/wlr/backend.cpp new file mode 100644 index 0000000..fecfa82 --- /dev/null +++ b/src/wlr/backend.cpp @@ -0,0 +1,60 @@ +#include + +extern "C" { +#include +#include +#include +} + +struct my_backend { + struct wlr_backend base; + // TODO: any backend‐specific fields +}; + +extern "C" { + +static bool backend_start(struct wlr_backend* backend) +{ + (void)backend; + // TODO: start event loops, create outputs, inputs, etc. + return true; +} + +static void backend_destroy(struct wlr_backend* backend) +{ + (void)backend; + // TODO: destroy outputs, free everything +} + +static int backend_get_drm_fd(struct wlr_backend* backend) +{ + (void)backend; + // TODO: return drm FD or -1 + return -1; +} + +static const struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_drm_fd = backend_get_drm_fd, +}; + +struct wlr_backend* wlr_my_backend_create( + struct wl_event_loop* loop, char const* name) +{ + (void)loop; + (void)name; + struct my_backend* b = (struct my_backend*)calloc(1, sizeof(*b)); + if (!b) { + return NULL; + } + wlr_backend_init(&b->base, &backend_impl); + // TODO: store loop/name, setup + return &b->base; +} + +bool wlr_backend_is_mybackend(struct wlr_backend* backend) +{ + return backend->impl == &backend_impl; +} +} diff --git a/src/wlr/output.cpp b/src/wlr/output.cpp new file mode 100644 index 0000000..bed5a0c --- /dev/null +++ b/src/wlr/output.cpp @@ -0,0 +1,91 @@ +extern "C" { +#include +#include +#include +} + +extern "C" { + +static void parse_setup(struct wlr_output* output) +{ + (void)output; + // TODO: parse your backend’s setup (e.g. native display info) +} + +static bool output_set_custom_mode(struct wlr_output* wlr_output, int32_t width, + int32_t height, int32_t refresh) +{ + (void)wlr_output; + (void)width; + (void)height; + (void)refresh; + // Intentionally left blank + return true; +} + +static void output_destroy(struct wlr_output* wlr_output) { (void)wlr_output; } + +static bool output_test( + struct wlr_output* wlr_output, const struct wlr_output_state* state) +{ + (void)wlr_output; + (void)state; + // TODO: check if this state is supported + return true; +} + +static bool output_commit( + struct wlr_output* wlr_output, const struct wlr_output_state* state) +{ + (void)wlr_output; + (void)state; + // TODO: apply enable/disable, mode, buffer, etc. + return true; +} + +static bool output_set_cursor(struct wlr_output* wlr_output, + struct wlr_buffer* buffer, int32_t hotspot_x, int32_t hotspot_y) +{ + (void)wlr_output; + (void)buffer; + (void)hotspot_x; + (void)hotspot_y; + // TODO: upload cursor image + return false; +} + +static bool output_move_cursor(struct wlr_output* wlr_output, int x, int y) +{ + (void)wlr_output; + (void)x; + (void)y; + // TODO: move cursor to x, y + return true; +} + +static const struct wlr_drm_format_set* output_get_primary_formats( + struct wlr_output* wlr_output, uint32_t buffer_caps) +{ + (void)wlr_output; + (void)buffer_caps; + // TODO: return DRM/SHM formats your backend supports + return NULL; +} + +static const struct wlr_output_impl output_impl = { + .set_cursor = output_set_cursor, + .move_cursor = output_move_cursor, + .destroy = output_destroy, + .test = output_test, + .commit = output_commit, + .get_primary_formats = output_get_primary_formats, +}; + +struct wlr_output* wlr_my_output_create(struct wlr_backend* backend) +{ + (void)backend; + // TODO: alloc + init your output struct and call + // wlr_output_init(&output->base, backend, &output_impl, …) + return NULL; +} +}