diff --git a/CMakeLists.txt b/CMakeLists.txt index e9c0f62..e8a6bce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.16) project(waylight LANGUAGES C CXX) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -12,6 +12,7 @@ pkg_check_modules(EGL REQUIRED IMPORTED_TARGET egl) pkg_check_modules(GLES2 REQUIRED IMPORTED_TARGET glesv2) pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(LIBPORTAL REQUIRED IMPORTED_TARGET libportal) +pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon) pkg_check_modules(WAYLAND_PROTOCOLS REQUIRED wayland-protocols) pkg_check_modules(WLR_PROTOCOLS REQUIRED wlr-protocols) @@ -130,6 +131,7 @@ target_link_libraries(waylight PRIVATE PkgConfig::GLES2 PkgConfig::GLIB PkgConfig::LIBPORTAL + PkgConfig::XKBCOMMON raylib diff --git a/flake.nix b/flake.nix index 642f27e..9cd5067 100644 --- a/flake.nix +++ b/flake.nix @@ -33,6 +33,7 @@ doxygen gtest cppcheck + inotify-tools pkg-config wayland @@ -42,6 +43,7 @@ libGL libportal glib + libxkbcommon ] ++ buildInputs ++ nativeBuildInputs diff --git a/src/common.hpp b/src/common.hpp new file mode 100644 index 0000000..b93ef5d --- /dev/null +++ b/src/common.hpp @@ -0,0 +1,36 @@ +#include + +using u8 = std::uint8_t; +using i8 = std::int8_t; +using u16 = std::uint16_t; +using i16 = std::int16_t; +using u32 = std::uint32_t; +using i32 = std::int32_t; +using u64 = std::uint64_t; +using i64 = std::int64_t; +using usize = std::uintptr_t; +using isize = std::intptr_t; + +inline auto rune_to_string(uint32_t cp) -> char const * { + static char utf8[5] = {0}; + for (auto &c : utf8) + c = 0; + + if (cp < 0x80) { + utf8[0] = cp; + } else if (cp < 0x800) { + utf8[0] = 0xC0 | (cp >> 6); + utf8[1] = 0x80 | (cp & 0x3F); + } else if (cp < 0x10000) { + utf8[0] = 0xE0 | (cp >> 12); + utf8[1] = 0x80 | ((cp >> 6) & 0x3F); + utf8[2] = 0x80 | (cp & 0x3F); + } else { + utf8[0] = 0xF0 | (cp >> 18); + utf8[1] = 0x80 | ((cp >> 12) & 0x3F); + utf8[2] = 0x80 | ((cp >> 6) & 0x3F); + utf8[3] = 0x80 | (cp & 0x3F); + } + + return utf8; +} diff --git a/src/main.cpp b/src/main.cpp index d1c4cba..7329e3e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,9 +5,13 @@ #include #include #include +#include #include +#include #include #include +#include +#include #include #include @@ -16,6 +20,7 @@ #include #include #include +#include #define namespace namespace_ #include "wlr-layer-shell-unstable-v1-client-protocol.h" @@ -30,6 +35,32 @@ extern "C" { #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() { @@ -74,6 +105,8 @@ private: 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{}; @@ -96,6 +129,53 @@ private: 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}; @@ -119,6 +199,16 @@ App::~App() { 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) @@ -139,13 +229,116 @@ auto App::init_wayland() -> void { std::exit(EXIT_FAILURE); } - auto handle_registry_global = [](void *data, wl_registry *registry, - uint32_t name, const char *interface, - uint32_t version) -> void { + 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, @@ -163,12 +356,14 @@ auto App::init_wayland() -> void { static wl_registry_listener const registry_listener{ .global = handle_registry_global, - .global_remove = [](void *, wl_registry *, uint32_t) {}, + .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(); @@ -276,15 +471,29 @@ auto App::render_frame() -> void { 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(theme().window.background); + 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 { @@ -336,8 +545,7 @@ auto App::create_layer_surface() -> void { } auto handle_layer_configure = [](void *data, zwlr_layer_surface_v1 *ls, - uint32_t serial, uint32_t w, - uint32_t h) -> void { + u32 serial, u32 w, u32 h) -> void { auto *app = static_cast(data); if (w) app->m_win_w = static_cast(w); @@ -459,7 +667,7 @@ auto App::update_blur_region() -> void { if (!region) return; - wl_region_add(region, 0, 0, m_win_w, m_win_h); + 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); diff --git a/tools/continuous_build.sh b/tools/continuous_build.sh new file mode 100755 index 0000000..60e0b44 --- /dev/null +++ b/tools/continuous_build.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +while inotifywait -r -e modify,create,delete src; do + cmake --build build +done +