/****** * * 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 #include #include #include #include #include #include #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) emacs\n" "(f) firefox\n" "(s) screenshot(grim)\n" "(l) waylock\n" "(p) pcmanfm\n" "(a) toggle HDMI-A-1\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, ®istry_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; }