Signed-off-by: Slendi <slendi@socopon.com>
This commit is contained in:
2025-07-01 04:00:52 +03:00
parent bdb9d472de
commit 470c248bcf
11 changed files with 626 additions and 11 deletions

View File

@@ -1,4 +1,3 @@
---
UseTab: ForIndentation
TabWidth: 4
IndentWidth: 4
@@ -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
...

0
.clangd Normal file
View File

29
CMakeLists.txt Normal file
View File

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

61
flake.lock generated Normal file
View File

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

View File

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

153
src/LunarWM.cppm Normal file
View File

@@ -0,0 +1,153 @@
module;
#include <memory>
#include <poll.h>
#include <print>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <wayland-server.h>
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> 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<const char *>(glGetString(GL_VERSION)));
}
{ // Wayland part 2: Electric boogaloo
m_wayland.shm = std::make_unique<Shm>(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

12
src/main.cpp Normal file
View File

@@ -0,0 +1,12 @@
#include <csignal>
#include <memory>
import LunarWM.LunarWM;
std::unique_ptr<LunarWM::LunarWM> g_comp;
int main(void) {
g_comp = std::make_unique<LunarWM::LunarWM>();
std::signal(SIGINT, [](int) { g_comp->terminate(); });
g_comp->run();
}

238
src/wl/Shm.cppm Normal file
View File

@@ -0,0 +1,238 @@
module;
#include <cerrno>
#include <cstring>
#include <stdexcept>
#include <wayland-server.h>
#include <GLES2/gl2.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
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<uint8_t *>(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<Buffer *>(::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<Pool *>(::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<Pool *>(::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<Pool *>(::wl_resource_get_user_data(pool_res));
if (off < 0 || static_cast<size_t>(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<Shm *>(::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<Shm *>(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

47
src/wl/Subcompositor.cppm Normal file
View File

@@ -0,0 +1,47 @@
module;
#include "util.h"
#include <wayland-server.h>
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

73
src/wl/Subsurface.cppm Normal file
View File

@@ -0,0 +1,73 @@
module;
#include "util.h"
#include <stdexcept>
#include <wayland-server.h>
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<Subsurface *>(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

6
src/wl/util.h Normal file
View File

@@ -0,0 +1,6 @@
#include <wayland-server.h>
static void resource_destroy(struct wl_client *client,
struct wl_resource *resource) {
wl_resource_destroy(resource);
}