Compare commits

...

2 Commits

Author SHA1 Message Date
1414a66e56 Format
Signed-off-by: Slendi <slendi@socopon.com>
2025-10-05 04:50:09 +03:00
8653b02c44 Code split
Signed-off-by: Slendi <slendi@socopon.com>
2025-10-05 04:49:16 +03:00
10 changed files with 884 additions and 797 deletions

26
.clang-format Normal file
View File

@@ -0,0 +1,26 @@
UseTab: ForIndentation
TabWidth: 4
IndentWidth: 4
ColumnLimit: 80
AlignEscapedNewlines: DontAlign
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
BasedOnStyle: WebKit
BraceWrapping:
AfterFunction: true
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: true
BreakConstructorInitializers: BeforeComma
IndentPPDirectives: AfterHash
IndentRequiresClause: false
InsertNewlineAtEOF: true
LineEnding: LF
NamespaceIndentation: None
PointerAlignment: Right # east pointer
QualifierAlignment: Right # east const
RemoveSemicolon: true
RequiresClausePosition: WithFollowing
RequiresExpressionIndentation: OuterScope
SpaceAfterTemplateKeyword: false

View File

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

552
src/App.cpp Normal file
View File

@@ -0,0 +1,552 @@
#include "App.hpp"
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/signalfd.h>
#include <thread>
#include <unistd.h>
#include <GLES3/gl3.h>
#include <glib.h>
#include <raylib.h>
#include <rlgl.h>
#include <xkbcommon/xkbcommon.h>
#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<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);
}
}
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<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, static_cast<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 {
static_cast<App *>(data)->m_kbd.held.clear();
};
auto kb_key = [](void *data, wl_keyboard *, u32, u32, u32 key,
u32 state) -> void {
auto *app = static_cast<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 = static_cast<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<App *>(data);
if (std::strcmp(interface, wl_compositor_interface.name) == 0) {
app->m_wayland.compositor = static_cast<wl_compositor *>(
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_seat *>(
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<App *>(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<zwlr_layer_shell_v1 *>(
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<ext_background_effect_manager_v1 *>(
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<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, &registry_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<EGLNativeDisplayType>(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<App *>(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<App *>(data);
if (w)
app->m_win_w = static_cast<int>(w);
if (h)
app->m_win_h = static_cast<int>(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<App *>(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<EGLNativeWindowType>(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());
}
}
}
}

151
src/App.hpp Normal file
View File

@@ -0,0 +1,151 @@
#pragma once
#include <memory_resource>
#include <unordered_set>
#include <vector>
#include <EGL/egl.h>
#include <libportal/portal.h>
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 <libportal/settings.h>
#undef namespace
}
#include <wayland-client.h>
#include <wayland-egl.h>
#include <xkbcommon/xkbcommon.h>
#include "Theme.hpp"
#include "common.hpp"
struct TypingBuffer : std::pmr::vector<u32> {
void push_utf8(char const *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*/, char const *ns,
char const *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<u32> held;
std::unordered_set<u32> pressed_syms;
std::unordered_set<u32> 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<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(char const *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<Theme, ColorScheme> 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 };
};

39
src/Frame.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "App.hpp"
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <print>
#include <raylib.h>
#include <rlgl.h>
#include <xkbcommon/xkbcommon.h>
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();
}

View File

@@ -5,31 +5,32 @@
#include "enum_array.hpp"
struct ColorScheme {
struct {
Color background;
} window;
struct {
Color background;
} window;
};
enum class Theme : int { Light = 0, Dark, _last = Dark };
template <> struct enum_traits<Theme> {
static constexpr Theme first = Theme::Light;
static constexpr Theme last = Theme::_last;
template<> struct enum_traits<Theme> {
static constexpr Theme first = Theme::Light;
static constexpr Theme last = Theme::_last;
};
constexpr auto make_default_themes() -> enum_array<Theme, ColorScheme> const {
enum_array<Theme, ColorScheme> array;
array[Theme::Light] = {
constexpr auto make_default_themes() -> enum_array<Theme, ColorScheme> const
{
enum_array<Theme, ColorScheme> array;
array[Theme::Light] = {
.window =
{
.background = {255, 255, 255, 100},
},
};
array[Theme::Dark] = {
array[Theme::Dark] = {
.window =
{
.background = {0, 0, 0, 100},
},
};
return array;
return array;
}

View File

@@ -11,26 +11,27 @@ 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;
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);
}
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;
return utf8;
}

View File

@@ -5,69 +5,75 @@
#include <stdexcept>
#include <type_traits>
template <class E> struct enum_traits;
template<class E> struct enum_traits;
template <class E>
template<class E>
concept EnumLike = std::is_enum_v<E>;
template <EnumLike E>
constexpr std::size_t enum_count_v =
static_cast<std::size_t>(enum_traits<E>::last) -
static_cast<std::size_t>(enum_traits<E>::first) + 1;
template<EnumLike E>
constexpr std::size_t enum_count_v
= static_cast<std::size_t>(enum_traits<E>::last)
- static_cast<std::size_t>(enum_traits<E>::first) + 1;
template <EnumLike E, class T> struct enum_array {
using value_type = T;
using enum_type = E;
using underlying_index_type = std::size_t;
template<EnumLike E, class T> struct enum_array {
using value_type = T;
using enum_type = E;
using underlying_index_type = std::size_t;
static constexpr E first = enum_traits<E>::first;
static constexpr E last = enum_traits<E>::last;
static constexpr std::size_t size_value = enum_count_v<E>;
static constexpr E first = enum_traits<E>::first;
static constexpr E last = enum_traits<E>::last;
static constexpr std::size_t size_value = enum_count_v<E>;
std::array<T, size_value> _data{};
std::array<T, size_value> _data {};
static constexpr std::size_t size() noexcept { return size_value; }
constexpr T *data() noexcept { return _data.data(); }
constexpr const T *data() const noexcept { return _data.data(); }
constexpr T *begin() noexcept { return _data.begin().operator->(); }
constexpr const T *begin() const noexcept {
return _data.begin().operator->();
}
constexpr T *end() noexcept { return _data.end().operator->(); }
constexpr const T *end() const noexcept { return _data.end().operator->(); }
static constexpr std::size_t size() noexcept { return size_value; }
constexpr T *data() noexcept { return _data.data(); }
constexpr T const *data() const noexcept { return _data.data(); }
constexpr T *begin() noexcept { return _data.begin().operator->(); }
constexpr T const *begin() const noexcept
{
return _data.begin().operator->();
}
constexpr T *end() noexcept { return _data.end().operator->(); }
constexpr T const *end() const noexcept { return _data.end().operator->(); }
constexpr T &operator[](E e) noexcept { return _data[to_index(e)]; }
constexpr const T &operator[](E e) const noexcept {
return _data[to_index(e)];
}
constexpr T &operator[](E e) noexcept { return _data[to_index(e)]; }
constexpr T const &operator[](E e) const noexcept
{
return _data[to_index(e)];
}
constexpr T &at(E e) {
auto i = to_index(e);
if (i >= size_value)
throw std::out_of_range("enum_array::at");
return _data[i];
}
constexpr const T &at(E e) const {
auto i = to_index(e);
if (i >= size_value)
throw std::out_of_range("enum_array::at");
return _data[i];
}
constexpr T &at(E e)
{
auto i = to_index(e);
if (i >= size_value)
throw std::out_of_range("enum_array::at");
return _data[i];
}
constexpr T const &at(E e) const
{
auto i = to_index(e);
if (i >= size_value)
throw std::out_of_range("enum_array::at");
return _data[i];
}
constexpr void fill(const T &v) { _data.fill(v); }
constexpr void fill(T const &v) { _data.fill(v); }
private:
static constexpr std::size_t to_index(E e) noexcept {
return static_cast<std::size_t>(e) - static_cast<std::size_t>(first);
}
static constexpr std::size_t to_index(E e) noexcept
{
return static_cast<std::size_t>(e) - static_cast<std::size_t>(first);
}
};
template <class E, class T, class... U>
requires EnumLike<E> && (std::is_same_v<T, U> && ...)
constexpr auto make_enum_array(T &&first_val, U &&...rest) {
enum_array<E, std::decay_t<T>> arr;
static_assert(sizeof...(rest) + 1 == enum_count_v<E>,
"initializer count must match enum range");
arr._data = {std::forward<T>(first_val), std::forward<U>(rest)...};
return arr;
template<class E, class T, class... U>
requires EnumLike<E> && (std::is_same_v<T, U> && ...)
constexpr auto make_enum_array(T &&first_val, U &&...rest)
{
enum_array<E, std::decay_t<T>> arr;
static_assert(sizeof...(rest) + 1 == enum_count_v<E>,
"initializer count must match enum range");
arr._data = { std::forward<T>(first_val), std::forward<U>(rest)... };
return arr;
}

View File

@@ -1,714 +1,31 @@
#include <cassert>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <optional>
#include <poll.h>
#include <print>
#include <signal.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/signalfd.h>
#include <thread>
#include <unordered_set>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <glib.h>
#include <libportal/portal.h>
#include <raylib.h>
#include <rlgl.h>
#include <wayland-egl.h>
#include <xkbcommon/xkbcommon.h>
#include "App.hpp"
#define namespace namespace_
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
extern "C" {
#include <libportal/settings.h>
}
#undef namespace
bool check_or_signal_running();
#include "blur-client-protocol.h"
#include "ext-background-effect-v1-client-protocol.h"
std::optional<App> g_app{};
#include <wayland-client.h>
#include "Theme.hpp"
#include "common.hpp"
struct TypingBuffer : std::pmr::vector<u32> {
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<u32> held;
std::unordered_set<u32> pressed_syms;
std::unordered_set<u32> 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<Theme, ColorScheme> 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<App *>(data);
if (std::strcmp(interface, wl_compositor_interface.name) == 0) {
app->m_wayland.compositor = static_cast<wl_compositor *>(
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_seat *>(
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<App *>(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, &registry_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<EGLNativeDisplayType>(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<App *>(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<App *>(data);
if (w)
app->m_win_w = static_cast<int>(w);
if (h)
app->m_win_h = static_cast<int>(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<App *>(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<EGLNativeWindowType>(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<App> 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();
}

8
tools/format.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)"
find "$ROOT/src" -type f -name '*.cpp' -o -name '*.hpp' -print0 | while IFS= read -r -d '' f; do
clang-format -i --style=file "$f"
done