summaryrefslogtreecommitdiffstats
path: root/rwm-mode-popup.c
diff options
context:
space:
mode:
Diffstat (limited to 'rwm-mode-popup.c')
-rw-r--r--rwm-mode-popup.c450
1 files changed, 450 insertions, 0 deletions
diff --git a/rwm-mode-popup.c b/rwm-mode-popup.c
new file mode 100644
index 0000000..1fcebf5
--- /dev/null
+++ b/rwm-mode-popup.c
@@ -0,0 +1,450 @@
+/******
+ *
+ * Code heavily inspired from https://git.sr.ht/~leon_plickat/river-tag-overlay
+ * with help from the good people of irc.libera.chat#river
+ *
+ */
+
+#include <wayland-client.h>
+#include <poll.h>
+#include <gtk/gtk.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <unistd.h>
+
+#include "river-status-unstable-v1.h"
+#include "wlr-layer-shell-unstable-v1.h"
+
+GtkWidget *window = NULL;
+
+bool loop = true;
+
+struct wl_display *wl_display = NULL;
+struct wl_registry *wl_registry = NULL;
+struct wl_callback *sync_callback = NULL;
+struct wl_compositor *wl_compositor = NULL;
+struct wl_shm *wl_shm = NULL;
+struct zriver_status_manager_v1 *river_status_manager = NULL;
+struct zwlr_layer_shell_v1 *layer_shell = NULL;
+
+static void noop( ) { }
+
+struct Output {
+ struct wl_list link;
+ struct wl_output *wl_output;
+ uint32_t global_name;
+ struct zriver_output_status_v1 *river_status;
+ bool configured;
+};
+
+struct Seat {
+ struct wl_list link;
+ struct wl_seat *wl_seat;
+ uint32_t global_name;
+ struct zriver_seat_status_v1 *river_status;
+ bool configured;
+};
+
+char active_mode[1024] = "";
+
+struct wl_list outputs;
+struct wl_list seats;
+
+static void
+handle_term(int signum)
+{
+ fputs("[river-popup] Terminated by signal.\n", stderr);
+
+ /* If cleanup fails or hangs and causes this signal to be recieved again,
+ * let the default signal handler kill us.
+ */
+ signal(signum, SIG_DFL);
+
+ loop = false;
+}
+
+static void
+init_signals(void)
+{
+ signal(SIGINT, handle_term);
+ signal(SIGTERM, handle_term);
+}
+
+void
+create_gtk_window(const char *mode)
+{
+ printf("Spawning gtk popup\n");
+
+ window = gtk_window_new();
+ gtk_window_set_title(GTK_WINDOW(window), mode);
+ gtk_window_set_default_size(GTK_WINDOW(window), 200, 140);
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+ gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
+
+ GtkWidget *label = gtk_label_new(NULL);
+ gtk_label_set_markup(GTK_LABEL(label),"\n(e) <b>emacs</b>\n"
+ "(f) <b>firefox</b>\n"
+ "(s) <b>screenshot(grim)</b>\n"
+ "(l) <b>waylock</b>\n"
+ "(p) <b>pcmanfm</b>\n"
+ "(a) <b>toggle HDMI-A-1</b>\n");
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
+ gtk_window_set_child(GTK_WINDOW(window), label);
+
+ /* GtkWidget *spinner = gtk_spinner_new(); */
+ /* gtk_window_set_child(GTK_WINDOW(window), spinner); */
+ /* gtk_spinner_start(GTK_SPINNER(spinner)); */
+
+ gtk_window_present(GTK_WINDOW(window));
+}
+
+/************
+ * *
+ * Output *
+ * *
+ ************/
+static void river_output_status_handle_focused_tags(void *data, struct zriver_output_status_v1 *river_status,
+ uint32_t tags)
+{
+ noop();
+}
+
+static void river_output_status_handle_view_tags(void *data, struct zriver_output_status_v1 *river_status,
+ struct wl_array *tags)
+{
+ noop();
+}
+
+static void river_output_status_handle_urgent_tags(void *data, struct zriver_output_status_v1 *river_status,
+ uint32_t tags)
+{
+ noop();
+}
+
+static const struct zriver_output_status_v1_listener river_output_status_listener = {
+ .focused_tags = river_output_status_handle_focused_tags,
+ .view_tags = river_output_status_handle_view_tags,
+ .urgent_tags = river_output_status_handle_urgent_tags,
+};
+
+static void destroy_output(struct Output *output)
+{
+ if ( output->river_status != NULL )
+ zriver_output_status_v1_destroy(output->river_status);
+ wl_output_destroy(output->wl_output);
+ wl_list_remove(&output->link);
+ free(output);
+}
+
+static struct Output *output_from_global_name(uint32_t name)
+{
+ struct Output *output;
+ wl_list_for_each(output, &outputs, link)
+ if ( output->global_name == name )
+ return output;
+ return NULL;
+}
+
+static void configure_output(struct Output *output)
+{
+ output->river_status = zriver_status_manager_v1_get_river_output_status(river_status_manager, output->wl_output);
+ zriver_output_status_v1_add_listener(output->river_status, &river_output_status_listener, output);
+ output->configured = true;
+}
+
+/**********
+ * *
+ * Seat *
+ * *
+ **********/
+static void river_seat_status_handle_focused_output(void *data, struct zriver_seat_status_v1 *seat_status,
+ struct wl_output *wl_output)
+{
+ if (strcmp(active_mode, "lnch") == 0) {
+ gtk_window_destroy(GTK_WINDOW(window));
+ window = NULL;
+ create_gtk_window("lnch");
+ }
+}
+
+static void
+river_seat_status_handle_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *title)
+{
+ noop();
+}
+
+static void
+river_seat_status_handle_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1, const char *name) {
+ strcpy(active_mode, name);
+ if (strcmp(name, "lnch") == 0) {
+ if (!window) {
+ create_gtk_window(name);
+ }
+ } else {
+ if (window) {
+ gtk_window_destroy(GTK_WINDOW(window));
+ window = NULL;
+ }
+ }
+}
+
+static const struct zriver_seat_status_v1_listener river_seat_status_listener = {
+ .focused_output = river_seat_status_handle_focused_output,
+ .unfocused_output = noop,
+ .focused_view = river_seat_status_handle_focused_view,
+ .mode = river_seat_status_handle_mode,
+};
+
+static struct Seat *seat_from_global_name(uint32_t name)
+{
+ struct Seat *seat;
+ wl_list_for_each(seat, &seats, link)
+ if ( seat->global_name == name )
+ return seat;
+ return NULL;
+}
+
+static void destroy_seat(struct Seat *seat)
+{
+ if ( seat->river_status != NULL )
+ zriver_seat_status_v1_destroy(seat->river_status);
+ wl_seat_destroy(seat->wl_seat);
+ wl_list_remove(&seat->link);
+ free(seat);
+}
+
+static void configure_seat(struct Seat *seat)
+{
+ seat->river_status = zriver_status_manager_v1_get_river_seat_status(river_status_manager, seat->wl_seat);
+ zriver_seat_status_v1_add_listener(seat->river_status, &river_seat_status_listener, seat);
+ seat->configured = true;
+}
+
+static char *check_for_interfaces(void)
+{
+ if ( wl_compositor == NULL )
+ return "wl_compositor";
+ if ( wl_shm == NULL )
+ return "wl_shm";
+ if ( layer_shell == NULL )
+ return "wlr_layershell_v1";
+ if ( river_status_manager == NULL )
+ return "river_status_v1";
+ return NULL;
+}
+
+static void
+sync_handle_done(void *data, struct wl_callback *wl_callback, uint32_t other)
+{
+ wl_callback_destroy(wl_callback);
+ sync_callback = NULL;
+
+ const char *missing = check_for_interfaces();
+ if ( missing != NULL ) {
+ fprintf(stderr, "ERROR, Wayland compositor does not support %s.\n", missing);
+ loop = false;
+ return;
+ }
+
+ struct Output *output;
+ wl_list_for_each(output, &outputs, link)
+ if (! output->configured)
+ configure_output(output);
+
+ struct Seat *seat;
+ wl_list_for_each(seat, &seats, link)
+ if (! seat->configured)
+ configure_seat(seat);
+}
+
+static const struct wl_callback_listener sync_callback_listener = {
+ .done = sync_handle_done,
+};
+
+static void
+global_registry_handler(void *data, struct wl_registry *registry, uint32_t id,
+ const char *interface, uint32_t version)
+{
+ if ( strcmp(interface, wl_output_interface.name) == 0 ) {
+ struct Output *output = calloc(1, sizeof(struct Output));
+ if ( output == NULL ) {
+ fprintf(stderr, "ERROR: calloc: %s.\n", strerror(errno));
+ return;
+ }
+
+ output->wl_output = wl_registry_bind(registry, id, &wl_output_interface, 3);
+ output->global_name = id;
+ wl_output_set_user_data(output->wl_output, output);
+ wl_list_insert(&outputs, &output->link);
+
+ if ( river_status_manager != NULL )
+ configure_output(output);
+ }
+ else if ( strcmp(interface, wl_seat_interface.name) == 0 ) {
+ struct Seat *seat = calloc(1, sizeof(struct Seat));
+ if (seat == NULL) {
+ fprintf(stderr, "ERROR: calloc: %s.\n", strerror(errno));
+ return;
+ }
+
+ seat->wl_seat = wl_registry_bind(registry, id, &wl_seat_interface, 7);
+ seat->global_name = id;
+ wl_seat_set_user_data(seat->wl_seat, seat);
+ wl_list_insert(&seats, &seat->link);
+
+ if ( river_status_manager != NULL )
+ configure_seat(seat);
+ }
+ else if ( strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0 ) {
+ layer_shell = wl_registry_bind(registry, id, &zwlr_layer_shell_v1_interface, 1);
+ }
+ else if ( strcmp(interface, zriver_status_manager_v1_interface.name) == 0 ) {
+ river_status_manager = wl_registry_bind(registry, id, &zriver_status_manager_v1_interface, 3);
+ }
+ else if ( strcmp(interface, wl_compositor_interface.name) == 0 ) {
+ wl_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 4);
+ }
+ else if ( strcmp(interface, wl_shm_interface.name) == 0 ) {
+ wl_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);
+ }
+}
+
+static void
+global_registry_remover(void *data, struct wl_registry *registry, uint32_t id)
+{
+ struct Output *output = output_from_global_name(id);
+ if ( output != NULL ) {
+ destroy_output(output);
+ return;
+ }
+
+ struct Seat *seat = seat_from_global_name(id);
+ if ( seat != NULL ) {
+ destroy_seat(seat);
+ return;
+ }
+}
+
+static const struct wl_registry_listener registry_listener = {
+ .global = global_registry_handler,
+ .global_remove = global_registry_remover,
+};
+
+void*
+gui_thread_function(void* arg)
+{
+ while (loop) {
+ if (window)
+ g_main_context_iteration(NULL, TRUE);
+ }
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int status = 0;
+
+ init_signals();
+
+ /* We query the display name here instead of letting wl_display_connect()
+ * figure it out itself, because libwayland (for legacy reasons) falls
+ * back to using "wayland-0" when $WAYLAND_DISPLAY is not set, which is
+ * generally not desirable.
+ */
+ const char *display_name = getenv("WAYLAND_DISPLAY");
+ if ( display_name == NULL ) {
+ fputs("ERROR: WAYLAND_DISPLAY is not set.\n", stderr);
+ return EXIT_FAILURE;
+ }
+
+ wl_display = wl_display_connect(display_name);
+ if ( wl_display == NULL ) {
+ fputs("ERROR: Can not connect to wayland display.\n", stderr);
+ return EXIT_FAILURE;
+ }
+
+ wl_list_init(&outputs);
+ wl_list_init(&seats);
+
+ wl_registry = wl_display_get_registry(wl_display);
+ wl_registry_add_listener(wl_registry, &registry_listener, NULL);
+
+ sync_callback = wl_display_sync(wl_display);
+ wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL);
+
+ gtk_init();
+ g_object_set(gtk_settings_get_default(), "gtk-application-prefer-dark-theme", TRUE, NULL);
+
+ pthread_t gui_thread;
+ if (pthread_create(&gui_thread, NULL, gui_thread_function, NULL)) {
+ fprintf(stderr, "Error creating thread\n");
+ return 1;
+ }
+
+ struct pollfd pollfds[] = {
+ {
+ .fd = wl_display_get_fd(wl_display),
+ .events = POLLIN,
+ },
+ };
+
+ while (loop) {
+ int current_timeout = -1;
+
+ /* Flush pending Wayland events/requests. */
+ int _status = 1;
+ while ( _status > 0 ) {
+ _status = wl_display_dispatch_pending(wl_display);
+ wl_display_flush(wl_display);
+ }
+ if ( _status < 0 ) {
+ fprintf(stderr, "ERROR: wl_display_dispatch_pending: %s\n", strerror(errno));
+ status = EXIT_FAILURE;
+ break;
+ }
+
+ if ( poll(pollfds, 1, current_timeout) < 0 ) {
+ if ( errno == EINTR )
+ continue;
+ fprintf(stderr, "ERROR: poll: %s.\n", strerror(errno));
+ status = EXIT_FAILURE;
+ break;
+ }
+
+ if ( (pollfds[0].revents & POLLIN) && wl_display_dispatch(wl_display) == -1 ) {
+ fprintf(stderr, "ERROR: wl_display_dispatch: %s.\n", strerror(errno));
+ break;
+ }
+ if ( (pollfds[0].revents & POLLOUT) && wl_display_flush(wl_display) == -1 ) {
+ fprintf(stderr, "ERROR: wl_display_flush: %s.\n", strerror(errno));
+ break;
+ }
+ }
+
+ close(pollfds[0].fd);
+
+ pthread_cancel(gui_thread);
+ pthread_join(gui_thread, NULL);
+
+ struct Output *output, *otmp;
+ wl_list_for_each_safe(output, otmp, &outputs, link)
+ destroy_output(output);
+
+ if ( wl_compositor != NULL )
+ wl_compositor_destroy(wl_compositor);
+ if ( wl_shm != NULL )
+ wl_shm_destroy(wl_shm);
+ if ( layer_shell != NULL )
+ zwlr_layer_shell_v1_destroy(layer_shell);
+ if ( river_status_manager != NULL )
+ zriver_status_manager_v1_destroy(river_status_manager);
+ if ( sync_callback != NULL )
+ wl_callback_destroy(sync_callback);
+ if ( wl_registry != NULL )
+ wl_registry_destroy(wl_registry);
+ wl_display_disconnect(wl_display);
+
+ return status;
+}