diff --git a/CMakeLists.txt b/CMakeLists.txt index e8a6bce..1fb4404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,8 @@ add_custom_target(generate_protocols ALL add_executable(waylight ${GEN_C_PRIVATES} + ${CMAKE_CURRENT_SOURCE_DIR}/src/App.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Frame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp ) add_dependencies(waylight generate_protocols) diff --git a/src/App.cpp b/src/App.cpp new file mode 100644 index 0000000..acd4035 --- /dev/null +++ b/src/App.cpp @@ -0,0 +1,552 @@ +#include "App.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define namespace namespace_ +#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#undef namespace + +#include "blur-client-protocol.h" +#include "ext-background-effect-v1-client-protocol.h" + +auto TypingBuffer::push_utf8(const char *s) -> void { + for (const unsigned char *p = reinterpret_cast(s); *p;) { + u32 cp = 0; + int len = 0; + if (*p < 0x80) { + cp = *p++; + len = 1; + } else if ((*p & 0xE0) == 0xC0) { + cp = *p++ & 0x1F; + len = 2; + } else if ((*p & 0xF0) == 0xE0) { + cp = *p++ & 0x0F; + len = 3; + } else { + cp = *p++ & 0x07; + len = 4; + } + for (int i = 1; i < len; i++) + cp = (cp << 6) | ((*p++) & 0x3F); + push_back(cp); + } +} + +App::App() { + init_wayland(); + init_egl(); + init_signal(); + init_theme_portal(); +} + +App::~App() { + if (m_sfd != -1) + close(m_sfd); + + destroy_layer_surface(); + + if (m_gl.edpy != EGL_NO_DISPLAY) { + if (m_gl.ectx != EGL_NO_CONTEXT) + eglDestroyContext(m_gl.edpy, m_gl.ectx); + eglTerminate(m_gl.edpy); + } + + if (m_kbd.xkb_state) + xkb_state_unref(m_kbd.xkb_state); + if (m_kbd.xkb_keymap) + xkb_keymap_unref(m_kbd.xkb_keymap); + if (m_kbd.xkb_ctx) + xkb_context_unref(m_kbd.xkb_ctx); + if (m_wayland.kbd) + wl_keyboard_destroy(m_wayland.kbd); + if (m_wayland.seat) + wl_seat_destroy(m_wayland.seat); + if (m_wayland.compositor) + wl_compositor_destroy(m_wayland.compositor); + if (m_wayland.registry) + wl_registry_destroy(m_wayland.registry); + if (m_wayland.display) + wl_display_disconnect(m_wayland.display); + + if (m_xdp.settings) + g_object_unref(m_xdp.settings); + if (m_xdp.portal) + g_object_unref(m_xdp.portal); +} + +auto App::run() -> void { + SetWindowSize(m_win_w, m_win_h); + while (m_running) { + pump_events(); + render_frame(); + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } +} + +auto App::set_visible(bool visible) -> void { + if (visible == m_visible) + return; + + if (visible) { + create_layer_surface(); + ensure_egl_surface(); + } else { + destroy_layer_surface(); + } + + if (m_wayland.display) + wl_display_flush(m_wayland.display); +} + +auto App::init_wayland() -> void { + m_wayland.display = wl_display_connect(nullptr); + if (!m_wayland.display) { + std::fprintf(stderr, "failed to connect to Wayland display\n"); + std::exit(EXIT_FAILURE); + } + + static wl_keyboard_listener keyboard_listener{}; + { + auto kb_keymap = [](void *data, wl_keyboard *, u32 format, i32 fd, + u32 size) -> void { + auto *app = static_cast(data); + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + void *map = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + close(fd); + return; + } + if (app->m_kbd.xkb_keymap) + xkb_keymap_unref(app->m_kbd.xkb_keymap); + if (app->m_kbd.xkb_state) { + xkb_state_unref(app->m_kbd.xkb_state); + app->m_kbd.xkb_state = nullptr; + } + app->m_kbd.xkb_keymap = xkb_keymap_new_from_string( + app->m_kbd.xkb_ctx, static_cast(map), + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + app->m_kbd.xkb_state = app->m_kbd.xkb_keymap + ? xkb_state_new(app->m_kbd.xkb_keymap) + : nullptr; + munmap(map, size); + close(fd); + }; + + auto kb_enter = [](void *, wl_keyboard *, u32, wl_surface *, wl_array *) -> void {}; + auto kb_leave = [](void *data, wl_keyboard *, u32, wl_surface *) -> void { + static_cast(data)->m_kbd.held.clear(); + }; + + auto kb_key = [](void *data, wl_keyboard *, u32, u32, u32 key, + u32 state) -> void { + auto *app = static_cast(data); + if (!app->m_kbd.xkb_state) + return; + + xkb_keycode_t kc = key + 8; + + if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { + app->m_kbd.held.insert(key); + + xkb_state_update_key(app->m_kbd.xkb_state, kc, XKB_KEY_DOWN); + xkb_keysym_t sym = xkb_state_key_get_one_sym(app->m_kbd.xkb_state, kc); + app->m_kbd.pressed_syms.insert(sym); + + bool ctrl = app->m_kbd.mod_active("Control"); + bool alt = app->m_kbd.mod_active("Mod1"); + bool meta = app->m_kbd.mod_active("Mod4"); + if (!(ctrl || alt || meta)) { + u32 cp = xkb_keysym_to_utf32(sym); + if (cp >= 0x20) { + char buf[8]; + int n = xkb_keysym_to_utf8(sym, buf, sizeof(buf)); + if (n > 0) + app->m_kbd.typing.push_utf8(buf); + } + } + } else { + xkb_keysym_t sym = xkb_state_key_get_one_sym(app->m_kbd.xkb_state, kc); + app->m_kbd.released_syms.insert(sym); + + app->m_kbd.held.erase(key); + xkb_state_update_key(app->m_kbd.xkb_state, kc, XKB_KEY_UP); + } + }; + + auto kb_mods = [](void *data, wl_keyboard *, u32, u32 depressed, u32 latched, + u32 locked, u32 group) -> void { + auto *app = static_cast(data); + if (!app->m_kbd.xkb_state) + return; + xkb_state_update_mask(app->m_kbd.xkb_state, depressed, latched, locked, 0, + 0, group); + }; + + auto kb_repeat_info = [](void *, wl_keyboard *, i32, i32) -> void {}; + + keyboard_listener = {kb_keymap, kb_enter, kb_leave, + kb_key, kb_mods, kb_repeat_info}; + } + + auto handle_registry_global = [](void *data, wl_registry *registry, u32 name, + const char *interface, u32 version) -> void { + auto *app = static_cast(data); + if (std::strcmp(interface, wl_compositor_interface.name) == 0) { + app->m_wayland.compositor = static_cast( + wl_registry_bind(registry, name, &wl_compositor_interface, 4)); + } else if (std::strcmp(interface, wl_seat_interface.name) == 0) { + app->m_wayland.seat = static_cast( + wl_registry_bind(registry, name, &wl_seat_interface, 9)); + static struct wl_seat_listener const seat_listener = { + .capabilities = [](void *data, struct wl_seat *seat, u32 caps) { + auto *app = static_cast(data); + if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { + app->m_wayland.kbd = wl_seat_get_keyboard(seat); + wl_keyboard_add_listener(app->m_wayland.kbd, &keyboard_listener, + data); + } + }, + .name = [](void *, struct wl_seat *, const char *) {}, + }; + wl_seat_add_listener(app->m_wayland.seat, &seat_listener, data); + } else if (std::strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { + app->m_wayland.layer_shell = static_cast( + wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, + version >= 4 ? 4 : version)); + } else if (std::strcmp(interface, + ext_background_effect_manager_v1_interface.name) == 0) { + app->m_wayland.mgr = static_cast( + wl_registry_bind(registry, name, + &ext_background_effect_manager_v1_interface, 1)); + } else if (std::strcmp(interface, "org_kde_kwin_blur_manager") == 0) { + app->m_wayland.kde_blur_mgr = static_cast( + wl_registry_bind(registry, name, &org_kde_kwin_blur_manager_interface, + 1)); + } + }; + + static wl_registry_listener const registry_listener{ + .global = handle_registry_global, + .global_remove = [](void *, wl_registry *, u32) {}, + }; + + m_wayland.registry = wl_display_get_registry(m_wayland.display); + wl_registry_add_listener(m_wayland.registry, ®istry_listener, this); + + m_kbd.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + wl_display_roundtrip(m_wayland.display); + + create_layer_surface(); +} + +auto App::init_egl() -> void { + m_gl.edpy = + eglGetDisplay(reinterpret_cast(m_wayland.display)); + eglInitialize(m_gl.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(m_gl.edpy, cfgAttribs, &m_gl.ecfg, 1, &n); + + const EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE}; + m_gl.ectx = + eglCreateContext(m_gl.edpy, m_gl.ecfg, EGL_NO_CONTEXT, ctxAttribs); + + ensure_egl_surface(); + + InitWindow(m_win_w, m_win_h, ""); +} + +auto App::init_signal() -> void { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + + if (pthread_sigmask(SIG_BLOCK, &mask, nullptr) != 0) { + std::perror("pthread_sigmask"); + std::exit(EXIT_FAILURE); + } + + m_sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (m_sfd == -1) { + std::perror("signalfd"); + std::exit(EXIT_FAILURE); + } +} + +void App::on_settings_changed(XdpSettings * /*self*/, const char *ns, + const char *key, GVariant * /*value*/, + gpointer data) { + auto *app = static_cast(data); + if (g_strcmp0(ns, "org.freedesktop.appearance") == 0 && + g_strcmp0(key, "color-scheme") == 0) { + guint v = xdp_settings_read_uint(app->m_xdp.settings, + "org.freedesktop.appearance", + "color-scheme", NULL, NULL); + + if (v == 1) + app->m_active_theme = Theme::Dark; + else + app->m_active_theme = Theme::Light; + } +} + +auto App::init_theme_portal() -> void { + m_xdp.portal = xdp_portal_new(); + m_xdp.settings = xdp_portal_get_settings(m_xdp.portal); + + guint v = xdp_settings_read_uint(m_xdp.settings, "org.freedesktop.appearance", + "color-scheme", NULL, NULL); + if (v == 1) + m_active_theme = Theme::Dark; + else + m_active_theme = Theme::Light; + + g_signal_connect(m_xdp.settings, "changed", G_CALLBACK(on_settings_changed), + this); +} + +auto App::create_layer_surface() -> void { + if (m_wayland.layer_surface) + return; + + if (!m_wayland.compositor || !m_wayland.layer_shell) + return; + + m_wayland.wl_surface = wl_compositor_create_surface(m_wayland.compositor); + + if (m_wayland.mgr) { + m_wayland.eff = ext_background_effect_manager_v1_get_background_effect( + m_wayland.mgr, m_wayland.wl_surface); + } + if (m_wayland.kde_blur_mgr) { + m_wayland.kde_blur = org_kde_kwin_blur_manager_create( + m_wayland.kde_blur_mgr, m_wayland.wl_surface); + } + + m_wayland.layer_surface = zwlr_layer_shell_v1_get_layer_surface( + m_wayland.layer_shell, m_wayland.wl_surface, nullptr, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "waylight-overlay"); + + if (!m_wayland.layer_surface) { + if (m_wayland.eff) { + ext_background_effect_surface_v1_destroy(m_wayland.eff); + m_wayland.eff = nullptr; + } + if (m_wayland.kde_blur) { + org_kde_kwin_blur_destroy(m_wayland.kde_blur); + m_wayland.kde_blur = nullptr; + } + if (m_wayland.wl_surface) { + wl_surface_destroy(m_wayland.wl_surface); + m_wayland.wl_surface = nullptr; + } + return; + } + + zwlr_layer_surface_v1_set_anchor(m_wayland.layer_surface, 0); + zwlr_layer_surface_v1_set_size(m_wayland.layer_surface, m_win_w, m_win_h); + zwlr_layer_surface_v1_set_exclusive_zone(m_wayland.layer_surface, 0); + + if (zwlr_layer_shell_v1_get_version(m_wayland.layer_shell) >= 3) { + zwlr_layer_surface_v1_set_keyboard_interactivity( + m_wayland.layer_surface, + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND); + } + + auto handle_layer_configure = [](void *data, zwlr_layer_surface_v1 *ls, + u32 serial, u32 w, u32 h) -> void { + auto *app = static_cast(data); + if (w) + app->m_win_w = static_cast(w); + if (h) + app->m_win_h = static_cast(h); + + zwlr_layer_surface_v1_ack_configure(ls, serial); + + if (app->m_gl.edpy != EGL_NO_DISPLAY) { + if (!app->m_gl.wegl || app->m_gl.esurf == EGL_NO_SURFACE) { + app->ensure_egl_surface(); + } else { + wl_egl_window_resize(app->m_gl.wegl, app->m_win_w, app->m_win_h, 0, 0); + eglMakeCurrent(app->m_gl.edpy, app->m_gl.esurf, app->m_gl.esurf, + app->m_gl.ectx); + } + } + + app->update_blur_region(); + + if (app->m_wayland.wl_surface) + wl_surface_commit(app->m_wayland.wl_surface); + }; + + auto handle_layer_closed = [](void *data, zwlr_layer_surface_v1 *) -> void { + static_cast(data)->m_running = false; + }; + + static const zwlr_layer_surface_v1_listener lsl = { + .configure = handle_layer_configure, + .closed = handle_layer_closed, + }; + + zwlr_layer_surface_v1_add_listener(m_wayland.layer_surface, &lsl, this); + + update_blur_region(); + + if (m_wayland.wl_surface) + wl_surface_commit(m_wayland.wl_surface); + + if (m_wayland.display) + wl_display_roundtrip(m_wayland.display); + + ensure_egl_surface(); + + m_visible = true; +} + +auto App::destroy_layer_surface() -> void { + if (m_gl.edpy != EGL_NO_DISPLAY && m_gl.esurf != EGL_NO_SURFACE) { + eglMakeCurrent(m_gl.edpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(m_gl.edpy, m_gl.esurf); + m_gl.esurf = EGL_NO_SURFACE; + } + + if (m_gl.wegl) { + wl_egl_window_destroy(m_gl.wegl); + m_gl.wegl = nullptr; + } + + if (m_wayland.eff) { + ext_background_effect_surface_v1_destroy(m_wayland.eff); + m_wayland.eff = nullptr; + } + + if (m_wayland.kde_blur) { + org_kde_kwin_blur_destroy(m_wayland.kde_blur); + m_wayland.kde_blur = nullptr; + } + + if (m_wayland.layer_surface) { + zwlr_layer_surface_v1_destroy(m_wayland.layer_surface); + m_wayland.layer_surface = nullptr; + } + + if (m_wayland.wl_surface) { + wl_surface_destroy(m_wayland.wl_surface); + m_wayland.wl_surface = nullptr; + } + + if (m_wayland.display) + wl_display_flush(m_wayland.display); + + m_visible = false; +} + +auto App::ensure_egl_surface() -> void { + if (m_gl.edpy == EGL_NO_DISPLAY || m_gl.ectx == EGL_NO_CONTEXT) + return; + if (!m_wayland.wl_surface) + return; + + if (!m_gl.wegl) + m_gl.wegl = wl_egl_window_create(m_wayland.wl_surface, m_win_w, m_win_h); + + if (!m_gl.wegl) + return; + + if (m_gl.esurf == EGL_NO_SURFACE) { + m_gl.esurf = eglCreateWindowSurface( + m_gl.edpy, m_gl.ecfg, reinterpret_cast(m_gl.wegl), + nullptr); + } + + if (m_gl.esurf == EGL_NO_SURFACE) + return; + + eglMakeCurrent(m_gl.edpy, m_gl.esurf, m_gl.esurf, m_gl.ectx); + eglSwapInterval(m_gl.edpy, 1); +} + +auto App::update_blur_region() -> void { + if (!m_wayland.compositor) + return; + if (!m_wayland.eff && !m_wayland.kde_blur) + return; + + wl_region *region = wl_compositor_create_region(m_wayland.compositor); + if (!region) + return; + + wl_region_add(region, 0, 0, m_win_w - 50, m_win_h); + + if (m_wayland.eff) + ext_background_effect_surface_v1_set_blur_region(m_wayland.eff, region); + if (m_wayland.kde_blur) + org_kde_kwin_blur_set_region(m_wayland.kde_blur, region); + + wl_region_destroy(region); +} + +auto App::pump_events() -> void { + while (g_main_context_iteration(nullptr, false)) + ; + + wl_display_dispatch_pending(m_wayland.display); + wl_display_flush(m_wayland.display); + + pollfd fds[2]{{wl_display_get_fd(m_wayland.display), POLLIN, 0}, + {m_sfd, POLLIN, 0}}; + + auto prepared = (wl_display_prepare_read(m_wayland.display) == 0); + auto ret = poll(fds, 2, 0); + + if (ret > 0 && (fds[0].revents & POLLIN)) { + if (prepared) { + wl_display_read_events(m_wayland.display); + prepared = false; + } + } else if (prepared) { + wl_display_cancel_read(m_wayland.display); + } + + if (ret > 0 && (fds[1].revents & POLLIN)) { + signalfd_siginfo si; + while (read(m_sfd, &si, sizeof(si)) == sizeof(si)) { + if (si.ssi_signo == SIGUSR1) { + set_visible(!visible()); + } + } + } +} diff --git a/src/App.hpp b/src/App.hpp new file mode 100644 index 0000000..2481f78 --- /dev/null +++ b/src/App.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include + +#include +#include +extern "C" { +#include "blur-client-protocol.h" +#define namespace namespace_ +#include "ext-background-effect-v1-client-protocol.h" +#include "wlr-layer-shell-unstable-v1-client-protocol.h" +#include +#undef namespace +} +#include +#include +#include + +#include "Theme.hpp" +#include "common.hpp" + +struct TypingBuffer : std::pmr::vector { + void push_utf8(const char *s); +}; + +struct App { + App(); + ~App(); + + auto run() -> void; + auto set_visible(bool visible) -> void; + auto visible() const -> bool { return m_visible; } + + auto stop() -> void { m_running = false; } + +private: + auto init_wayland() -> void; + auto init_egl() -> void; + auto init_signal() -> void; + auto init_theme_portal() -> void; + auto pump_events() -> void; + auto render_frame() -> void; + auto create_layer_surface() -> void; + auto destroy_layer_surface() -> void; + auto ensure_egl_surface() -> void; + auto update_blur_region() -> void; + auto theme() const -> ColorScheme const & { return m_themes[m_active_theme]; } + + static void on_settings_changed(XdpSettings * /*self*/, const char *ns, + const char *key, GVariant * /*value*/, + gpointer data); + + struct { + wl_display *display{}; + wl_registry *registry{}; + wl_compositor *compositor{}; + wl_seat *seat{}; + wl_keyboard *kbd{}; + wl_surface *wl_surface{}; + zwlr_layer_shell_v1 *layer_shell{}; + zwlr_layer_surface_v1 *layer_surface{}; + ext_background_effect_manager_v1 *mgr{}; + ext_background_effect_surface_v1 *eff{}; + org_kde_kwin_blur_manager *kde_blur_mgr{}; + org_kde_kwin_blur *kde_blur{}; + } m_wayland; + + struct { + EGLDisplay edpy{EGL_NO_DISPLAY}; + EGLConfig ecfg{}; + EGLContext ectx{EGL_NO_CONTEXT}; + EGLSurface esurf{EGL_NO_SURFACE}; + wl_egl_window *wegl{}; + } m_gl; + + struct { + XdpPortal *portal{}; + XdpSettings *settings{}; + } m_xdp; + + struct { + TypingBuffer typing{}; + + xkb_context *xkb_ctx{}; + xkb_keymap *xkb_keymap{}; + xkb_state *xkb_state{}; + + std::unordered_set held; + std::unordered_set pressed_syms; + std::unordered_set released_syms; + + auto is_down_evdev(u32 evdev) const -> bool { + return held.find(evdev) != held.end(); + } + + auto is_down_sym(xkb_keysym_t sym) const -> bool { + if (!xkb_state) + return false; + for (auto k : held) { + if (xkb_state_key_get_one_sym(xkb_state, + static_cast(k + 8)) == sym) + return true; + } + return false; + } + + auto is_sym_pressed(xkb_keysym_t sym) const -> bool { + return pressed_syms.find(sym) != pressed_syms.end(); + } + + auto is_sym_released(xkb_keysym_t sym) const -> bool { + return released_syms.find(sym) != released_syms.end(); + } + + auto mod_active(const char *name) const -> bool { + return xkb_state && xkb_state_mod_name_is_active( + xkb_state, name, XKB_STATE_MODS_EFFECTIVE) > 0; + } + + auto ctrl() const -> bool { return mod_active("Control"); } + auto shift() const -> bool { return mod_active("Shift"); } + + void clear_transients() { + pressed_syms.clear(); + released_syms.clear(); + } + } m_kbd; + + enum_array m_themes{make_default_themes()}; + Theme m_active_theme{Theme::Light}; + + int m_win_w{800}; + int m_win_h{600}; + bool m_running{true}; + bool m_visible{true}; + + int m_sfd{-1}; +}; diff --git a/src/Frame.cpp b/src/Frame.cpp new file mode 100644 index 0000000..4273a8f --- /dev/null +++ b/src/Frame.cpp @@ -0,0 +1,39 @@ +#include "App.hpp" + +#include +#include +#include +#include +#include +#include + +auto App::render_frame() -> void { + if (!m_visible || m_gl.edpy == EGL_NO_DISPLAY || m_gl.esurf == EGL_NO_SURFACE) + return; + + glViewport(0, 0, m_win_w, m_win_h); + + for (auto const cp : m_kbd.typing) { + std::println("Char typed: {} ({}) shift={} ctrl={}", rune_to_string(cp), cp, + m_kbd.shift() ? 'y' : 'n', m_kbd.ctrl() ? 'y' : 'n'); + } + + if (m_kbd.is_sym_pressed(XKB_KEY_Escape)) { + set_visible(!visible()); + if (m_kbd.ctrl() && m_kbd.shift()) { + m_running = false; + } + } + + BeginDrawing(); + + ClearBackground(BLANK); + + DrawFPS(10, 10); + + EndDrawing(); + + eglSwapBuffers(m_gl.edpy, m_gl.esurf); + m_kbd.typing.clear(); + m_kbd.clear_transients(); +} diff --git a/src/main.cpp b/src/main.cpp index 7329e3e..df321d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,714 +1,31 @@ -#include #include #include #include -#include +#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "App.hpp" -#define namespace namespace_ -#include "wlr-layer-shell-unstable-v1-client-protocol.h" -extern "C" { -#include -} -#undef namespace +bool check_or_signal_running(); -#include "blur-client-protocol.h" -#include "ext-background-effect-v1-client-protocol.h" +std::optional g_app{}; -#include - -#include "Theme.hpp" -#include "common.hpp" - -struct TypingBuffer : std::pmr::vector { - void push_utf8(const char *s) { - for (const unsigned char *p = (const unsigned char *)s; *p;) { - u32 cp = 0; - int len = 0; - if (*p < 0x80) { - cp = *p++; - len = 1; - } else if ((*p & 0xE0) == 0xC0) { - cp = *p++ & 0x1F; - len = 2; - } else if ((*p & 0xF0) == 0xE0) { - cp = *p++ & 0x0F; - len = 3; - } else { - cp = *p++ & 0x07; - len = 4; - } - for (int i = 1; i < len; i++) - cp = (cp << 6) | ((*p++) & 0x3F); - push_back(cp); - } - } -}; - -struct App { - App() { - init_wayland(); - init_egl(); - init_signal(); - init_theme_portal(); - } - ~App(); - - auto run() -> void { - SetWindowSize(m_win_w, m_win_h); - while (m_running) { - pump_events(); - render_frame(); - std::this_thread::sleep_for(std::chrono::milliseconds(16)); - } - } - auto set_visible(bool visible) -> void; - auto visible() const -> bool { return m_visible; } - - auto stop() -> void { m_running = false; } - -private: - auto init_wayland() -> void; - auto init_egl() -> void; - auto init_signal() -> void; - auto init_theme_portal() -> void; - auto pump_events() -> void; - auto render_frame() -> void; - auto create_layer_surface() -> void; - auto destroy_layer_surface() -> void; - auto ensure_egl_surface() -> void; - auto update_blur_region() -> void; - auto theme() const -> ColorScheme const & { return m_themes[m_active_theme]; } - - static void on_settings_changed(XdpSettings * /*self*/, const char *ns, - const char *key, GVariant * /*value*/, - gpointer data); - - struct { - wl_display *display{}; - wl_registry *registry{}; - wl_compositor *compositor{}; - wl_seat *seat{}; - wl_keyboard *kbd{}; - wl_surface *wl_surface{}; - zwlr_layer_shell_v1 *layer_shell{}; - zwlr_layer_surface_v1 *layer_surface{}; - ext_background_effect_manager_v1 *mgr{}; - ext_background_effect_surface_v1 *eff{}; - org_kde_kwin_blur_manager *kde_blur_mgr{}; - org_kde_kwin_blur *kde_blur{}; - } m_wayland; - - struct { - EGLDisplay edpy{EGL_NO_DISPLAY}; - EGLConfig ecfg{}; - EGLContext ectx{EGL_NO_CONTEXT}; - EGLSurface esurf{EGL_NO_SURFACE}; - wl_egl_window *wegl{}; - } m_gl; - - struct { - XdpPortal *portal{}; - XdpSettings *settings{}; - } m_xdp; - - struct { - TypingBuffer typing{}; - - xkb_context *xkb_ctx{}; - xkb_keymap *xkb_keymap{}; - xkb_state *xkb_state{}; - - std::unordered_set held; - std::unordered_set pressed_syms; - std::unordered_set released_syms; - - auto is_down_evdev(u32 evdev) const -> bool { - return held.find(evdev) != held.end(); - } - - auto is_down_sym(xkb_keysym_t sym) const -> bool { - if (!xkb_state) - return false; - for (auto k : held) { - if (xkb_state_key_get_one_sym(xkb_state, (xkb_keycode_t)(k + 8)) == sym) - return true; - } - return false; - } - - auto is_sym_pressed(xkb_keysym_t sym) const -> bool { - return pressed_syms.find(sym) != pressed_syms.end(); - } - - auto is_sym_released(xkb_keysym_t sym) const -> bool { - return released_syms.find(sym) != released_syms.end(); - } - - auto mod_active(const char *name) const -> bool { - return xkb_state && xkb_state_mod_name_is_active( - xkb_state, name, XKB_STATE_MODS_EFFECTIVE) > 0; - } - - auto ctrl() const -> bool { return mod_active("Control"); } - auto shift() const -> bool { return mod_active("Shift"); } - - void clear_transients() { - pressed_syms.clear(); - released_syms.clear(); - } - } m_kbd; - - enum_array m_themes{make_default_themes()}; - Theme m_active_theme{Theme::Light}; - - int m_win_w{800}; - int m_win_h{600}; - bool m_running{true}; - bool m_visible{true}; - - int m_sfd{-1}; // Signal fd for toggling visibility -}; - -App::~App() { - if (m_sfd != -1) - close(m_sfd); - - destroy_layer_surface(); - - if (m_gl.edpy != EGL_NO_DISPLAY) { - if (m_gl.ectx != EGL_NO_CONTEXT) - eglDestroyContext(m_gl.edpy, m_gl.ectx); - eglTerminate(m_gl.edpy); +auto main() -> int { + if (check_or_signal_running()) { + return 0; } - if (m_kbd.xkb_state) - xkb_state_unref(m_kbd.xkb_state); - if (m_kbd.xkb_keymap) - xkb_keymap_unref(m_kbd.xkb_keymap); - if (m_kbd.xkb_ctx) - xkb_context_unref(m_kbd.xkb_ctx); - if (m_wayland.kbd) - wl_keyboard_destroy(m_wayland.kbd); - if (m_wayland.seat) - wl_seat_destroy(m_wayland.seat); - if (m_wayland.compositor) - wl_compositor_destroy(m_wayland.compositor); - if (m_wayland.registry) - wl_registry_destroy(m_wayland.registry); - if (m_wayland.display) - wl_display_disconnect(m_wayland.display); + std::signal(SIGINT, [](int) { + if (g_app) + g_app->stop(); + }); - if (m_xdp.settings) - g_object_unref(m_xdp.settings); - if (m_xdp.portal) - g_object_unref(m_xdp.portal); -} - -auto App::init_wayland() -> void { - m_wayland.display = wl_display_connect(nullptr); - if (!m_wayland.display) { - std::fprintf(stderr, "failed to connect to Wayland display\n"); - std::exit(EXIT_FAILURE); - } - - static wl_keyboard_listener keyboard_listener{}; - { - auto kb_keymap = [](void *data, wl_keyboard *, u32 format, i32 fd, - u32 size) -> void { - auto *app = (App *)data; - if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { - close(fd); - return; - } - void *map = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (map == MAP_FAILED) { - close(fd); - return; - } - if (app->m_kbd.xkb_keymap) - xkb_keymap_unref(app->m_kbd.xkb_keymap); - if (app->m_kbd.xkb_state) { - xkb_state_unref(app->m_kbd.xkb_state); - app->m_kbd.xkb_state = nullptr; - } - app->m_kbd.xkb_keymap = xkb_keymap_new_from_string( - app->m_kbd.xkb_ctx, (const char *)map, XKB_KEYMAP_FORMAT_TEXT_V1, - XKB_KEYMAP_COMPILE_NO_FLAGS); - app->m_kbd.xkb_state = app->m_kbd.xkb_keymap - ? xkb_state_new(app->m_kbd.xkb_keymap) - : nullptr; - munmap(map, size); - close(fd); - }; - - auto kb_enter = [](void *, wl_keyboard *, u32, wl_surface *, - wl_array *) -> void {}; - auto kb_leave = [](void *data, wl_keyboard *, u32, wl_surface *) -> void { - auto *app = (App *)data; - app->m_kbd.held.clear(); - }; - - auto kb_key = [](void *data, wl_keyboard *, u32, u32, u32 key, - u32 state) -> void { - auto *app = (App *)data; - if (!app->m_kbd.xkb_state) - return; - - xkb_keycode_t kc = key + 8; - - if (state == WL_KEYBOARD_KEY_STATE_PRESSED) { - app->m_kbd.held.insert(key); - - xkb_state_update_key(app->m_kbd.xkb_state, kc, XKB_KEY_DOWN); - xkb_keysym_t sym = xkb_state_key_get_one_sym(app->m_kbd.xkb_state, kc); - app->m_kbd.pressed_syms.insert(sym); - - bool ctrl = app->m_kbd.mod_active("Control"); - bool alt = app->m_kbd.mod_active("Mod1"); - bool meta = app->m_kbd.mod_active("Mod4"); - if (!(ctrl || alt || meta)) { - u32 cp = xkb_keysym_to_utf32(sym); - if (cp >= 0x20) { - char buf[8]; - int n = xkb_keysym_to_utf8(sym, buf, sizeof(buf)); - if (n > 0) - app->m_kbd.typing.push_utf8(buf); - } - } - } else { - xkb_keysym_t sym = xkb_state_key_get_one_sym(app->m_kbd.xkb_state, kc); - app->m_kbd.released_syms.insert(sym); - - app->m_kbd.held.erase(key); - xkb_state_update_key(app->m_kbd.xkb_state, kc, XKB_KEY_UP); - } - }; - - auto kb_mods = [](void *data, wl_keyboard *, u32, u32 depressed, - u32 latched, u32 locked, u32 group) -> void { - auto *app = (App *)data; - if (!app->m_kbd.xkb_state) - return; - xkb_state_update_mask(app->m_kbd.xkb_state, depressed, latched, locked, 0, - 0, group); - }; - - auto kb_repeat_info = [](void *, wl_keyboard *, i32, i32) -> void {}; - - keyboard_listener = {kb_keymap, kb_enter, kb_leave, - kb_key, kb_mods, kb_repeat_info}; - } - - auto handle_registry_global = [](void *data, wl_registry *registry, u32 name, - const char *interface, u32 version) -> void { - auto *app = static_cast(data); - if (std::strcmp(interface, wl_compositor_interface.name) == 0) { - app->m_wayland.compositor = static_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, 4)); - } else if (strcmp(interface, wl_seat_interface.name) == 0) { - app->m_wayland.seat = static_cast( - wl_registry_bind(registry, name, &wl_seat_interface, 9)); - static struct wl_seat_listener const seat_listener = { - .capabilities = - [](void *data, struct wl_seat *seat, u32 caps) { - auto *app = static_cast(data); - if (caps & WL_SEAT_CAPABILITY_KEYBOARD) { - app->m_wayland.kbd = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(app->m_wayland.kbd, - &keyboard_listener, data); - } - }, - .name = [](void *data, struct wl_seat *wl_seat, const char *name) {}, - }; - wl_seat_add_listener(app->m_wayland.seat, &seat_listener, data); - } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - app->m_wayland.layer_shell = (zwlr_layer_shell_v1 *)wl_registry_bind( - registry, name, &zwlr_layer_shell_v1_interface, - version >= 4 ? 4 : version); - } else if (strcmp(interface, - ext_background_effect_manager_v1_interface.name) == 0) { - app->m_wayland.mgr = (ext_background_effect_manager_v1 *)wl_registry_bind( - registry, name, &ext_background_effect_manager_v1_interface, 1); - } else if (strcmp(interface, "org_kde_kwin_blur_manager") == 0) { - app->m_wayland.kde_blur_mgr = - (org_kde_kwin_blur_manager *)wl_registry_bind( - registry, name, &org_kde_kwin_blur_manager_interface, 1); - } - }; - - static wl_registry_listener const registry_listener{ - .global = handle_registry_global, - .global_remove = [](void *, wl_registry *, u32) {}, - }; - - m_wayland.registry = wl_display_get_registry(m_wayland.display); - wl_registry_add_listener(m_wayland.registry, ®istry_listener, this); - - m_kbd.xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - - wl_display_roundtrip(m_wayland.display); - - create_layer_surface(); -} - -auto App::set_visible(bool visible) -> void { - if (visible == m_visible) - return; - - if (visible) { - create_layer_surface(); - ensure_egl_surface(); - } else { - destroy_layer_surface(); - } - - if (m_wayland.display) - wl_display_flush(m_wayland.display); -} - -auto App::init_egl() -> void { - m_gl.edpy = - eglGetDisplay(reinterpret_cast(m_wayland.display)); - eglInitialize(m_gl.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(m_gl.edpy, cfgAttribs, &m_gl.ecfg, 1, &n); - - const EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE}; - m_gl.ectx = - eglCreateContext(m_gl.edpy, m_gl.ecfg, EGL_NO_CONTEXT, ctxAttribs); - - ensure_egl_surface(); - - InitWindow(m_win_w, m_win_h, ""); -} - -auto App::init_signal() -> void { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGUSR1); - - if (pthread_sigmask(SIG_BLOCK, &mask, nullptr) != 0) { - std::perror("pthread_sigmask"); - std::exit(EXIT_FAILURE); - } - - m_sfd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); - if (m_sfd == -1) { - std::perror("signalfd"); - std::exit(EXIT_FAILURE); - } -} - -void App::on_settings_changed(XdpSettings * /*self*/, const char *ns, - const char *key, GVariant * /*value*/, - gpointer data) { - auto *app = static_cast(data); - if (g_strcmp0(ns, "org.freedesktop.appearance") == 0 && - g_strcmp0(key, "color-scheme") == 0) { - guint v = xdp_settings_read_uint(app->m_xdp.settings, - "org.freedesktop.appearance", - "color-scheme", NULL, NULL); - - if (v == 1) - app->m_active_theme = Theme::Dark; - else - app->m_active_theme = Theme::Light; - } -} - -auto App::init_theme_portal() -> void { - m_xdp.portal = xdp_portal_new(); - m_xdp.settings = xdp_portal_get_settings(m_xdp.portal); - - guint v = xdp_settings_read_uint(m_xdp.settings, "org.freedesktop.appearance", - "color-scheme", NULL, NULL); - if (v == 1) - m_active_theme = Theme::Dark; - else - m_active_theme = Theme::Light; - - g_signal_connect(m_xdp.settings, "changed", G_CALLBACK(on_settings_changed), - this); -} - -auto App::render_frame() -> void { - if (!m_visible || m_gl.edpy == EGL_NO_DISPLAY || m_gl.esurf == EGL_NO_SURFACE) - return; - - glViewport(0, 0, m_win_w, m_win_h); - - for (auto const cp : m_kbd.typing) { - std::println("Char typed: {} ({}) shift={} ctrl={}", rune_to_string(cp), - cp, m_kbd.shift() ? 'y' : 'n', m_kbd.ctrl() ? 'y' : 'n'); - } - - if (m_kbd.is_sym_pressed(XKB_KEY_Escape)) { - set_visible(!visible()); - if (m_kbd.ctrl() && m_kbd.shift()) { - m_running = false; - } - } - - BeginDrawing(); - - ClearBackground(BLANK); - - DrawFPS(10, 10); - - EndDrawing(); - - eglSwapBuffers(m_gl.edpy, m_gl.esurf); - m_kbd.typing.clear(); - m_kbd.clear_transients(); -} - -auto App::create_layer_surface() -> void { - if (m_wayland.layer_surface) - return; - - if (!m_wayland.compositor || !m_wayland.layer_shell) - return; - - m_wayland.wl_surface = wl_compositor_create_surface(m_wayland.compositor); - - if (m_wayland.mgr) { - m_wayland.eff = ext_background_effect_manager_v1_get_background_effect( - m_wayland.mgr, m_wayland.wl_surface); - } - if (m_wayland.kde_blur_mgr) { - m_wayland.kde_blur = org_kde_kwin_blur_manager_create( - m_wayland.kde_blur_mgr, m_wayland.wl_surface); - } - - m_wayland.layer_surface = zwlr_layer_shell_v1_get_layer_surface( - m_wayland.layer_shell, m_wayland.wl_surface, nullptr, - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "waylight-overlay"); - - if (!m_wayland.layer_surface) { - if (m_wayland.eff) { - ext_background_effect_surface_v1_destroy(m_wayland.eff); - m_wayland.eff = nullptr; - } - if (m_wayland.kde_blur) { - org_kde_kwin_blur_destroy(m_wayland.kde_blur); - m_wayland.kde_blur = nullptr; - } - if (m_wayland.wl_surface) { - wl_surface_destroy(m_wayland.wl_surface); - m_wayland.wl_surface = nullptr; - } - return; - } - - zwlr_layer_surface_v1_set_anchor(m_wayland.layer_surface, 0); - zwlr_layer_surface_v1_set_size(m_wayland.layer_surface, m_win_w, m_win_h); - zwlr_layer_surface_v1_set_exclusive_zone(m_wayland.layer_surface, 0); - - if (zwlr_layer_shell_v1_get_version(m_wayland.layer_shell) >= 3) { - zwlr_layer_surface_v1_set_keyboard_interactivity( - m_wayland.layer_surface, - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND); - } - - auto handle_layer_configure = [](void *data, zwlr_layer_surface_v1 *ls, - u32 serial, u32 w, u32 h) -> void { - auto *app = static_cast(data); - if (w) - app->m_win_w = static_cast(w); - if (h) - app->m_win_h = static_cast(h); - - zwlr_layer_surface_v1_ack_configure(ls, serial); - - if (app->m_gl.edpy != EGL_NO_DISPLAY) { - if (!app->m_gl.wegl || app->m_gl.esurf == EGL_NO_SURFACE) { - app->ensure_egl_surface(); - } else { - wl_egl_window_resize(app->m_gl.wegl, app->m_win_w, app->m_win_h, 0, 0); - eglMakeCurrent(app->m_gl.edpy, app->m_gl.esurf, app->m_gl.esurf, - app->m_gl.ectx); - } - } - - app->update_blur_region(); - - if (app->m_wayland.wl_surface) - wl_surface_commit(app->m_wayland.wl_surface); - }; - - auto handle_layer_closed = [](void *data, zwlr_layer_surface_v1 *) -> void { - static_cast(data)->m_running = false; - }; - - static const zwlr_layer_surface_v1_listener lsl = { - .configure = handle_layer_configure, - .closed = handle_layer_closed, - }; - - zwlr_layer_surface_v1_add_listener(m_wayland.layer_surface, &lsl, this); - - update_blur_region(); - - if (m_wayland.wl_surface) - wl_surface_commit(m_wayland.wl_surface); - - if (m_wayland.display) - wl_display_roundtrip(m_wayland.display); - - ensure_egl_surface(); - - m_visible = true; -} - -auto App::destroy_layer_surface() -> void { - if (m_gl.edpy != EGL_NO_DISPLAY && m_gl.esurf != EGL_NO_SURFACE) { - eglMakeCurrent(m_gl.edpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(m_gl.edpy, m_gl.esurf); - m_gl.esurf = EGL_NO_SURFACE; - } - - if (m_gl.wegl) { - wl_egl_window_destroy(m_gl.wegl); - m_gl.wegl = nullptr; - } - - if (m_wayland.eff) { - ext_background_effect_surface_v1_destroy(m_wayland.eff); - m_wayland.eff = nullptr; - } - - if (m_wayland.kde_blur) { - org_kde_kwin_blur_destroy(m_wayland.kde_blur); - m_wayland.kde_blur = nullptr; - } - - if (m_wayland.layer_surface) { - zwlr_layer_surface_v1_destroy(m_wayland.layer_surface); - m_wayland.layer_surface = nullptr; - } - - if (m_wayland.wl_surface) { - wl_surface_destroy(m_wayland.wl_surface); - m_wayland.wl_surface = nullptr; - } - - if (m_wayland.display) - wl_display_flush(m_wayland.display); - - m_visible = false; -} - -auto App::ensure_egl_surface() -> void { - if (m_gl.edpy == EGL_NO_DISPLAY || m_gl.ectx == EGL_NO_CONTEXT) - return; - if (!m_wayland.wl_surface) - return; - - if (!m_gl.wegl) - m_gl.wegl = wl_egl_window_create(m_wayland.wl_surface, m_win_w, m_win_h); - - if (!m_gl.wegl) - return; - - if (m_gl.esurf == EGL_NO_SURFACE) { - m_gl.esurf = eglCreateWindowSurface( - m_gl.edpy, m_gl.ecfg, reinterpret_cast(m_gl.wegl), - nullptr); - } - - if (m_gl.esurf == EGL_NO_SURFACE) - return; - - eglMakeCurrent(m_gl.edpy, m_gl.esurf, m_gl.esurf, m_gl.ectx); - eglSwapInterval(m_gl.edpy, 1); -} - -auto App::update_blur_region() -> void { - if (!m_wayland.compositor) - return; - if (!m_wayland.eff && !m_wayland.kde_blur) - return; - - wl_region *region = wl_compositor_create_region(m_wayland.compositor); - if (!region) - return; - - wl_region_add(region, 0, 0, m_win_w - 50, m_win_h); - - if (m_wayland.eff) - ext_background_effect_surface_v1_set_blur_region(m_wayland.eff, region); - if (m_wayland.kde_blur) - org_kde_kwin_blur_set_region(m_wayland.kde_blur, region); - - wl_region_destroy(region); -} - -auto App::pump_events() -> void { - while (g_main_context_iteration(nullptr, false)) - ; - - wl_display_dispatch_pending(m_wayland.display); - wl_display_flush(m_wayland.display); - - pollfd fds[2]{{wl_display_get_fd(m_wayland.display), POLLIN, 0}, - {m_sfd, POLLIN, 0}}; - - auto prepared = (wl_display_prepare_read(m_wayland.display) == 0); - auto ret = poll(fds, 2, 0); - - // Wayland - if (ret > 0 && (fds[0].revents & POLLIN)) { - if (prepared) { - wl_display_read_events(m_wayland.display); - prepared = false; - } - } else if (prepared) { - wl_display_cancel_read(m_wayland.display); - } - - // Signals - if (ret > 0 && (fds[1].revents & POLLIN)) { - signalfd_siginfo si; - while (read(m_sfd, &si, sizeof(si)) == sizeof(si)) { - if (si.ssi_signo == SIGUSR1) { - set_visible(!visible()); - } - } - } + g_app.emplace(); + g_app->run(); } bool check_or_signal_running() { @@ -733,19 +50,3 @@ bool check_or_signal_running() { dprintf(fd, "%d\n", getpid()); return false; } - -std::optional g_app{}; - -auto main() -> int { - if (check_or_signal_running()) { - return 0; - } - - std::signal(SIGINT, [](int) { - if (g_app) - g_app->stop(); - }); - - g_app.emplace(); - g_app->run(); -}