From fcf3c612a0fa35f1b4b639795e90779e247fc4be Mon Sep 17 00:00:00 2001 From: Slendi Date: Sat, 4 Oct 2025 19:10:50 +0300 Subject: [PATCH] asdf Signed-off-by: Slendi --- .envrc | 1 + .gitignore | 4 + CMakeLists.txt | 163 ++++++++++++++++++++++++++++++++++++++++ flake.lock | 59 +++++++++++++++ flake.nix | 60 +++++++++++++++ main.cpp | 200 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 487 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 main.cpp diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..149a11e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +[Bb]uild* +result +.cache +.direnv diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..efa8a53 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 3.16) +project(skia_wayland_nodeco LANGUAGES C CXX) + +# --- toolchain & opts --------------------------------------------------------- +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# --- deps (pkg-config) -------------------------------------------------------- +find_package(PkgConfig REQUIRED) +pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) +pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) +pkg_check_modules(EGL REQUIRED egl) +pkg_check_modules(GLES2 REQUIRED glesv2) +# Skia package name varies; this works on many distros. Adjust if needed. +pkg_check_modules(SKIA REQUIRED skia) +# The protocol XMLs live here: +pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) + +# --- scanners ----------------------------------------------------------------- +find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) +# waylandpp's generator; provided by the waylandpp package +# (typically installed as 'wayland-scanner++') +find_program(WAYLAND_SCANNER_PP wayland-scanner++ REQUIRED) + +# --- protocol XML paths ------------------------------------------------------- +pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) + +# fallback via datarootdir if needed +if(NOT WAYLAND_PROTOCOLS_DIR OR NOT EXISTS "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml") + pkg_get_variable(_WP_DATAROOT wayland-protocols datarootdir) + if(_WP_DATAROOT AND EXISTS "${_WP_DATAROOT}/wayland-protocols") + set(WAYLAND_PROTOCOLS_DIR "${_WP_DATAROOT}/wayland-protocols") + endif() +endif() + +# last-resort search (covers odd layouts) +if(NOT WAYLAND_PROTOCOLS_DIR OR NOT EXISTS "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml") + find_path(WAYLAND_PROTOCOLS_DIR + NAMES stable/xdg-shell/xdg-shell.xml + HINTS ${CMAKE_SYSTEM_PREFIX_PATH} + PATH_SUFFIXES wayland-protocols share/wayland-protocols + ) +endif() + +if(NOT WAYLAND_PROTOCOLS_DIR) + message(FATAL_ERROR "Could not locate wayland-protocols datadir (install wayland-protocols?)") +endif() + +set(XDG_SHELL_XML + "${WAYLAND_PROTOCOLS_DIR}/stable/xdg-shell/xdg-shell.xml" +) +set(XDG_DECOR_XML + "${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" +) + +# --- generated outputs (into build dir) --------------------------------------- +set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") +file(MAKE_DIRECTORY "${GEN_DIR}") + +# waylandpp C++ headers + code (used by the app) +set(GEN_CXX_HEADERS + "${GEN_DIR}/xdg-shell-client-protocol.hpp" + "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.hpp" +) +set(GEN_CXX_SOURCES + "${GEN_DIR}/xdg-shell-client-protocol.cpp" + "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.cpp" +) + +# (optional) C headers + private impls (not linked, but generated) +set(GEN_C_HEADERS + "${GEN_DIR}/xdg-shell-client-protocol.h" + "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.h" +) +set(GEN_C_PRIVATES + "${GEN_DIR}/xdg-shell-protocol.c" + "${GEN_DIR}/xdg-decoration-unstable-v1-protocol.c" +) + +# --- custom commands: generate C++ (required) --------------------------------- +add_custom_command( + OUTPUT ${GEN_CXX_HEADERS} ${GEN_CXX_SOURCES} + COMMAND "${WAYLAND_SCANNER_PP}" "${XDG_SHELL_XML}" "${GEN_DIR}/xdg-shell-client-protocol.hpp" "${GEN_DIR}/xdg-shell-client-protocol.cpp" + COMMAND "${WAYLAND_SCANNER_PP}" "${XDG_DECOR_XML}" "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.hpp" "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.cpp" + DEPENDS "${XDG_SHELL_XML}" "${XDG_DECOR_XML}" + COMMENT "Generating waylandpp C++ protocol headers/sources with wayland-scanner++" + VERBATIM +) + +# --- custom commands: generate C (optional) ----------------------------------- +add_custom_command( + OUTPUT ${GEN_C_HEADERS} ${GEN_C_PRIVATES} + COMMAND "${WAYLAND_SCANNER}" client-header "${XDG_SHELL_XML}" "${GEN_DIR}/xdg-shell-client-protocol.h" + COMMAND "${WAYLAND_SCANNER}" private-code "${XDG_SHELL_XML}" "${GEN_DIR}/xdg-shell-protocol.c" + COMMAND "${WAYLAND_SCANNER}" client-header "${XDG_DECOR_XML}" "${GEN_DIR}/xdg-decoration-unstable-v1-client-protocol.h" + COMMAND "${WAYLAND_SCANNER}" private-code "${XDG_DECOR_XML}" "${GEN_DIR}/xdg-decoration-unstable-v1-protocol.c" + DEPENDS "${XDG_SHELL_XML}" "${XDG_DECOR_XML}" + COMMENT "Generating C client headers + private code with wayland-scanner (optional)" + VERBATIM +) + +add_custom_target(generate_protocols ALL + DEPENDS ${GEN_CXX_HEADERS} ${GEN_CXX_SOURCES} ${GEN_C_HEADERS} ${GEN_C_PRIVATES} +) + +# --- executable --------------------------------------------------------------- +add_executable(skia_wayland + ${GEN_CXX_HEADERS} + ${GEN_CXX_SOURCES} + + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp +) +add_dependencies(skia_wayland generate_protocols) + +# include dirs (system + generated) +target_include_directories(skia_wayland PRIVATE + ${WAYLAND_CLIENT_INCLUDE_DIRS} + ${WAYLAND_EGL_INCLUDE_DIRS} + ${EGL_INCLUDE_DIRS} + ${GLES2_INCLUDE_DIRS} + ${SKIA_INCLUDE_DIRS} + # waylandpp headers (wayland-client.hpp). If they aren't system-wide, add your path here: + # ${CMAKE_CURRENT_SOURCE_DIR}/external/waylandpp/include + ${GEN_DIR} +) + +# libs +target_link_libraries(skia_wayland PRIVATE + ${WAYLAND_CLIENT_LIBRARIES} + ${WAYLAND_EGL_LIBRARIES} + ${EGL_LIBRARIES} + ${GLES2_LIBRARIES} + ${SKIA_LIBRARIES} + # some distros need these as well: + m + dl + pthread +) + +# cflags/ldflags from pkg-config +target_compile_options(skia_wayland PRIVATE + ${WAYLAND_CLIENT_CFLAGS_OTHER} + ${WAYLAND_EGL_CFLAGS_OTHER} + ${EGL_CFLAGS_OTHER} + ${GLES2_CFLAGS_OTHER} + ${SKIA_CFLAGS_OTHER} +) +target_link_options(skia_wayland PRIVATE + ${WAYLAND_CLIENT_LDFLAGS_OTHER} + ${WAYLAND_EGL_LDFLAGS_OTHER} + ${EGL_LDFLAGS_OTHER} + ${GLES2_LDFLAGS_OTHER} + ${SKIA_LDFLAGS_OTHER} +) + +# --- warnings ----------------------------------------------------------------- +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(skia_wayland PRIVATE -Wall -Wextra -Wno-unused-parameter) +endif() + +# --- install (optional) ------------------------------------------------------- +install(TARGETS skia_wayland RUNTIME DESTINATION bin) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..73fc090 --- /dev/null +++ b/flake.lock @@ -0,0 +1,59 @@ +{ + "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": 1759381078, + "narHash": "sha256-gTrEEp5gEspIcCOx9PD8kMaF1iEmfBcTbO0Jag2QhQs=", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "revCount": 870157, + "type": "tarball", + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.870157%2Brev-7df7ff7d8e00218376575f0acdcc5d66741351ee/0199a6ee-287f-71cc-b285-5fb880bb8f7f/source.tar.gz?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee&revCount=870157" + }, + "original": { + "type": "tarball", + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1" + } + }, + "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..69f5fd7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,60 @@ +{ + description = "My flake"; + + inputs = { + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + nativeBuildInputs = [ ]; + buildInputs = with pkgs; [ + cmake + ninja + ]; + in + { + devShells.default = pkgs.mkShell.override { stdenv = pkgs.llvmPackages_21.libcxxStdenv; } { + packages = + with pkgs; + [ + llvmPackages_21.clang-tools + lldb + codespell + doxygen + gtest + cppcheck + + pkg-config + wayland + wayland-protocols + wayland-scanner + waylandpp + libGL + skia + ] + ++ buildInputs + ++ nativeBuildInputs + ++ pkgs.lib.optionals pkgs.stdenv.isLinux ( + with pkgs; + [ + valgrind-light + ] + ); + + env = { }; + + shellHook = ''''; + }; + } + ); +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..cf0058e --- /dev/null +++ b/main.cpp @@ -0,0 +1,200 @@ +#include "xdg-shell-client-protocol.hpp" + +#include "xdg-decoration-unstable-v1-client-protocol.hpp" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct App { + wayland::display_t display; + wayland::registry_t registry; + wayland::compositor_t compositor; + wayland::xdg_wm_base_t xdg_wm; + wayland::zxdg_decoration_manager_v1_t dec_mgr; + wayland::surface_t wl_surface; + wayland::xdg_surface_t xdg_surface; + wayland::xdg_toplevel_t xdg_toplevel; + + EGLDisplay edpy = EGL_NO_DISPLAY; + EGLConfig ecfg = nullptr; + EGLContext ectx = EGL_NO_CONTEXT; + EGLSurface esurf = EGL_NO_SURFACE; + wl_egl_window *wegl = nullptr; + + sk_sp gl_iface; + sk_sp gr_ctx; + sk_sp sk_surface; + + int win_w = 800, win_h = 600; + bool running = true; + + void init_wayland() { + registry = display.get_registry(); + registry.on_global() = [&](uint32_t name, const std::string &iface, + uint32_t ver) { + if (iface == wayland::compositor_t::interface_name) + compositor = registry.bind( + name, std::min(ver, 4)); + else if (iface == wayland::xdg_wm_base_t::interface_name) + xdg_wm = registry.bind(name, 1); + else if (iface == wayland::zxdg_decoration_manager_v1_t::interface_name) + dec_mgr = registry.bind(name, 1); + }; + xdg_wm.on_ping() = [&](uint32_t serial) { xdg_wm.pong(serial); }; + display.roundtrip(); + if (!compositor || !xdg_wm) { + fprintf(stderr, "missing compositor\n"); + exit(1); + } + + wl_surface = compositor.create_surface(); + xdg_surface = xdg_wm.get_xdg_surface(wl_surface); + xdg_toplevel = xdg_surface.get_toplevel(); + + if (dec_mgr) { + auto deco = dec_mgr.get_toplevel_decoration(xdg_toplevel); + deco.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::client_side); + } + xdg_toplevel.set_title("Skia Hello World"); + xdg_toplevel.on_close() = [&] { running = false; }; + + xdg_surface.on_configure() = [&](uint32_t serial) { + xdg_surface.ack_configure(serial); + wl_surface.commit(); + }; + xdg_toplevel.on_configure() = [&](int32_t w, int32_t h, + std::vector) { + if (w > 0 && h > 0 && (w != win_w || h != win_h)) { + win_w = w; + win_h = h; + if (wegl) { + wl_egl_window_resize(wegl, win_w, win_h, 0, 0); + recreate_skia_surface(); + } + } + }; + + wl_surface.commit(); + display.roundtrip(); + } + + void init_egl() { + edpy = eglGetDisplay((EGLNativeDisplayType)display.c_ptr()); + eglInitialize(edpy, nullptr, nullptr); + + const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_NONE}; + EGLint n = 0; + eglChooseConfig(edpy, cfgAttribs, &ecfg, 1, &n); + const EGLint ctxAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + ectx = eglCreateContext(edpy, ecfg, EGL_NO_CONTEXT, ctxAttribs); + wegl = wl_egl_window_create(wl_surface, win_w, win_h); + esurf = + eglCreateWindowSurface(edpy, ecfg, (EGLNativeWindowType)wegl, nullptr); + eglMakeCurrent(edpy, esurf, esurf, ectx); + eglSwapInterval(edpy, 1); + } + + void init_skia() { + gl_iface = GrGLMakeNativeInterface(); + gr_ctx = GrDirectContext::MakeGL(gl_iface); + recreate_skia_surface(); + } + + void recreate_skia_surface() { + GrGLFramebufferInfo fbInfo{0, GL_RGBA8}; + GrBackendRenderTarget backendRT(win_w, win_h, 0, 8, fbInfo); + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + sk_surface = SkSurfaces::WrapBackendRenderTarget( + gr_ctx.get(), backendRT, kBottomLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, nullptr, &props); + } + + void draw_frame() { + SkCanvas *c = sk_surface->getCanvas(); + c->clear(SkColorSetARGB(255, 20, 22, 26)); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(255, 240, 240, 255)); + + SkFont font; + font.setSize(48); + + const char *text = "Hello, world!"; + SkRect bounds; + font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds); + + float x = (win_w - bounds.width()) / 2 - bounds.left(); + float y = (win_h + bounds.height()) / 2 - bounds.bottom(); + + c->drawSimpleText(text, strlen(text), SkTextEncoding::kUTF8, x, y, font, + paint); + + sk_surface->flushAndSubmit(); + eglSwapBuffers(edpy, esurf); + } + + void run() { + while (running) { + display.dispatch_pending(); + display.flush(); + draw_frame(); + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } + } + + void cleanup() { + gr_ctx->flushAndSubmit(); + gr_ctx->releaseResourcesAndAbandonContext(); + sk_surface.reset(); + gl_iface.reset(); + eglMakeCurrent(edpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(edpy, esurf); + wl_egl_window_destroy(wegl); + eglDestroyContext(edpy, ectx); + eglTerminate(edpy); + } +}; + +int main() { + App a; + a.init_wayland(); + a.init_egl(); + a.init_skia(); + a.run(); + a.cleanup(); + return 0; +}