/******
*
* 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, ®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;
}