diff --git a/.build.yml b/.build.yml index ecef9bd..4c827a8 100644 --- a/.build.yml +++ b/.build.yml @@ -3,6 +3,7 @@ packages: - meson - wayland - scdoc + - libvarlink sources: - https://github.com/emersion/kanshi tasks: diff --git a/README.md b/README.md index 18d9ccf..a4f1f01 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Dependencies: * wayland-client * scdoc (optional, for man pages) +* libvarlink (optional, for remote control functionality) ```sh meson build diff --git a/event-loop.c b/event-loop.c index 7c60d34..40a1a94 100644 --- a/event-loop.c +++ b/event-loop.c @@ -10,6 +10,10 @@ #include "kanshi.h" +#if KANSHI_HAS_VARLINK +#include +#endif + static int set_pipe_flags(int fd) { int flags = fcntl(fd, F_GETFL); if (flags == -1) { @@ -45,6 +49,9 @@ static void signal_handler(int signum) { enum readfds_type { FD_WAYLAND, FD_SIGNAL, +#if KANSHI_HAS_VARLINK + FD_VARLINK, +#endif FD_COUNT, }; @@ -74,6 +81,10 @@ int kanshi_main_loop(struct kanshi_state *state) { readfds[FD_WAYLAND].events = POLLIN; readfds[FD_SIGNAL].fd = signal_pipefds[0]; readfds[FD_SIGNAL].events = POLLIN; +#if KANSHI_HAS_VARLINK + readfds[FD_VARLINK].fd = varlink_service_get_fd(state->service); + readfds[FD_VARLINK].events = POLLIN; +#endif while (state->running) { while (wl_display_prepare_read(state->display) != 0) { @@ -107,6 +118,17 @@ int kanshi_main_loop(struct kanshi_state *state) { return EXIT_FAILURE; } +#if KANSHI_HAS_VARLINK + if (readfds[FD_VARLINK].revents & POLLIN) { + long result = varlink_service_process_events(state->service); + if (result != 0) { + fprintf(stderr, "varlink_service_process_events failed: %s\n", + varlink_error_string(-result)); + return EXIT_FAILURE; + } + } +#endif + if (readfds[FD_SIGNAL].revents & POLLIN) { for (;;) { int signum; diff --git a/include/ipc.h b/include/ipc.h new file mode 100644 index 0000000..e6c8528 --- /dev/null +++ b/include/ipc.h @@ -0,0 +1,13 @@ +#ifndef KANSHI_IPC_H +#define KANSHI_IPC_H + +#include + +#include "kanshi.h" + +int kanshi_init_ipc(struct kanshi_state *state); +void kanshi_free_ipc(struct kanshi_state *state); + +int get_ipc_address(char *address, size_t size); + +#endif diff --git a/include/kanshi.h b/include/kanshi.h index c8a7df0..22b8f47 100644 --- a/include/kanshi.h +++ b/include/kanshi.h @@ -43,6 +43,9 @@ struct kanshi_state { bool running; struct wl_display *display; struct zwlr_output_manager_v1 *output_manager; +#if KANSHI_HAS_VARLINK + struct VarlinkService *service; +#endif struct kanshi_config *config; const char *config_arg; diff --git a/ipc-addr.c b/ipc-addr.c new file mode 100644 index 0000000..6e7b0a7 --- /dev/null +++ b/ipc-addr.c @@ -0,0 +1,21 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include + +#include "ipc.h" + +int get_ipc_address(char *address, size_t size) { + const char *wayland_display = getenv("WAYLAND_DISPLAY"); + const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); + if (!wayland_display || !wayland_display[0]) { + fprintf(stderr, "WAYLAND_DISPLAY is not set\n"); + return -1; + } + if (!xdg_runtime_dir || !xdg_runtime_dir[0]) { + fprintf(stderr, "XDG_RUNTIME_DIR is not set\n"); + return -1; + } + + return snprintf(address, size, "unix:%s/fr.emersion.kanshi.%s", + xdg_runtime_dir, wayland_display); +} diff --git a/ipc.c b/ipc.c new file mode 100644 index 0000000..a695295 --- /dev/null +++ b/ipc.c @@ -0,0 +1,69 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include + +#include "kanshi.h" +#include "ipc.h" + +static void reload_config_done(void *data, struct wl_callback *callback, + uint32_t serial) { + VarlinkCall *call = data; + varlink_call_reply(call, NULL, 0); + wl_callback_destroy(callback); +} + +static struct wl_callback_listener reload_config_listener = { + .done = reload_config_done +}; + +static long handle_reload(VarlinkService *service, VarlinkCall *call, + VarlinkObject *parameters, uint64_t flags, void *userdata) { + struct kanshi_state *state = userdata; + kanshi_reload_config(state); + // this only ensures that the server has received the configuration request, + // the server is free to wait an arbitrary amount of time before applying the configuration + // TODO: use the wlr-output-management event instead + struct wl_callback *callback = wl_display_sync(state->display); + wl_callback_add_listener(callback, &reload_config_listener, call); + return 0; +} + +int kanshi_init_ipc(struct kanshi_state *state) { + VarlinkService *service; + char address[PATH_MAX]; + if (get_ipc_address(address, sizeof(address)) < 0) { + return -1; + } + if (varlink_service_new(&service, + "emersion", "kanshi", KANSHI_VERSION, "https://wayland.emersion.fr/kanshi/", + address, -1) < 0) { + fprintf(stderr, "Couldn't start kanshi varlink service at %s.\n" + "Is the kanshi daemon already running?\n", address); + return -1; + } + + const char *interface = "interface fr.emersion.kanshi\n" + "method Reload() -> ()"; + + long result = varlink_service_add_interface(service, interface, + "Reload", handle_reload, state, + NULL); + if (result != 0) { + fprintf(stderr, "varlink_service_add_interface failed: %s\n", + varlink_error_string(-result)); + varlink_service_free(service); + return -1; + } + + state->service = service; + + return 0; +} + +void kanshi_free_ipc(struct kanshi_state *state) { + if (state->service) { + varlink_service_free(state->service); + state->service = NULL; + } +} diff --git a/main.c b/main.c index 58b281c..db34789 100644 --- a/main.c +++ b/main.c @@ -15,6 +15,7 @@ #include "config.h" #include "kanshi.h" #include "parser.h" +#include "ipc.h" #include "wlr-output-management-unstable-v1-client-protocol.h" #define HEADS_MAX 64 @@ -577,6 +578,12 @@ int main(int argc, char *argv[]) { .config_arg = config_arg, }; int ret = EXIT_SUCCESS; +#if KANSHI_HAS_VARLINK + if (kanshi_init_ipc(&state) != 0) { + ret = EXIT_FAILURE; + goto done; + } +#endif wl_list_init(&state.heads); struct wl_registry *registry = wl_display_get_registry(display); @@ -594,6 +601,9 @@ int main(int argc, char *argv[]) { ret = kanshi_main_loop(&state); done: +#if KANSHI_HAS_VARLINK + kanshi_free_ipc(&state); +#endif wl_display_disconnect(display); return ret; diff --git a/meson.build b/meson.build index d562cb6..a321cba 100644 --- a/meson.build +++ b/meson.build @@ -35,18 +35,37 @@ add_project_arguments(cc.get_supported_arguments([ ]), language: 'c') wayland_client = dependency('wayland-client') +varlink = dependency('libvarlink', required: get_option('ipc')) + +add_project_arguments([ + '-DKANSHI_VERSION="@0@"'.format(meson.project_version()), + '-DKANSHI_HAS_VARLINK=@0@'.format(varlink.found().to_int()), +], language: 'c') subdir('protocol') +kanshi_deps = [ + wayland_client, + client_protos, +] + +kanshi_srcs = [ + 'event-loop.c', + 'main.c', + 'parser.c', + 'ipc-addr.c', +] + +if varlink.found() + kanshi_deps += varlink + kanshi_srcs += ['ipc.c', 'ipc-addr.c'] +endif + executable( meson.project_name(), - files( - 'main.c', - 'parser.c', - 'event-loop.c', - ), + kanshi_srcs, include_directories: include_directories('include'), - dependencies: [wayland_client, client_protos], + dependencies: kanshi_deps, install: true, ) diff --git a/meson_options.txt b/meson_options.txt index e40a23d..ae475f4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1 +1,2 @@ option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') +option('ipc', type: 'feature', value: 'auto', description: 'Enable remote control with varlink')