#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #ifdef __GLIBC__ #include #endif #endif #include "wlr-layer-shell-unstable-v1.h" /* Note: Atlas width must be divisable by 4. */ #include "neko-bitmap.xbm" const int neko_bitmap_stride = neko_bitmap_width / 8; const int neko_size = 32; pixman_image_t *neko_atlas = NULL; pixman_image_t *neko_atlas_bg_fill = NULL; pixman_image_t *neko_atlas_border_fill = NULL; enum Neko { NEKO_SLEEP_1 = 0, NEKO_SLEEP_2, NEKO_YAWN, NEKO_SHOCK, NEKO_THINK, NEKO_STARE, NEKO_SCRATCH_1, NEKO_SCRATCH_2, NEKO_RUN_RIGHT_1, NEKO_RUN_RIGHT_2, NEKO_RUN_LEFT_1, NEKO_RUN_LEFT_2, }; const uint16_t animation_timeout = 200; size_t animation_ticks_until_next_frame = 10; enum Neko current_neko = NEKO_STARE; struct Buffer { struct wl_list link; uint32_t width; uint32_t height; uint32_t stride; size_t size; void *mmap; struct wl_buffer *wl_buffer; pixman_image_t *pixman_image; bool busy; }; struct Surface { struct wl_surface *wl_surface; struct zwlr_layer_surface_v1 *layer_surface; bool configured; }; struct Surface surface = { 0 }; const uint32_t surface_width = 100; const uint32_t surface_height = neko_size; int ret = EXIT_SUCCESS; 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 zwlr_layer_shell_v1 *layer_shell = NULL; struct timespec last_tick; /* The amount of buffers per surface we consider the reasonable upper limit. * Some compositors sometimes tripple-buffer, so three seems to be ok. * Note that we can absolutely work with higher buffer numbers if needed, * however we consider that to be an anomaly and therefore do not want to * keep all those extra buffers around if we can avoid it, as to not have * unecessary memory overhead. */ int max_buffer_multiplicity = 3; struct wl_list buffer_pool; /* No-Op function plugged into Wayland listeners we don't care about. */ static void noop () {} /************* * * * Signals * * * *************/ /** * 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" "│ │\n" "│ wayneko 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); /* 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); } /** * Intercept soft kills (like SIGINT and SIGTERM) so we can attempt to clean up * and exit gracefully. */ static void handle_term (int signum) { fputs("[wayneko] 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; } /** * 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); } /************ * * * Buffer * * * ************/ static void buffer_randomize_string (char *str, size_t len) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); long r = ts.tv_nsec; for (size_t i = 0; i < len; i++, str++) { /* Use two byte from the current nano-second to pseudo-randomly * increase the ASCII character 'A' into another character, * which will then subsitute the character at *str. */ *str = (char)('A' + (r&15) + (r&16)); r >>= 5; } } /* Tries to create a shared memory object and returns its file descriptor if * successful. */ static bool buffer_get_shm_fd (int *fd, size_t size) { char name[] = "/wayneko-RANDOM"; char *rp = name + strlen("/wayneko-"); /* Pointer to random part. */ size_t rl = strlen("RANDOM"); /* Length of random part. */ /* Try a few times to get a unique name. */ for (int tries = 100; tries > 0; tries--) { /* Make the name pseudo-random to not conflict with other * running instances. */ buffer_randomize_string(rp, rl); /* Try to create a shared memory object. Returns -1 if the * memory object already exists. */ *fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); /* If a shared memory object was created, set its size and * return its file descriptor. */ if ( *fd >= 0 ) { shm_unlink(name); if ( ftruncate(*fd, (off_t)size) < 0 ) { fprintf(stderr, "ERROR: ftruncate: %s.\n", strerror(errno)); close(*fd); return false; } return true; } /* The EEXIST error means that the name is not unique and we * must try again. */ if ( errno != EEXIST ) { fprintf(stderr, "ERROR: shm_open: %s.\n", strerror(errno)); return false; } } return false; } static void buffer_handle_release (void *data, struct wl_buffer *wl_buffer) { struct Buffer *buffer = (struct Buffer *)data; buffer->busy = false; } static const struct wl_buffer_listener buffer_listener = { .release = buffer_handle_release, }; static void buffer_finish (struct Buffer *buffer) { if ( buffer->wl_buffer != NULL ) wl_buffer_destroy(buffer->wl_buffer); if ( buffer->pixman_image != NULL ) pixman_image_unref(buffer->pixman_image); if ( buffer->mmap != NULL ) munmap(buffer->mmap, buffer->size); } static void buffer_destroy (struct Buffer *buffer) { wl_list_remove(&buffer->link); free(buffer); } #define PIXMAN_STRIDE(A, B) (((PIXMAN_FORMAT_BPP(A) * B + 7) / 8 + 4 - 1) & -4) static bool buffer_init (struct Buffer *buffer, uint32_t width, uint32_t height) { assert(!buffer->busy); bool ret = true; int fd = -1; struct wl_shm_pool *shm_pool = NULL; buffer->width = width; buffer->height = height; buffer->stride = (uint32_t)PIXMAN_STRIDE(PIXMAN_a8r8g8b8, (int32_t)width); buffer->size = (size_t)(buffer->stride * height); if ( buffer->size == 0 ) { ret = false; goto cleanup; } if (! buffer_get_shm_fd(&fd, buffer->size)) { ret = false; goto cleanup; } buffer->mmap = mmap(NULL, buffer->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if ( buffer->mmap == MAP_FAILED ) { fprintf(stderr, "ERROR: mmap: %s.\n", strerror(errno)); ret = false; goto cleanup; } shm_pool = wl_shm_create_pool(wl_shm, fd, (int32_t)buffer->size); if ( shm_pool == NULL ) { ret = false; goto cleanup; } buffer->wl_buffer = wl_shm_pool_create_buffer(shm_pool, 0, (int32_t)width, (int32_t)height, (int32_t)buffer->stride, WL_SHM_FORMAT_ARGB8888); if ( buffer->wl_buffer == NULL ) { ret = false; goto cleanup; } wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); buffer->pixman_image = pixman_image_create_bits_no_clear(PIXMAN_a8r8g8b8, (int32_t)width, (int32_t)height, buffer->mmap, (int32_t)buffer->stride); if ( buffer->pixman_image == NULL ) { ret = false; goto cleanup; } cleanup: if ( shm_pool != NULL ) wl_shm_pool_destroy(shm_pool); if ( fd != -1 ) close(fd); if (! ret) buffer_finish(buffer); return ret; } #undef PIXMAN_STRIDE static struct Buffer *buffer_pool_find_suitable_buffer (uint32_t width, uint32_t height) { struct Buffer *first_unbusy_buffer = NULL; struct Buffer *current = NULL; wl_list_for_each(current, &buffer_pool, link) { if (current->busy) continue; first_unbusy_buffer = current; if ( current->width != width ) continue; if ( current->height != height ) continue; return current; } /* No buffer has matching dimensions, however we do have an unbusy * buffer which we can just re-init. */ if ( first_unbusy_buffer != NULL ) { buffer_finish(first_unbusy_buffer); if (!buffer_init(first_unbusy_buffer, width, height)) { buffer_destroy(first_unbusy_buffer); return NULL; } return first_unbusy_buffer; } return NULL; } static struct Buffer *buffer_pool_new_buffer (uint32_t width, uint32_t height) { struct Buffer *buffer = calloc(1, sizeof(struct Buffer)); if ( buffer == NULL ) { fprintf(stderr, "ERROR: calloc(): %s\n", strerror(errno)); return NULL; } memset(buffer, 0, sizeof(struct Buffer)); wl_list_insert(&buffer_pool, &buffer->link); if (!buffer_init(buffer, width, height)) { buffer_destroy(buffer); return NULL; } return buffer; } /** * Get a buffer of the specified dimenisons. If possible an idle buffer is * reused, otherweise a new one is created. */ static struct Buffer *buffer_pool_next_buffer (uint32_t width, uint32_t height) { struct Buffer *ret = buffer_pool_find_suitable_buffer(width, height); if ( ret == NULL ) ret = buffer_pool_new_buffer(width, height); // XXX cull buffers return ret; } static void buffer_pool_destroy_all_buffers (void) { struct Buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &buffer_pool, link) { buffer_finish(buffer); buffer_destroy(buffer); } } /*********** * * * Atlas * * * ***********/ static bool colour_from_hex (pixman_color_t *colour, const char *hex) { uint16_t r = 0, g = 0, b = 0, a = 255; if ( 4 != sscanf(hex, "0x%02hx%02hx%02hx%02hx", &r, &g, &b, &a) && 3 != sscanf(hex, "0x%02hx%02hx%02hx", &r, &g, &b) ) { fprintf(stderr, "ERROR: Invalid colour: %s\n", hex); return false; } colour->alpha = (uint16_t)(((double)a / 255.0) * 65535.0); colour->red = (uint16_t)((((double)r / 255.0) * 65535.0) * colour->alpha / 0xffff); colour->green = (uint16_t)((((double)g / 255.0) * 65535.0) * colour->alpha / 0xffff); colour->blue = (uint16_t)((((double)b / 255.0) * 65535.0) * colour->alpha / 0xffff); return true; } static void atlas_deinit (void) { if ( neko_atlas != NULL ) { pixman_image_unref(neko_atlas); neko_atlas = NULL; } if ( neko_atlas_bg_fill != NULL ) { pixman_image_unref(neko_atlas_bg_fill); neko_atlas_bg_fill = NULL; } if ( neko_atlas_border_fill != NULL ) { pixman_image_unref(neko_atlas_border_fill); neko_atlas_border_fill = NULL; } } static bool atlas_init (void) { assert(neko_atlas == NULL); neko_atlas = pixman_image_create_bits_no_clear( PIXMAN_a1, neko_bitmap_width, neko_bitmap_height, (uint32_t *)(&neko_bitmap_bits), neko_bitmap_stride ); if ( neko_atlas == NULL ) { fprintf(stderr, "ERROR: Failed to create texture atlas.\n"); atlas_deinit(); return false; } pixman_color_t bg_colour; colour_from_hex(&bg_colour, "0xFFFFFF"); neko_atlas_bg_fill = pixman_image_create_solid_fill(&bg_colour); if ( neko_atlas_bg_fill == NULL ) { fprintf(stderr, "ERROR: Failed to create solid fill.\n"); atlas_deinit(); return false; } pixman_color_t border_colour; colour_from_hex(&border_colour, "0x000000"); neko_atlas_border_fill = pixman_image_create_solid_fill(&border_colour); if ( neko_atlas_border_fill == NULL ) { fprintf(stderr, "ERROR: Failed to create solid fill.\n"); atlas_deinit(); return false; } return true; } static void atlas_composite_neko (struct Buffer *buffer, enum Neko neko_type, uint16_t x, uint16_t y) { pixman_image_composite32( PIXMAN_OP_SRC, neko_atlas_bg_fill, /* Source. */ neko_atlas, /* Mask. */ buffer->pixman_image, /* Destination. */ 0, /* Source x. */ 0, /* Source y. */ (uint16_t)neko_type * neko_size, /* Mask x. */ neko_size, /* Mask y. */ x, /* Destination x. */ y, /* Destination y. */ neko_size, /* Source width. */ neko_size /* Source height. */ ); pixman_image_composite32( PIXMAN_OP_OVER, neko_atlas_border_fill, neko_atlas, buffer->pixman_image, 0, 0, (uint16_t)neko_type * neko_size, 0, x, y, neko_size, neko_size ); } /** Returns true if new frame is needed. */ static bool animation_next_state (void) { if ( animation_ticks_until_next_frame > 0 ) { animation_ticks_until_next_frame--; return false; } switch (current_neko) { case NEKO_STARE: switch (rand() % 20) { case 0: current_neko = NEKO_SCRATCH_1; return true; case 1: current_neko = NEKO_SLEEP_1; animation_ticks_until_next_frame = 10; return true; case 2: current_neko = NEKO_SHOCK; animation_ticks_until_next_frame = 5; return true; case 3: current_neko = NEKO_YAWN; animation_ticks_until_next_frame = 5; return true; case 4: current_neko = NEKO_THINK; animation_ticks_until_next_frame = 10; return true; default: return false; } break; case NEKO_SLEEP_1: case NEKO_SLEEP_2: case NEKO_SCRATCH_1: case NEKO_SCRATCH_2: switch (rand() % 4) { case 0: current_neko = NEKO_STARE; animation_ticks_until_next_frame = 5; return true; default: switch (current_neko) { case NEKO_SLEEP_1: current_neko = NEKO_SLEEP_2; break; case NEKO_SLEEP_2: current_neko = NEKO_SLEEP_1; break; case NEKO_SCRATCH_1: current_neko = NEKO_SCRATCH_2; break; case NEKO_SCRATCH_2: current_neko = NEKO_SCRATCH_1; break; default: /* unreachable. */ break; } if ( current_neko == NEKO_SLEEP_1 || current_neko == NEKO_SLEEP_2 ) animation_ticks_until_next_frame = 10; return true; } break; case NEKO_SHOCK: case NEKO_YAWN: case NEKO_THINK: default: current_neko = NEKO_STARE; animation_ticks_until_next_frame = 5; return true; } return false; } /************* * * * Surface * * * *************/ static void surface_next_frame (void) { if (!surface.configured) return; struct Buffer *buffer = buffer_pool_next_buffer(surface_width, surface_height); if ( buffer == NULL ) return; atlas_composite_neko(buffer, current_neko, 0, 0); wl_surface_set_buffer_scale(surface.wl_surface, 1); wl_surface_attach(surface.wl_surface, buffer->wl_buffer, 0, 0); wl_surface_damage_buffer(surface.wl_surface, 0, 0, INT32_MAX, INT32_MAX); buffer->busy = true; wl_surface_commit(surface.wl_surface); } static void surface_destroy (void) { if ( surface.layer_surface != NULL ) zwlr_layer_surface_v1_destroy(surface.layer_surface); if ( surface.wl_surface != NULL ) wl_surface_destroy(surface.wl_surface ); } static void layer_surface_handle_configure (void *data, struct zwlr_layer_surface_v1 *layer_surface, uint32_t serial, uint32_t width, uint32_t height) { (void)data; (void)layer_surface; zwlr_layer_surface_v1_ack_configure(surface.layer_surface, serial); surface.configured = true; if ( width != surface_width || height != surface_height ) fprintf(stderr, "ERROR: Bad dimensions in configure: %d %d\n", width, height); surface_next_frame(); } static void layer_surface_handle_closed (void *data, struct zwlr_layer_surface_v1 *layer_surface) { (void)data; (void)layer_surface; surface_destroy(); } const struct zwlr_layer_surface_v1_listener layer_surface_listener = { .configure = layer_surface_handle_configure, .closed = layer_surface_handle_closed }; static void surface_create (void) { assert(!surface.configured); surface.wl_surface = wl_compositor_create_surface(wl_compositor); surface.layer_surface = zwlr_layer_shell_v1_get_layer_surface( layer_shell, surface.wl_surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM, "wayneko" ); zwlr_layer_surface_v1_add_listener( surface.layer_surface, &layer_surface_listener, NULL ); zwlr_layer_surface_v1_set_size( surface.layer_surface, surface_width, surface_height ); zwlr_layer_surface_v1_set_anchor( surface.layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ); /* Empty input region. */ struct wl_region *region = wl_compositor_create_region(wl_compositor); wl_surface_set_input_region(surface.wl_surface, region); wl_region_destroy(region); wl_surface_commit(surface.wl_surface); } /********** * * * Main * * * **********/ static void registry_handle_global (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { if ( strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0 ) layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); else if ( strcmp(interface, wl_compositor_interface.name) == 0 ) wl_compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); else if ( strcmp(interface, wl_shm_interface.name) == 0 ) wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, /* We do not bind interfaces that - realistically - will ever disappear. */ .global_remove = noop, }; 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"; 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; ret = EXIT_FAILURE; return; } surface_create(); } static const struct wl_callback_listener sync_callback_listener = { .done = sync_handle_done, }; static void timespec_diff (struct timespec *a, struct timespec *b, struct timespec *result) { result->tv_sec = a->tv_sec - b->tv_sec; result->tv_nsec = a->tv_nsec - b->tv_nsec; if ( result->tv_nsec < 0 ) { result->tv_sec--; result->tv_nsec += 1000000000L; } } int main (void) { init_signals(); wl_list_init(&buffer_pool); srand((unsigned int)time(0)); // TODO command line args if (!atlas_init()) return EXIT_FAILURE; /* 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_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); struct pollfd pollfds[] = { { .fd = wl_display_get_fd(wl_display), .events = POLLIN, }, }; while (loop) { int current_timeout = animation_timeout; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if (surface.configured) { const uint32_t nsec_delay = animation_timeout * 1000000; const uint32_t epsilon = 1000000; struct timespec time_since_last_tick; timespec_diff(&now, &last_tick, &time_since_last_tick); if ( time_since_last_tick.tv_sec > 0 || time_since_last_tick.tv_nsec >= nsec_delay - epsilon ) { if (animation_next_state()) surface_next_frame(); fprintf(stderr, "tick!\n"); clock_gettime(CLOCK_MONOTONIC, &last_tick); } else { const int _current_timeout = (int)(nsec_delay - time_since_last_tick.tv_nsec) / 1000000; if ( current_timeout == -1 || current_timeout > _current_timeout ) current_timeout = _current_timeout; } } /* Flush pending Wayland events/requests. */ while ( wl_display_prepare_read(wl_display) != 0 ) { if ( wl_display_dispatch_pending(wl_display) != 0 ) { fprintf(stderr, "ERROR: wl_display_dispatch_pending(): %s\n", strerror(errno)); ret = EXIT_FAILURE; goto exit_main_loop; } } while (true) { /* Returns the amount of bytes flushed. */ const int flush_ret = wl_display_flush(wl_display); if (flush_ret == -1) /* Error. */ { if ( errno == EAGAIN ) continue; fprintf(stderr, "ERROR: wl_display_flush(): %s\n", strerror(errno)); ret = EXIT_FAILURE; goto exit_main_loop; } else if (flush_ret == 0) /* Done flushing. */ break; } if ( poll(pollfds, 1, current_timeout) < 0 ) { if ( errno == EINTR ) /* Interrupt: Signal received. */ continue; fprintf(stderr, "ERROR: poll(): %s.\n", strerror(errno)); ret = EXIT_FAILURE; break; } if ( wl_display_read_events(wl_display) == -1 ) { fprintf(stderr, "ERROR: wl_display_read_events(): %s.\n", strerror(errno)); break; } if ( wl_display_dispatch_pending(wl_display) == -1 ) { fprintf(stderr, "ERROR: wl_display_dispatch_pending(): %s.\n", strerror(errno)); break; } } /* Since C doesn't have first-class support for exiting the outer-loop * from inside a nested loop, we unfortunately need to use a jump label. */ exit_main_loop: close(pollfds[0].fd); surface_destroy(); buffer_pool_destroy_all_buffers(); 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 ( sync_callback != NULL ) wl_callback_destroy(sync_callback); if ( wl_registry != NULL ) wl_registry_destroy(wl_registry); wl_display_disconnect(wl_display); atlas_deinit(); return ret; }