From ee2df36041fd1ff934b4b0a05a9f28059350858b Mon Sep 17 00:00:00 2001 From: Squibid Date: Sun, 21 Sep 2025 23:46:57 -0400 Subject: [PATCH] feat(wayland): add idle inhibitor for wayland --- include/wayland.h | 17 ++++ meson.build | 37 ++++++- src/led.c | 2 +- src/main.c | 7 ++ src/wayland.c | 240 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 include/wayland.h create mode 100644 src/wayland.c diff --git a/include/wayland.h b/include/wayland.h new file mode 100644 index 0000000..68b4ce7 --- /dev/null +++ b/include/wayland.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** + * @brief create the wayland thread + * + * @return 0 on success + */ +int wayland_create_thread(void); + +/** + * @brief set the idle lock + * + * @param lock change the idle lock state + */ +void wayland_set_idle_lock(int lock); diff --git a/meson.build b/meson.build index c012265..9d55475 100644 --- a/meson.build +++ b/meson.build @@ -7,16 +7,51 @@ add_project_arguments([ srcfiles = files( 'src/main.c', 'src/led.c', + 'src/wayland.c', 'src/acpi.c', 'src/utils.c' ) +# wayland stuff +wscanner = dependency('wayland-scanner', native: true) +wscanner_prog = find_program( + wscanner.get_variable('wayland_scanner'), native: true) + +wayland_protocols = dependency('wayland-protocols', version: '>=1.41', + default_options: ['tests=false']) +wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir') +wayland_client = dependency('wayland-client') +wayland_server = dependency('wayland-server') +wl_proto_c = [] +wl_proto_h = [] +wl_proto_xml = [ + wayland_protocols_datadir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml' + ] + +foreach prot : wl_proto_xml + wl_proto_h += custom_target( + prot.underscorify() + '-client-header', + input: prot, + output: '@BASENAME@.h', + command: [ wscanner_prog, 'client-header', '@INPUT@', '@OUTPUT@' ]) + + wl_proto_c += custom_target( + prot.underscorify() + '-private-code', + input: prot, + output: '@BASENAME@.c', + command: [ wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@' ]) +endforeach + +# build stuff cc = meson.get_compiler('c') math_dep = cc.find_library('m', required: true) executable('eh', srcfiles, + wl_proto_c + wl_proto_h, dependencies: [ - math_dep + math_dep, + wayland_client, + wayland_server ], include_directories: [ 'lib/log.c/src', diff --git a/src/led.c b/src/led.c index 76b1a61..0a5c0a9 100644 --- a/src/led.c +++ b/src/led.c @@ -23,7 +23,7 @@ struct Data { /* global data accessed by all threads */ static struct Data *data; -pthread_t thread; +static pthread_t thread; /* mutex to lock the thread */ static pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; diff --git a/src/main.c b/src/main.c index d9fa49b..8b9b547 100644 --- a/src/main.c +++ b/src/main.c @@ -14,6 +14,7 @@ #include "log.h" #include "led.h" +#include "wayland.h" #include "acpi.h" #include "utils.h" @@ -86,8 +87,11 @@ on_battery_event(char *percent, bool plugged) } else { led_set_rate(0); } + + wayland_set_idle_lock(false); } else { led_set_rate(0); + wayland_set_idle_lock(true); } free(percent); @@ -109,6 +113,9 @@ main(int argc, char *argv[]) /* create the led thread */ led_create_thread((char *)ledfile); + /* create the wayland thread */ + wayland_create_thread(); + /* run events that need to be run on start to ensure that the current state * inside the program reflects that that is outside the program */ diff --git a/src/wayland.c b/src/wayland.c new file mode 100644 index 0000000..b52ccc3 --- /dev/null +++ b/src/wayland.c @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "idle-inhibit-unstable-v1.h" + +#include "wayland.h" +#include "log.h" + +struct Data { + int idle_lock; + + struct wl_compositor *compositor; + struct wl_display *display; + struct wl_registry *registry; + struct wl_surface *surface; + struct zwp_idle_inhibitor_v1 *inhibitor; + struct zwp_idle_inhibit_manager_v1 *inhibitmanager; +}; + +/* global data accessed by all threads */ +static struct Data *data; +static pthread_t thread; + +/* mutex to lock the thread */ +static pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cv = PTHREAD_COND_INITIALIZER; + +static void +reghandler(void *data, struct wl_registry *registry, uint32_t id, + const char *interface, uint32_t version) +{ + struct Data *d = (struct Data *)data; + if (strcmp(interface, wl_compositor_interface.name) == 0) { + d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); + } else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { + d->inhibitmanager = wl_registry_bind(registry, id, &zwp_idle_inhibit_manager_v1_interface, 1); + } +} + +static const struct wl_registry_listener reglistener = { + .global = reghandler, +}; + +/** + * @brief helper function to set the idle lock + * + * @param value set the state of the idle lock + * @param d data + */ +static inline void +idle_lock_set(int lock, struct Data *d) +{ + if (lock) { + d->inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(d->inhibitmanager, d->surface); + } else if (d->inhibitor) { + zwp_idle_inhibitor_v1_destroy(d->inhibitor); + d->inhibitor = NULL; + } + wl_display_roundtrip(d->display); +} + +/** + * @brief main thread loop + * + * @param args args + */ +static void +*run(void *args) +{ + int err; + struct Data *d = (struct Data *)args; + + for (;;) { + /* wait for the condition to change */ + while (true) { + if ((err = pthread_cond_wait(&cv, &mp)) != 0) { + log_fatal("pthread_cond_wait: %s", strerror(err)); + pthread_exit(0); + } else { + break; + } + } + + /* exit if we're told to */ + if (d->idle_lock == -1) { + log_debug("received a null idle lock, exiting"); + pthread_exit(0); + } + + idle_lock_set(d->idle_lock, d); + } + + pthread_exit(0); +} + +/** + * @brief run before exiting the program + * this is for cleaning up all the messes we've made + * + * @param signal signal + */ +static void +pre_exit(int signal) +{ + idle_lock_set(false, data); + wayland_set_idle_lock(-1); + pthread_cond_destroy(&cv); + pthread_mutex_destroy(&mp); + + zwp_idle_inhibit_manager_v1_destroy(data->inhibitmanager); + wl_registry_destroy(data->registry); + wl_compositor_destroy(data->compositor); + wl_surface_destroy(data->surface); + /* TODO: this causes a segfault + wl_display_destroy(data->display); + */ + + /* make sure to cleanup the allocated data */ + free(data); + + exit(0); +} + +int +wayland_create_thread(void) +{ + int err; + struct sigaction act; + static bool running; + + /* make sure the wayland thread cannot be created twice */ + if (running) { + log_warn("wayland thread has already been created"); + return 1; + } + + /* setup data */ + data = calloc(1, sizeof(struct Data)); + + /* connect to wayland */ + data->display = wl_display_connect(NULL); + if (!data->display) { + log_fatal("failed to connect to wayland display"); + return -1; + } + + data->registry = wl_display_get_registry(data->display); + if (wl_registry_add_listener(data->registry, ®listener, data) < 0) { + log_fatal("failed to connect to the wayland registry"); + return -1; + } + + /* roundtrip so that the registry listener collects all the data */ + wl_display_roundtrip(data->display); + + if (!data->compositor) { + log_fatal("wayland needs a compositor to run"); + return -1; + } + if (!data->inhibitmanager) { + log_fatal("compositor doesn't support the idle inhibit protocol"); + return -1; + } + + data->surface = wl_compositor_create_surface(data->compositor); + if (!data->surface) { + log_fatal("wayland couldn't create a surface"); + return -1; + } + + data->idle_lock = false; + data->inhibitor = NULL; + + /* zero the array, and setup sig handler and exit handler */ + memset(&act, 0, sizeof(act)); + act.sa_handler = &pre_exit; + if (sigaction(SIGINT, &act, NULL) < 0) { + log_fatal("failed to create sigaction: %s, memory is gonna leak", + strerror(errno)); + } + + /* attempt to create the thread */ + if ((err = pthread_create(&thread, NULL, run, data)) != 0) { + log_fatal("failed to create wayland thread: pthread_create: %s", strerror(err)); + return -1; + } + + /* detach the thread to make cleanup nice and easy */ + if ((err = pthread_detach(thread)) != 0) { + log_fatal("failed to create wayland thread: pthread_detach: %s", strerror(err)); + return -1; + } + + log_info("started wayland thread"); + running = true; + + return 0; +} + +void +wayland_set_idle_lock(int lock) +{ + int err; + static bool locked = false; + + /* there's no reason to set the lock if it's already the same */ + if (lock == data->idle_lock) { + return; + } + + /* lock the thread, update, and unlock it */ + if (!locked) { + if ((err = pthread_mutex_lock(&mp)) != 0) { + log_fatal("failed to lock the mutex: %s", strerror(errno)); + } + } + + /* set the idle lock */ + data->idle_lock = lock; + log_debug("%s wayland idle lock", lock ? "enabled" : "disabled"); + + pthread_cond_signal(&cv); + if (!locked) { + if ((err = pthread_mutex_unlock(&mp)) != 0) { + log_fatal("failed to lock the mutex: %s", strerror(errno)); + } + } + + /* make sure we don't lock the thread in an infinite loop */ + locked = (lock != 0); +}