#include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #ifdef __GLIBC__ #include #endif #endif #include #include #include #include"wlr-layer-shell-unstable-v1-protocol.h" #include"xdg-output-unstable-v1-protocol.h" #include"xdg-shell-protocol.h" #include"colour.h" #include"misc.h" #include"output.h" #include"render.h" #include"surface.h" #include"wlclock.h" struct Wlclock_context context = {0}; /** * Intercept user signals so that they don't kill us. */ static void handle_user_signal (int signum) { clocklog(0, "ERROR: wlclock does not handle user signals.\n"); } /** * 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" "│ wlclock has crashed. │\n" "│ │\n" "│ This is 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); #ifdef __linux__ #ifdef __GLIBC__ fputs("Attempting to get backtrace:\n", stderr); /* In some rare cases, getting a backtrace can also cause a segfault. * There is nothing we can or should do about that. All hope is lost at * that point. */ void *buffer[255]; const int calls = backtrace(buffer, sizeof(buffer) / sizeof(void *)); backtrace_symbols_fd(buffer, calls, fileno(stderr)); fputs("\n", stderr); #endif #endif /* Let the default handlers deal with the rest. */ signal(signum, SIG_DFL); kill(getpid(), signum); } /** * Intercept soft kills (like SIGINT and SIGTERM) so we can attempt to clean up * and exit gracefully. */ static void handle_term (int signum) { clocklog(1, "[signal] Soft kill received.\n"); /* If cleanup fails or hangs and causes this signal to be recieved again, * let the default signal handler kill us. */ signal(signum, SIG_DFL); context.loop = false; } /** * Set up signal handlers. */ static void init_signals (void) { signal(SIGSEGV, handle_error); signal(SIGFPE, handle_error); signal(SIGINT, handle_term); signal(SIGTERM, handle_term); signal(SIGUSR1, handle_user_signal); signal(SIGUSR2, handle_user_signal); } static void registry_handle_global (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if (! strcmp(interface, wl_compositor_interface.name)) { clocklog(2, "[main] Get wl_compositor.\n"); context.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); } if (! strcmp(interface, wl_subcompositor_interface.name)) { clocklog(2, "[main] Get wl_subcompositor.\n"); context.subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1); } else if (! strcmp(interface, wl_shm_interface.name)) { clocklog(2, "[main] Get wl_shm.\n"); context.shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (! strcmp(interface, zwlr_layer_shell_v1_interface.name)) { clocklog(2, "[main] Get zwlr_layer_shell_v1.\n"); context.layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (! strcmp(interface, zxdg_output_manager_v1_interface.name)) { clocklog(2, "[main] Get zxdg_output_manager_v1.\n"); context.xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 3); } else if (! strcmp(interface, wl_output_interface.name)) { if (! create_output(registry, name, interface, version)) goto error; } return; error: context.loop = false; context.ret = EXIT_FAILURE; } static void registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { clocklog(1, "[main] Global remove.\n"); destroy_output(get_output_from_global_name(name)); } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove }; /* Helper function for capability support error message. */ static bool capability_test (void *ptr, const char *name) { if ( ptr != NULL ) return true; clocklog(0, "ERROR: Wayland compositor does not support %s.\n", name); return false; } static bool init_wayland (void) { clocklog(1, "[main] Init Wayland.\n"); /* Connect to Wayland server. */ clocklog(2, "[main] Connecting to server.\n"); if ( NULL == (context.display = wl_display_connect(NULL)) ) { clocklog(0, "ERROR: Can not connect to a Wayland server.\n"); return false; } /* Get registry and add listeners. */ clocklog(2, "[main] Get wl_registry.\n"); if ( NULL == (context.registry = wl_display_get_registry(context.display)) ) { clocklog(0, "ERROR: Can not get registry.\n"); return false; } wl_registry_add_listener(context.registry, ®istry_listener, NULL); /* Allow registry listeners to catch up. */ if ( wl_display_roundtrip(context.display) == -1 ) { clocklog(0, "ERROR: Roundtrip failed.\n"); return false; } /* Testing compatibilities. */ if (! capability_test(context.compositor, "wl_compositor")) return false; if (! capability_test(context.shm, "wl_shm")) return false; if (! capability_test(context.layer_shell, "zwlr_layer_shell")) return false; if (! capability_test(context.xdg_output_manager, "xdg_output_manager")) return false; clocklog(2, "[main] Catching up on output configuration.\n"); struct Wlclock_output *op; wl_list_for_each(op, &context.outputs, link) if ( ! op->configured && ! configure_output(op) ) return false; return true; } /* Finish him! */ static void finish_wayland (void) { if ( context.display == NULL ) return; clocklog(1, "[main] Finish Wayland.\n"); destroy_all_outputs(); clocklog(2, "[main] Destroying Wayland objects.\n"); if ( context.layer_shell != NULL ) zwlr_layer_shell_v1_destroy(context.layer_shell); if ( context.compositor != NULL ) wl_compositor_destroy(context.compositor); if ( context.shm != NULL ) wl_shm_destroy(context.shm); if ( context.registry != NULL ) wl_registry_destroy(context.registry); clocklog(2, "[main] Diconnecting from server.\n"); wl_display_disconnect(context.display); } static int count_args (int index, int argc, char *argv[]) { index--; int args = 0; while ( index < argc ) { if ( *argv[index] == '-' ) break; args++; index++; } return args; } static bool handle_command_flags (int argc, char *argv[]) { enum { BACKGROUND_COLOUR, BORDER_COLOUR, BORDER_SIZE, CLOCK_COLOUR, CORNER_RADIUS, EXCLUSIVE_ZONE, HAND_WIDTH, LAYER, MARGIN, MARKING_WIDTH, NAMEPSACE, NO_INPUT, OUTPUT, POSITION, SIZE, SNAP }; static struct option opts[] = { {"help", no_argument, NULL, 'h'}, {"verbose", no_argument, NULL, 'v'}, {"version", no_argument, NULL, 'V'}, {"background-colour", required_argument, NULL, BACKGROUND_COLOUR}, {"border-colour", required_argument, NULL, BORDER_COLOUR}, {"border-size", required_argument, NULL, BORDER_SIZE}, {"clock-colour", required_argument, NULL, CLOCK_COLOUR}, {"corner-radius", required_argument, NULL, CORNER_RADIUS}, {"exclusive-zone", required_argument, NULL, EXCLUSIVE_ZONE}, {"hand-width", required_argument, NULL, HAND_WIDTH}, {"layer", required_argument, NULL, LAYER}, {"margin", required_argument, NULL, MARGIN}, {"marking-width", required_argument, NULL, MARKING_WIDTH}, {"namespace", required_argument, NULL, NAMEPSACE}, {"no-input", no_argument, NULL, NO_INPUT}, {"output", required_argument, NULL, OUTPUT}, {"position", required_argument, NULL, POSITION}, {"size", required_argument, NULL, SIZE}, {"snap", no_argument, NULL, SNAP} }; const char *usage = "Usage: wlclock [options]\n" "\n" " -h, --help Show this help text.\n" " -v, --verbose Increase verbosity of output.\n" " -V, --version Show the version.\n" " --background-colour Background colour.\n" " --border-colour Border colour.\n" " --border-size Size of the border.\n" " --clock-colour Colour of the clock elements.\n" " --corner-radius Corner radii.\n" " --exclusive-zone Exclusive zone of the layer surface.\n" " --hand-width Width of the clock hands.\n" " --layer Layer of the layer surface.\n" " --margin Directional margins.\n" " --marking-width Width of the markings on the clock face.\n" " --namespace Namespace of the layer surface.\n" " --no-input Let inputs surface pass trough the layer surface.\n" " --output The output which the clock will be displayed.\n" " --position Set the position of the context.\n" " --size Size of the context.\n" " --snap Let the hour hand snap to the next position instead of slowly progressing.\n" "\n"; int opt, args; extern int optind; extern char *optarg; while ( (opt = getopt_long(argc, argv, "hvV", opts, &optind)) != -1 ) switch (opt) { case 'h': fputs(usage, stderr); context.ret = EXIT_SUCCESS; return false; case 'v': context.verbosity++; break; case 'V': fputs("wlclock version " VERSION "\n", stderr); context.ret = EXIT_SUCCESS; return false; case POSITION: if (! strcmp(optarg, "center")) context.anchor = 0; else if (! strcmp(optarg, "top")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; else if (! strcmp(optarg, "right")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; else if (! strcmp(optarg, "bottom")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; else if (! strcmp(optarg, "left")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; else if (! strcmp(optarg, "top-left")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; else if (! strcmp(optarg, "top-right")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; else if (! strcmp(optarg, "bottom-left")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; else if (! strcmp(optarg, "bottom-right")) context.anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; else { clocklog(0, "ERROR: Unrecognized position \"%s\".\n" "INFO: Possible positisions are 'center', " "'top', 'right', 'bottom', 'left', " "'top-right', 'top-left', 'bottom-right', 'bottom-left'.\n", optarg); return false; } break; break; case BACKGROUND_COLOUR: if (! colour_from_string(&context.background_colour, optarg)) return false; break; case BORDER_COLOUR: if (! colour_from_string(&context.border_colour, optarg)) return false; break; case BORDER_SIZE: args = count_args(optind, argc, argv); if ( args == 1 ) context.border_top = context.border_right = context.border_bottom = context.border_left = atoi(optarg); else if ( args == 4 ) { context.border_top = atoi(argv[optind-1]); context.border_right = atoi(argv[optind]); context.border_bottom = atoi(argv[optind+1]); context.border_left = atoi(argv[optind+2]); optind += 3; /* Tell getopt() to skip three argv fields. */ } else { clocklog(0, "ERROR: Border configuration " "requires one or four arguments.\n"); return false; } if ( context.border_top < 0 || context.border_right < 0 || context.border_bottom < 0 || context.border_left < 0 ) { clocklog(0, "ERROR: Borders may not be smaller than zero.\n"); return false; } break; case CLOCK_COLOUR: if (! colour_from_string(&context.clock_colour, optarg)) return false; break; case MARKING_WIDTH: context.marking_width = atoi(optarg); if ( context.marking_width < 0 ) { clocklog(0, "ERROR: Marking width may not be smaller than zero.\n"); return false; } break; case HAND_WIDTH: context.hand_width = atoi(optarg); if ( context.hand_width < 0 ) { clocklog(0, "ERROR: Hand width may not be smaller than zero.\n"); return false; } break; case EXCLUSIVE_ZONE: if (is_boolean_true(optarg)) context.exclusive_zone = 1; else if (is_boolean_false(optarg)) context.exclusive_zone = 0; else if (! strcmp(optarg, "stationary")) context.exclusive_zone = -1; else { clocklog(0, "ERROR: Unrecognized exclusive zone option \"%s\".\n" "INFO: Possible options are 'true', " "'false' and 'stationary'.\n", optarg); return false; } break; case LAYER: if (! strcmp(optarg, "overlay")) context.layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; else if (! strcmp(optarg, "top")) context.layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; else if (! strcmp(optarg, "bottom")) context.layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; else if (! strcmp(optarg, "background")) context.layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; else { clocklog(0, "ERROR: Unrecognized layer \"%s\".\n" "INFO: Possible layers are 'overlay', " "'top', 'bottom', and 'background'.\n", optarg); return false; } break; case MARGIN: args = count_args(optind, argc, argv); if ( args == 1 ) context.margin_top = context.margin_right = context.margin_bottom = context.margin_left = atoi(optarg); else if ( args == 4 ) { context.margin_top = atoi(argv[optind-1]); context.margin_right = atoi(argv[optind]); context.margin_bottom = atoi(argv[optind+1]); context.margin_left = atoi(argv[optind+2]); optind += 3; /* Tell getopt() to skip three argv fields. */ } else { clocklog(0, "ERROR: Margin configuration " "requires one or four arguments.\n"); return false; } if ( context.margin_top < 0 || context.margin_right < 0 || context.margin_bottom < 0 || context.margin_left < 0 ) { clocklog(0, "ERROR: Margins may not be smaller than zero.\n"); return false; } break; case NAMEPSACE: set_string(&context.namespace, optarg); break; case NO_INPUT: context.input = false; break; case SNAP: context.snap = true; break; case OUTPUT: if ( ! strcmp("all", optarg) || ! strcmp("*", optarg) ) free_if_set(context.output); else set_string(&context.output, optarg); break; case CORNER_RADIUS: args = count_args(optind, argc, argv); if ( args == 1 ) context.radius_top_left = context.radius_top_right = context.radius_bottom_right = context.radius_bottom_left = atoi(optarg); else if ( args == 4 ) { context.radius_top_left = atoi(argv[optind-1]); context.radius_top_right = atoi(argv[optind]); context.radius_bottom_right = atoi(argv[optind+1]); context.radius_bottom_left = atoi(argv[optind+2]); optind += 3; /* Tell getopt() to skip three argv fields. */ } else { clocklog(0, "ERROR: Radius configuration " "requires one or four arguments.\n"); return false; } if ( context.radius_top_left < 0 || context.radius_top_right < 0 || context.radius_bottom_right < 0 || context.radius_bottom_left < 0 ) { clocklog(0, "ERROR: Radii may not be smaller than zero.\n"); return false; } break; case SIZE: context.dimensions.center_size = atoi(optarg); if ( context.dimensions.center_size <= 10 ) { clocklog(0, "ERROR: Unreasonably small size \"%d\".\n", context.dimensions.center_size); return false; } break; default: return false; } return true; } /* Timeout until next minute. */ static time_t get_timeout (void) { time_t now = time(NULL); return ((now / 60 * 60 ) + 60 - now) * 1000; } static void clock_run () { clocklog(1, "[main] Starting loop.\n"); context.ret = EXIT_SUCCESS; struct pollfd fds[1] = {0}; size_t wayland_fd = 0; fds[wayland_fd].events = POLLIN; if ( -1 == (fds[wayland_fd].fd = wl_display_get_fd(context.display)) ) { clocklog(0, "ERROR: Unable to open Wayland display fd.\n"); context.ret = EXIT_FAILURE; goto exit; } while (context.loop) { /* Flush Wayland events. */ errno = 0; do { if ( wl_display_flush(context.display) == 1 && errno != EAGAIN ) { clocklog(0, "ERROR: wl_display_flush: %s\n", strerror(errno)); break; } } while ( errno == EAGAIN ); int ret = poll(fds, 1, get_timeout()); if ( ret == 0 ) /* Timeout -> update clock hands. */ { clocklog(1, "[surface] Updating all hands.\n"); struct Wlclock_output *op, *tmp; wl_list_for_each_safe(op, tmp, &context.outputs, link) if ( op->surface != NULL ) { render_hands_frame(op->surface); wl_surface_commit(op->surface->hands_surface); wl_surface_commit(op->surface->background_surface); } continue; } else if ( ret < 0 ) { if ( errno == EINTR ) clocklog(1, "[loop] Interrupted by signal.\n"); else clocklog(0, "ERROR: poll: %s\n", strerror(errno)); continue; } /* Wayland events */ if ( fds[wayland_fd].revents & POLLIN && wl_display_dispatch(context.display) == -1 ) { clocklog(0, "ERROR: wl_display_flush: %s\n", strerror(errno)); context.ret = EXIT_FAILURE; break; } if ( fds[wayland_fd].revents & POLLOUT && wl_display_flush(context.display) == -1 ) { clocklog(0, "ERROR: wl_display_flush: %s\n", strerror(errno)); context.ret = EXIT_FAILURE; break; } } exit: if ( fds[wayland_fd].fd != -1 ) close(fds[wayland_fd].fd); return; } int main (int argc, char *argv[]) { init_signals(); context.ret = EXIT_FAILURE; context.loop = true; context.verbosity = 0; context.dimensions.center_size = 165; /* About the size of xclock, at least on my machine. */ context.exclusive_zone = -1; context.input = true; context.snap = false; context.layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; context.anchor = 0; /* Center */ context.border_bottom = context.border_top = context.border_left = context.border_right = 1; context.radius_bottom_left = context.radius_bottom_right = context.radius_top_left = context.radius_top_right = 0; context.margin_bottom = context.margin_top = context.margin_left = context.margin_right = 0; context.marking_width = 1; context.hand_width = 0; wl_list_init(&context.outputs); set_string(&context.namespace, "wlclock"); colour_from_string(&context.background_colour, "#FFFFFF"); colour_from_string(&context.border_colour, "#000000"); colour_from_string(&context.clock_colour, "#000000"); if (! handle_command_flags(argc, argv)) goto exit; context.dimensions.w = context.dimensions.center_size + context.border_left + context.border_right; context.dimensions.h = context.dimensions.center_size + context.border_top + context.border_bottom; context.dimensions.center_x = context.border_left; context.dimensions.center_y = context.border_top; clocklog(1, "[main] wlclock: version=%s\n" "[main] Default dimensions: size=%d cx=%d cy=%d w=%d h=%d\n", VERSION, context.dimensions.center_size, context.dimensions.center_x, context.dimensions.center_y, context.dimensions.w, context.dimensions.h); if (! init_wayland()) goto exit; clock_run(); exit: finish_wayland(); free_if_set(context.output); free_if_set(context.namespace); clocklog(1, "[main] Bye!\n"); return context.ret; }