#include "wayland-client-core.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #ifdef __GLIBC__ #include #endif #endif #include "river-layout-v3.h" #include "ext-idle-notify-v1.h" #include "river-control-unstable-v1.h" #include "river-status-unstable-v1.h" #include "load-script.h" #include "call-exit-handler.h" #include "riverguile.h" #include "output.h" #include "seat.h" struct Context context = { /* Handlers are initially NULL instead of SCM_EOL because I am not sure whether * scm_null_p() is safe to use outside of a guile context and if yes, whether * that is intended or will disappear in the future. Special care must be taken * to never call any scm_* function on these while they are NULL. */ .new_output_handler = NULL, .layout_demand_handler = NULL, .user_command_handler = NULL, .exit_handler = NULL, .loop = true, .ret = EXIT_SUCCESS, }; jmp_buf skip_main_loop; static void registry_handle_global (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if ( strcmp(interface, river_layout_manager_v3_interface.name) == 0 ) { context.layout_manager = wl_registry_bind( registry, name, &river_layout_manager_v3_interface, 2 ); } else if ( strcmp(interface, zriver_control_v1_interface.name) == 0 ) { context.river_control = wl_registry_bind( registry, name, &zriver_control_v1_interface, 1 ); } else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 ) { context.idle_notifier = wl_registry_bind( registry, name, &ext_idle_notifier_v1_interface, 1 ); } else if ( strcmp(interface, zriver_status_manager_v1_interface.name) == 0 ) { context.status_manager = wl_registry_bind( registry, name, &zriver_status_manager_v1_interface, 4 ); } else if ( strcmp(interface, wl_output_interface.name) == 0 ) { struct wl_output *wl_output = wl_registry_bind( registry, name, &wl_output_interface, 1 ); struct Output *output = output_create(wl_output, name); if ( output == NULL ) { wl_output_destroy(wl_output); context.ret = EXIT_FAILURE; context.loop = false; return; } wl_list_insert(&context.outputs, &output->link); } else if ( strcmp(interface, wl_seat_interface.name) == 0 ) { struct wl_seat *wl_seat = wl_registry_bind( registry, name, &wl_seat_interface, 1 ); struct Seat *seat = seat_create(wl_seat, name); if ( seat == NULL ) { wl_seat_destroy(wl_seat); context.ret = EXIT_FAILURE; context.loop = false; return; } wl_list_insert(&context.seats, &seat->link); } } static void registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { struct Output *output, *tmp_o; wl_list_for_each_safe(output, tmp_o, &context.outputs, link) { if ( output->name != name ) continue; wl_list_remove(&output->link); output_destroy(output); return; } struct Seat *seat, *tmp_s; wl_list_for_each_safe(seat, tmp_s, &context.seats, link) { if ( seat->name != name ) continue; wl_list_remove(&seat->link); seat_destroy(seat); return; } } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static void sync_handle_done (void *, struct wl_callback *, uint32_t); static const struct wl_callback_listener sync_callback_listener = { .done = sync_handle_done, }; static void sync_handle_done (void *data, struct wl_callback *wl_callback, uint32_t other_data) { static int i = 0; i++; if ( i > 2 ) { assert(context.mode == ONESHOT); context.loop = false; return; } wl_callback_destroy(wl_callback); context.sync_callback = NULL; if (i == 1) { struct Output *output; wl_list_for_each(output, &context.outputs, link) { output_status_init(output); } struct Seat *seat; wl_list_for_each(seat, &context.seats, link) { seat_status_init(seat); } context.sync_callback = wl_display_sync(context.wl_display); wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL); return; } /* Load the script after connecting to the server and binding interfaces * to allow calling Wayland requests from it. */ assert(context.path != NULL); void *res = scm_with_guile(load_script, (void *)context.path); if ( res != NULL ) { fputs((char *)res, stderr); context.ret = EXIT_FAILURE; context.loop = false; return; } if ( context.layout_demand_handler == NULL && context.user_command_handler != NULL ) { fputs("ERROR: Installing user-command handler without installing a layout-demand handler is not allowed.\n", stderr); fputs("INFO: This error is not fatal, but means riverguile will not provide any layout.\n", stderr); } switch (context.mode) { case ONESHOT: /* Oneshot mode. Sync again so we are sure that all commands * have been send, then exit. */ context.sync_callback = wl_display_sync(context.wl_display); wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL); fputs("INFO: No handlers installed: Riverguile will exit.\n", stderr); break; case CONTINOUS: fputs("INFO: At least one handler installed: Riverguile will run continously.\n", stderr); break; } } static void handle_interrupt (int signum) { fputs("INFO: Killed 💀\n", stderr); context.loop = false; longjmp(skip_main_loop, 1); } /** * Intercept error signals (like SIGSEGV and SIGFPE) so that we can try to * print a fancy error message and a backtracke before letting the system kill us. */ static void handle_error (int signum) { const char *msg = "\n" "┌──────────────────────────────────────────┐\n" "│ │\n" "│ riverguile has crashed. │\n" "│ │\n" "│ This is most likely a bug, so please │\n" "│ report this to the mailing list. │\n" "│ │\n" "│ ~leon_plickat/public-inbox@lists.sr.ht │\n" "│ │\n" "└──────────────────────────────────────────┘\n" "\n"; fputs(msg, stderr); /* Set up the default handlers to deal with the rest. We do this before * attempting to get a backtrace, because sometimes that could also * cause a SEGFAULT and we don't want a funny signal loop to happen. */ signal(signum, SIG_DFL); #ifdef __linux__ #ifdef __GLIBC__ fputs("Attempting to get backtrace:\n", stderr); void *buffer[255]; const int calls = backtrace(buffer, sizeof(buffer) / sizeof(void *)); backtrace_symbols_fd(buffer, calls, fileno(stderr)); fputs("\n", stderr); #endif #endif /* Easiest way of calling the default signal handler. */ kill(getpid(), signum); } static char *formatted_buffer (const char *fmt, ...) { /* Determine length of formatted text. */ va_list args; va_start(args, fmt); /* +1 for NULL. */ unsigned long len = (unsigned long)vsnprintf(NULL, 0, fmt, args) + 1; va_end(args); char *buffer = calloc(1, len); if ( buffer == NULL ) { fprintf(stderr, "ERROR: calloc: %s\n", strerror(errno)); return NULL; } /* Finally write formatted text into buffer. */ va_start(args, fmt); vsnprintf(buffer, len, fmt, args); va_end(args); return buffer; } static char *get_script_path (char *relative_path) { assert(relative_path != NULL); char *path = NULL; if ( relative_path[0] == '/' ) path = formatted_buffer("%s", relative_path); else { /* When scm_primitive_load_path() receives a relative path, it * tries to look it up in the guile module path. So we must * always provide it an absolute path. */ char buffer[4 * 1024]; char *cwd = getcwd(buffer, 4 * 1024); if ( cwd == NULL ) { fprintf(stderr, "ERROR: getcwd: %s\n", strerror(errno)); return NULL; } path = formatted_buffer("%s/%s", cwd, relative_path); } if ( path == NULL ) return NULL; if ( access(path, R_OK) != 0 ) { fprintf(stderr, "ERROR: Script '%s' is not readable.\n", path); free(path); return NULL; } return path; } int main (int argc, char *argv[]) { if ( argc != 2 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "-?") == 0 ) { fprintf(stderr, "USAGE: %s path-to-script\n", argv[0]); return EXIT_FAILURE; } fputs("INFO: Welcome to riverguile!\n", stderr); signal(SIGSEGV, handle_error); signal(SIGFPE, handle_error); signal(SIGINT, handle_interrupt); context.path = get_script_path(argv[1]); if ( context.path == NULL ) { context.ret = EXIT_FAILURE; goto early_exit; } fprintf(stderr, "INFO: Using script: %s\n", context.path); wl_list_init(&context.outputs); wl_list_init(&context.seats); /* 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); context.ret = EXIT_FAILURE; goto early_exit; } context.wl_display = wl_display_connect(display_name); if ( context.wl_display == NULL ) { fputs("ERROR: Can not connect to wayland display.\n", stderr); context.ret = EXIT_FAILURE; goto early_exit; } context.wl_registry = wl_display_get_registry(context.wl_display); wl_registry_add_listener(context.wl_registry, ®istry_listener, NULL); context.sync_callback = wl_display_sync(context.wl_display); wl_callback_add_listener(context.sync_callback, &sync_callback_listener, NULL); if ( setjmp(skip_main_loop) == 0 ) while ( context.loop && wl_display_dispatch(context.wl_display) > 0 ); if ( context.exit_handler != NULL ) { void *res = scm_with_guile(call_exit_handler, NULL); if ( res != NULL ) { fputs((char *)res, stderr); context.ret = EXIT_FAILURE; context.loop = false; } } struct Output *output, *tmp_o; wl_list_for_each_safe(output, tmp_o, &context.outputs, link) { wl_list_remove(&output->link); output_destroy(output); } struct Seat *seat, *tmp_s; wl_list_for_each_safe(seat, tmp_s, &context.seats, link) { wl_list_remove(&seat->link); seat_destroy(seat); } if ( context.idle_notifier != NULL ) ext_idle_notifier_v1_destroy(context.idle_notifier); if ( context.layout_manager != NULL ) river_layout_manager_v3_destroy(context.layout_manager); if ( context.river_control != NULL ) zriver_control_v1_destroy(context.river_control); if ( context.status_manager != NULL ) zriver_status_manager_v1_destroy(context.status_manager); if ( context.sync_callback != NULL ) wl_callback_destroy(context.sync_callback); if ( context.wl_registry != NULL ) wl_registry_destroy(context.wl_registry); wl_display_disconnect(context.wl_display); early_exit: if ( context.path != NULL ) free(context.path); fputs("INFO: Exiting.\n", stderr); return context.ret; }