From 470c248bcf600f16f9fdbae011a96c39cbaceda2 Mon Sep 17 00:00:00 2001 From: Slendi Date: Tue, 1 Jul 2025 04:00:52 +0300 Subject: [PATCH] yes Signed-off-by: Slendi --- .clang-format | 12 +- .clangd | 0 CMakeLists.txt | 29 +++++ flake.lock | 61 ++++++++++ flake.nix | 6 +- src/LunarWM.cppm | 153 ++++++++++++++++++++++++ src/main.cpp | 12 ++ src/wl/Shm.cppm | 238 ++++++++++++++++++++++++++++++++++++++ src/wl/Subcompositor.cppm | 47 ++++++++ src/wl/Subsurface.cppm | 73 ++++++++++++ src/wl/util.h | 6 + 11 files changed, 626 insertions(+), 11 deletions(-) create mode 100644 .clangd create mode 100644 CMakeLists.txt create mode 100644 flake.lock create mode 100644 src/LunarWM.cppm create mode 100644 src/main.cpp create mode 100644 src/wl/Shm.cppm create mode 100644 src/wl/Subcompositor.cppm create mode 100644 src/wl/Subsurface.cppm create mode 100644 src/wl/util.h diff --git a/.clang-format b/.clang-format index a872546..c8c48aa 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,3 @@ ---- UseTab: ForIndentation TabWidth: 4 IndentWidth: 4 @@ -6,11 +5,11 @@ ColumnLimit: 80 AlignEscapedNewlines: DontAlign AlignTrailingComments: - Kind: Always - OverEmptyLines: 0 + Kind: Always + OverEmptyLines: 0 BasedOnStyle: WebKit BraceWrapping: - AfterFunction: true + AfterFunction: true BreakBeforeBraces: Custom BreakBeforeInheritanceComma: true BreakConstructorInitializers: BeforeComma @@ -19,10 +18,9 @@ IndentRequiresClause: false InsertNewlineAtEOF: true LineEnding: LF NamespaceIndentation: None -QualifierAlignment: Right +PointerAlignment: Right # east pointer +QualifierAlignment: Right # east const RemoveSemicolon: true RequiresClausePosition: WithFollowing RequiresExpressionIndentation: OuterScope SpaceAfterTemplateKeyword: false - -... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..e69de29 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1b860db --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.30) + +project(LunarWM C CXX) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(PkgConfig) + +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) + +add_executable(${PROJECT_NAME}) +target_sources(${PROJECT_NAME} PUBLIC + src/main.cpp +) +target_sources(${PROJECT_NAME} PUBLIC FILE_SET CXX_MODULES FILES + src/wl/Shm.cppm + src/wl/Subsurface.cppm + src/wl/Subcompositor.cppm + src/LunarWM.cppm +) +target_link_libraries(${PROJECT_NAME} PUBLIC + PkgConfig::WAYLAND + PkgConfig::EGL + PkgConfig::GLES2 +) + 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 index 7781a2d..faddd0c 100644 --- a/flake.nix +++ b/flake.nix @@ -22,8 +22,8 @@ }; in { - devShells.default = pkgs.mkShell { - buildInputs = with pkgs; [ + devShells.default = pkgs.mkShell.override { stdenv = pkgs.llvmPackages_20.libcxxStdenv; } { + packages = with pkgs; [ pkg-config cmake ninja @@ -36,7 +36,6 @@ xorg.libxcb pixman libgbm - vulkan-loader lcms2 seatd libdisplay-info @@ -46,7 +45,6 @@ xorg.xcbutilwm xorg.xcbutilerrors - wlroots libffi wayland wayland-scanner diff --git a/src/LunarWM.cppm b/src/LunarWM.cppm new file mode 100644 index 0000000..84500cb --- /dev/null +++ b/src/LunarWM.cppm @@ -0,0 +1,153 @@ +module; + +#include +#include +#include + +#include +#include +#include +#include + +export module LunarWM.LunarWM; + +import LunarWM.wl.Subcompositor; +import LunarWM.wl.Shm; + +namespace LunarWM { + +export struct LunarWM { + LunarWM(); + ~LunarWM(); + + void run(); + void terminate(); + +private: + struct { + wl_display *display = nullptr; + wl_event_loop *event_loop = nullptr; + std::string socket; + + std::unique_ptr shm; + wl_global *subcompositor = nullptr; + } m_wayland; + + struct { + EGLDisplay display = EGL_NO_DISPLAY; + EGLConfig config; + EGLContext context; + } m_egl; + + bool m_running = true; +}; + +LunarWM::LunarWM() { + { // Wayland + m_wayland.display = wl_display_create(); + if (!m_wayland.display) + throw std::runtime_error("Failed to create wayland display"); + + auto const socket = wl_display_add_socket_auto(m_wayland.display); + if (!socket) + throw std::runtime_error("Failed to add socket"); + m_wayland.socket = socket; + setenv("WAYLAND_DISPLAY", m_wayland.socket.c_str(), 1); + + m_wayland.event_loop = wl_display_get_event_loop(m_wayland.display); + if (!m_wayland.event_loop) + throw std::runtime_error("Failed to get display event loop"); + } + + { // EGL + m_egl.display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, + EGL_DEFAULT_DISPLAY, nullptr); + bool ret = eglInitialize(m_egl.display, nullptr, nullptr); + if (ret != EGL_TRUE) + throw std::runtime_error("eglInitialize failed"); + + // clang-format off + EGLint attribs[] { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE + }; + // clang-format on + + EGLint num_configs; + ret = + eglChooseConfig(m_egl.display, attribs, &m_egl.config, 1, &num_configs); + if (!num_configs || ret != EGL_TRUE) + throw std::runtime_error("eglChooseConfig failed"); + + ret = eglBindAPI(EGL_OPENGL_ES_API); + if (ret != EGL_TRUE) + throw std::runtime_error("eglBindAPI failed"); + + // clang-format off + EGLint ctx_attribs[] { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE, + }; + // clang-format on + + m_egl.context = + eglCreateContext(m_egl.display, m_egl.config, nullptr, ctx_attribs); + if (m_egl.context == EGL_NO_CONTEXT) + throw std::runtime_error("eglCreateContext failed"); + + EGLint pb_attribs[]{EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; + EGLSurface pb = + eglCreatePbufferSurface(m_egl.display, m_egl.config, pb_attribs); + if (pb == EGL_NO_SURFACE) + throw std::runtime_error("eglCreatePbufferSurface failed"); + + if (eglMakeCurrent(m_egl.display, pb, pb, m_egl.context) != EGL_TRUE) + throw std::runtime_error("eglMakeCurrent failed"); + + std::println("GL ES version: {}", + reinterpret_cast(glGetString(GL_VERSION))); + } + + { // Wayland part 2: Electric boogaloo + m_wayland.shm = std::make_unique(m_wayland.display); + + m_wayland.subcompositor = subcompositor_create(m_wayland.display); + if (!m_wayland.subcompositor) + throw std::runtime_error("Failed to create subcompositor"); + } +} + +LunarWM::~LunarWM() { + { // Wayland second initialization block + if (m_wayland.subcompositor) + wl_global_destroy(m_wayland.subcompositor); + if (m_wayland.shm) + m_wayland.shm.reset(); + } + { // EGL + if (m_egl.display != EGL_NO_DISPLAY) + eglDestroyContext(m_egl.display, m_egl.context); + } + { // Wayland + if (m_wayland.display) + wl_display_destroy(m_wayland.display); + } + std::println("bai bai~!"); +} + +void LunarWM::run() { + std::println("Running wayland compositor on WAYLAND_DISPLAY={}.", + m_wayland.socket); + + while (m_running) { + if (wl_event_loop_dispatch(m_wayland.event_loop, 0) < 0) { + break; + } + wl_display_flush_clients(m_wayland.display); + } +} + +void LunarWM::terminate() { m_running = false; } + +} // namespace LunarWM diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..a481538 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,12 @@ +#include +#include + +import LunarWM.LunarWM; + +std::unique_ptr g_comp; + +int main(void) { + g_comp = std::make_unique(); + std::signal(SIGINT, [](int) { g_comp->terminate(); }); + g_comp->run(); +} diff --git a/src/wl/Shm.cppm b/src/wl/Shm.cppm new file mode 100644 index 0000000..b1f9e79 --- /dev/null +++ b/src/wl/Shm.cppm @@ -0,0 +1,238 @@ +module; + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +export module LunarWM.wl.Shm; + +namespace LunarWM { + +export struct Shm { + Shm(wl_display *display); + ~Shm(); + + wl_global *global = nullptr; +}; + +} // namespace LunarWM + +namespace { + +using LunarWM::Shm; + +struct Pool { + wl_resource *res = nullptr; + Shm *shm = nullptr; + void *data = nullptr; + size_t size = 0; + unsigned refs = 1; +}; + +inline void unref_pool(Pool *p) { + if (--p->refs == 0) { + if (p->data && p->data != MAP_FAILED) + ::munmap(p->data, p->size); + delete p; + } +} + +struct Buffer { + wl_resource *res = nullptr; + Pool *pool = nullptr; + GLuint tex = 0; + int32_t w = 0; + int32_t h = 0; + int32_t stride = 0; + uint32_t fmt = WL_SHM_FORMAT_ARGB8888; + int32_t off = 0; + + void *pixels() const { return static_cast(pool->data) + off; } +}; + +#ifdef __NetBSD__ +void *remap(void *oldp, size_t oldsz, size_t newsz) { + return ::mremap(oldp, oldsz, nullptr, newsz, 0); +} +#else +void *remap(void *oldp, size_t oldsz, size_t newsz) { + return ::mremap(oldp, oldsz, newsz, MREMAP_MAYMOVE); +} +#endif + +void buf_req_destroy(wl_client *, wl_resource *r) { ::wl_resource_destroy(r); } + +void buf_resource_destroy(wl_resource *r) { + auto *b = static_cast(::wl_resource_get_user_data(r)); + + if (b->tex) + ::glDeleteTextures(1, &b->tex); + + unref_pool(b->pool); + delete b; +} + +const struct wl_buffer_interface buffer_impl{.destroy = buf_req_destroy}; + +void pool_req_destroy(wl_client *, wl_resource *r) { ::wl_resource_destroy(r); } + +void pool_resource_destroy(wl_resource *r) { + auto *p = static_cast(::wl_resource_get_user_data(r)); + unref_pool(p); +} + +void pool_req_resize(wl_client *, wl_resource *r, int32_t new_sz) { + auto *p = static_cast(::wl_resource_get_user_data(r)); + + void *d = remap(p->data, p->size, new_sz); + if (d == MAP_FAILED) { + ::wl_resource_post_error(r, WL_SHM_ERROR_INVALID_FD, "mremap failed: %s", + ::strerror(errno)); + return; + } + p->data = d; + p->size = new_sz; +} + +void pool_req_create_buf(wl_client *client, wl_resource *pool_res, uint32_t id, + int32_t off, int32_t w, int32_t h, int32_t stride, + uint32_t fmt) { + auto *p = static_cast(::wl_resource_get_user_data(pool_res)); + + if (off < 0 || static_cast(off) > p->size) { + ::wl_resource_post_error(pool_res, WL_SHM_ERROR_INVALID_STRIDE, + "offset out of bounds"); + return; + } + + auto *b = new Buffer{}; + if (!b) { + ::wl_resource_post_no_memory(pool_res); + return; + } + + b->pool = p; + b->w = w; + b->h = h; + b->stride = stride; + b->fmt = fmt; + b->off = off; + ++p->refs; + + b->res = ::wl_resource_create(client, &wl_buffer_interface, 1, id); + if (!b->res) { + ::wl_resource_post_no_memory(pool_res); + unref_pool(p); + delete b; + return; + } + ::wl_resource_set_implementation(b->res, &buffer_impl, b, + &buf_resource_destroy); + + ::glGenTextures(1, &b->tex); + ::glBindTexture(GL_TEXTURE_2D, b->tex); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ::glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + +#ifndef GL_BGRA_EXT +#define GL_BGRA_EXT 0x80E1 +#endif + + GLenum pfmt = (fmt == WL_SHM_FORMAT_XRGB8888 || fmt == WL_SHM_FORMAT_ARGB8888) + ? GL_BGRA_EXT + : GL_RGBA; // fallback + + ::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, pfmt, GL_UNSIGNED_BYTE, + b->pixels()); +} + +const struct wl_shm_pool_interface shm_pool_impl{ + .create_buffer = pool_req_create_buf, + .destroy = pool_req_destroy, + .resize = pool_req_resize, +}; + +void create_pool(wl_client *client, wl_resource *res, uint32_t id, int32_t fd, + int32_t size) { + auto *shm = static_cast(::wl_resource_get_user_data(res)); + + auto *p = new Pool{}; + if (!p) { + ::wl_resource_post_no_memory(res); + ::close(fd); + return; + } + + p->shm = shm; + + p->res = ::wl_resource_create(client, &wl_shm_pool_interface, + ::wl_resource_get_version(res), id); + if (!p->res) { + ::wl_resource_post_no_memory(res); + delete p; + ::close(fd); + return; + } + + ::wl_resource_set_implementation(p->res, &shm_pool_impl, p, + &pool_resource_destroy); + + p->data = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (p->data == MAP_FAILED) { + ::wl_resource_post_error(res, WL_SHM_ERROR_INVALID_FD, "mmap failed: %s", + ::strerror(errno)); + ::wl_resource_destroy(p->res); + delete p; + ::close(fd); + return; + } + + p->size = size; + ::close(fd); +} + +const struct wl_shm_interface shm_impl{ + .create_pool = create_pool, +}; + +void bind_shm(wl_client *client, void *data, uint32_t version, uint32_t id) { + auto *shm = static_cast(data); + + wl_resource *r = ::wl_resource_create(client, &wl_shm_interface, version, id); + if (!r) { + ::wl_client_post_no_memory(client); + return; + } + + ::wl_resource_set_implementation(r, &shm_impl, shm, nullptr); + + ::wl_shm_send_format(r, WL_SHM_FORMAT_XRGB8888); + ::wl_shm_send_format(r, WL_SHM_FORMAT_ARGB8888); +} + +} // namespace + +namespace LunarWM { + +Shm::Shm(wl_display *display) { + this->global = + ::wl_global_create(display, &wl_shm_interface, 1, this, &bind_shm); + if (!this->global) + throw std::runtime_error("wl_global_create failed"); +} + +Shm::~Shm() { + if (this->global) + ::wl_global_destroy(this->global); +} + +} // namespace LunarWM diff --git a/src/wl/Subcompositor.cppm b/src/wl/Subcompositor.cppm new file mode 100644 index 0000000..8a27ee2 --- /dev/null +++ b/src/wl/Subcompositor.cppm @@ -0,0 +1,47 @@ +module; + +#include "util.h" + +#include + +export module LunarWM.wl.Subcompositor; + +import LunarWM.wl.Subsurface; + +namespace LunarWM { + +export wl_global *subcompositor_create(struct wl_display *display); + +void get_subsurface(wl_client *client, wl_resource *res, uint32_t id, + wl_resource *surface, wl_resource *parent) { + auto subsurface = Subsurface::make(client, wl_resource_get_version(res), id); + if (!subsurface) { + wl_resource_post_no_memory(res); + return; + } +} + +struct wl_subcompositor_interface const subcompositor_impl = { + .destroy = resource_destroy, + .get_subsurface = get_subsurface, +}; + +void bind_subcompositor(wl_client *client, void *data, uint32_t version, + uint32_t id) { + struct wl_resource *resource; + + resource = + wl_resource_create(client, &wl_subcompositor_interface, version, id); + if (!resource) { + wl_client_post_no_memory(client); + return; + } + wl_resource_set_implementation(resource, &subcompositor_impl, NULL, NULL); +} + +wl_global *subcompositor_create(struct wl_display *display) { + return wl_global_create(display, &wl_subcompositor_interface, 1, NULL, + &bind_subcompositor); +} + +} // namespace LunarWM diff --git a/src/wl/Subsurface.cppm b/src/wl/Subsurface.cppm new file mode 100644 index 0000000..6842465 --- /dev/null +++ b/src/wl/Subsurface.cppm @@ -0,0 +1,73 @@ +module; + +#include "util.h" + +#include + +#include + +export module LunarWM.wl.Subsurface; + +namespace LunarWM { + +export struct Subsurface { + Subsurface() = delete; + + static Subsurface *make(wl_client *client, uint32_t version, uint32_t id); + static void destroy(wl_resource *res); + + struct wl_resource *resource; +}; + +void set_position(wl_client *client, wl_resource *resource, int32_t x, + int32_t y) { + // TODO: Implement. +} + +void place_above(wl_client *client, wl_resource *resource, + wl_resource *sibling_resource) { + // TODO: Implement. +} + +void place_below(wl_client *client, wl_resource *resource, + wl_resource *sibling_resource) { + // TODO: Implement. +} + +void set_sync(wl_client *client, wl_resource *resource) { + // TODO: Implement. +} + +void set_desync(wl_client *client, wl_resource *resource) { + // TODO: Implement. +} + +struct wl_subsurface_interface const subsurface_impl{ + .destroy = resource_destroy, + .set_position = set_position, + .place_above = place_above, + .place_below = place_below, + .set_sync = set_sync, + .set_desync = set_desync, +}; + +void Subsurface::destroy(wl_resource *res) { + free(wl_resource_get_user_data(res)); +} + +Subsurface *Subsurface::make(wl_client *client, uint32_t version, uint32_t id) { + Subsurface *subsurface = + reinterpret_cast(malloc(sizeof(Subsurface))); + subsurface->resource = + wl_resource_create(client, &wl_subsurface_interface, version, id); + if (!subsurface->resource) { + free(subsurface); + throw std::runtime_error("wl_resource_create(subsurface) failed"); + } + + wl_resource_set_implementation(subsurface->resource, &subsurface_impl, + subsurface, &Subsurface::destroy); + return subsurface; +} + +} // namespace LunarWM diff --git a/src/wl/util.h b/src/wl/util.h new file mode 100644 index 0000000..9ace17a --- /dev/null +++ b/src/wl/util.h @@ -0,0 +1,6 @@ +#include + +static void resource_destroy(struct wl_client *client, + struct wl_resource *resource) { + wl_resource_destroy(resource); +}