#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #ifdef __GLIBC__ #include #endif #endif #define MIN(A, B) (A < B ? A : B) #include "wlr-layer-shell-unstable-v1.h" #include "ext-idle-notify-v1.h" const char usage[] = "Usage: wayneko [options...]\n" " -h, --help\n" " --background-colour 0xRRGGBB[AA]\n" " --outline-colour 0xRRGGBB[AA]\n" " --type plain|striped|random\n" " --wall none|left|right|both\n" " --idle-sleep seconds\n" " --sleep-phase seconds-seconds\n" " --awake-phase seconds-seconds\n" " --currently awake|asleep|random\n" " --position float\n" " --layer background|bottom|top|overlay\n" " --output name\n" " --survive-close\n" "\n"; pixman_color_t bg_colour; pixman_color_t border_colour; /* Note: Atlas width must be divisable by 4. */ #include "neko-bitmap.xbm" const int neko_bitmap_stride = neko_bitmap_width / 8; const uint8_t 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 Type { PLAIN = 0, STRIPED = 2, }; 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, NEKO_WALL_RIGHT_1, NEKO_WALL_RIGHT_2, NEKO_WALL_LEFT_1, NEKO_WALL_LEFT_2, }; // lsb is right, next bit is left enum Wall { WALL_NONE = 0, WALL_RIGHT, WALL_LEFT, WALL_BOTH }; const uint16_t animation_timeout = 200; /* milliseconds */ size_t animation_ticks_until_next_frame = 10; enum Neko current_neko = NEKO_STARE; enum Type type = PLAIN; bool follow_pointer = false; // TODO: implement this again bool recreate_surface_on_close = false; enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; float start_position = 0.5; enum Wall wall_behaviour = WALL_BOTH; struct ext_idle_notifier_v1 *idle_notifier = NULL; uint32_t neko_idle_timeout_ms = 180000; /* 3 minutes. */ enum Phase { PHASE_AWAKE, PHASE_SLEEP }; typedef enum Phase Phase; /* wayneko will wake up at startup */ Phase current_phase = PHASE_SLEEP; size_t ticks_until_phase_change = 0; uint32_t phase_sleep_min = 450; uint32_t phase_sleep_max = 1800; uint32_t phase_awake_min = 120; uint32_t phase_awake_max = 600; bool wakeup_shock = false; /* will force a shock, used when woken up */ char* output_name = NULL; struct wl_output* output = NULL; uint32_t required_hits = 1; /* we at least need the sync */ uint32_t found_hits = 0; struct Seat { struct wl_list link; struct wl_seat *wl_seat; struct wl_pointer *wl_pointer; uint32_t global_name; bool on_surface; uint32_t surface_x; struct ext_idle_notification_v1 *idle_notification; bool currently_idle; }; 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; uint32_t width, height; uint16_t neko_x, prev_neko_x; bool configured; }; struct Surface surface = { 0 }; const uint32_t desired_surface_height = neko_size; const uint8_t neko_x_advance = 15; const uint32_t neko_x_wall_attraction = 400; 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; struct wl_list seats; /* 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. */ const int max_buffer_multiplicity = 3; const int surface_amount = 1; 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" "│ wayneko has crashed :( │\n" "│ │\n" "│ This is likely a bug in the fork │\n" "│ you are running, so feel free to │\n" "│ report your stuff there. │\n" "│ │\n" "│ https://copeberg.org/virt/wayneko │\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; } static void wakeup(int signum); /** * 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, wakeup); } /************ * * * 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; } static void buffer_pool_cull_buffers (struct Buffer *skip) { int to_remove = wl_list_length(&buffer_pool) - (max_buffer_multiplicity * surface_amount); struct Buffer *buffer, *tmp; wl_list_for_each_safe(buffer, tmp, &buffer_pool, link) { if ( buffer == skip ) continue; if ( to_remove == 0 ) break; if (buffer->busy) continue; buffer_finish(buffer); buffer_destroy(buffer); to_remove--; } } /** * 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); if ( wl_list_length(&buffer_pool) > max_buffer_multiplicity * surface_amount ) buffer_pool_cull_buffers(ret); 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); } } /********** * * * Seat * * * **********/ 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 seat_release_pointer (struct Seat *seat) { seat->on_surface = false; if (seat->wl_pointer) { wl_pointer_release(seat->wl_pointer); seat->wl_pointer = NULL; } } static void pointer_handle_enter (void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface, wl_fixed_t x, wl_fixed_t y) { struct Seat *seat = (struct Seat *)data; assert(wl_surface == surface.wl_surface); seat->on_surface = true; seat->surface_x = (uint32_t)wl_fixed_to_int(x); /* Abort current animation frame. */ animation_ticks_until_next_frame = 0; } static void pointer_handle_leave (void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface) { struct Seat *seat = (struct Seat *)data; assert(wl_surface == surface.wl_surface); assert(seat->on_surface == true); seat->on_surface = false; } static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t x, wl_fixed_t y) { struct Seat *seat = (struct Seat *)data; assert(seat->on_surface == true); seat->surface_x = (uint32_t)wl_fixed_to_int(x); } static const struct wl_pointer_listener pointer_listener = { .enter = pointer_handle_enter, .leave = pointer_handle_leave, .motion = pointer_handle_motion, .axis_discrete = noop, .axis = noop, .axis_source = noop, .axis_stop = noop, .button = noop, .frame = noop, }; static void seat_bind_pointer (struct Seat *seat) { seat->wl_pointer = wl_seat_get_pointer(seat->wl_seat); wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); } static void seat_handle_capabilities (void *data, struct wl_seat *wl_seat, uint32_t capabilities) { struct Seat *seat = (struct Seat *)data; if ( capabilities & WL_SEAT_CAPABILITY_POINTER ) seat_bind_pointer(seat); else seat_release_pointer(seat); } static const struct wl_seat_listener seat_listener = { .capabilities = seat_handle_capabilities, .name = noop, }; static void ext_idle_notification_handle_idled (void *data, struct ext_idle_notification_v1 *ext_idle_notification_v1) { (void)ext_idle_notification_v1; struct Seat *seat = (struct Seat *)data; assert(!seat->currently_idle); seat->currently_idle = true; } static void ext_idle_notification_handle_resumed (void *data, struct ext_idle_notification_v1 *ext_idle_notification_v1) { (void)ext_idle_notification_v1; struct Seat *seat = (struct Seat *)data; assert(seat->currently_idle); seat->currently_idle = false; } static const struct ext_idle_notification_v1_listener ext_idle_notification_listener = { .idled = ext_idle_notification_handle_idled, .resumed = ext_idle_notification_handle_resumed, }; static void seat_add_idle (struct Seat *seat) { assert(seat->idle_notification == NULL); assert(!seat->currently_idle); seat->idle_notification = ext_idle_notifier_v1_get_idle_notification( idle_notifier, neko_idle_timeout_ms, seat->wl_seat ); ext_idle_notification_v1_add_listener( seat->idle_notification, &ext_idle_notification_listener, seat ); } static void seat_new (struct wl_seat *wl_seat, uint32_t name) { struct Seat *seat = calloc(1, sizeof(struct Seat)); if ( seat == NULL ) { fprintf(stderr, "ERROR: calloc(): %s\n", strerror(errno)); return; } seat->wl_seat = wl_seat; seat->global_name = name; seat->on_surface = false; /* Create idle_notification if we have the global idle_notifier. Note * that during the initial registry burst seats may be advertised before * the idle protocol global, so this also has to be done on first sync. */ if ( idle_notifier != NULL ) seat_add_idle(seat); wl_seat_set_user_data(seat->wl_seat, seat); wl_list_insert(&seats, &seat->link); wl_seat_add_listener(wl_seat, &seat_listener, seat); } static void seat_destroy (struct Seat *seat) { if ( seat->idle_notification != NULL ) ext_idle_notification_v1_destroy(seat->idle_notification); seat_release_pointer(seat); wl_seat_destroy(seat->wl_seat); wl_list_remove(&seat->link); free(seat); } /*********** * * * Atlas * * * ***********/ 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; } 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; } 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 + (uint16_t)type * 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, (uint16_t)type * neko_size, x, y, neko_size, neko_size ); } static bool animation_can_run_left (bool tight) { return surface.neko_x > (tight ? 0 : neko_x_advance); } static void animation_neko_do_run_left (void) { current_neko = current_neko == NEKO_RUN_LEFT_1 ? NEKO_RUN_LEFT_2 : NEKO_RUN_LEFT_1; animation_ticks_until_next_frame = 0; } static bool animation_can_run_right (bool tight) { return surface.neko_x < surface.width - neko_size - (tight ? 0 : neko_x_advance); } static void animation_neko_do_run_right (void) { current_neko = current_neko == NEKO_RUN_RIGHT_1 ? NEKO_RUN_RIGHT_2 : NEKO_RUN_RIGHT_1; animation_ticks_until_next_frame = 0; } static void animation_neko_advance_left (bool tight) { surface.prev_neko_x = surface.neko_x; if (tight) surface.neko_x = 0; else surface.neko_x -= neko_x_advance; } static void animation_neko_advance_right (bool tight) { surface.prev_neko_x = surface.neko_x; if (tight) surface.neko_x = (uint16_t) surface.width - neko_size; else surface.neko_x += neko_x_advance; } static void animation_neko_do_stare (bool quick) { current_neko = NEKO_STARE; animation_ticks_until_next_frame = quick ? 5 : 10; } static void animation_neko_do_yawn (void) { current_neko = NEKO_YAWN; animation_ticks_until_next_frame = 5; } static void animation_neko_do_think (void) { current_neko = NEKO_THINK; animation_ticks_until_next_frame = 15; } static void animation_neko_do_shock (void) { current_neko = NEKO_SHOCK; animation_ticks_until_next_frame = 3; } static void animation_neko_do_sleep (void) { current_neko = current_neko == NEKO_SLEEP_1 ? NEKO_SLEEP_2 : NEKO_SLEEP_1; animation_ticks_until_next_frame = 10; } static void animation_neko_do_scratch (void) { current_neko = current_neko == NEKO_SCRATCH_1 ? NEKO_SCRATCH_2 : NEKO_SCRATCH_1; animation_ticks_until_next_frame = 0; } static void animation_neko_do_wall_left (void) { current_neko = current_neko == NEKO_WALL_LEFT_1 ? NEKO_WALL_LEFT_2 : NEKO_WALL_LEFT_1; animation_ticks_until_next_frame = 0; } static void animation_neko_do_wall_right (void) { current_neko = current_neko == NEKO_WALL_RIGHT_1 ? NEKO_WALL_RIGHT_2 : NEKO_WALL_RIGHT_1; animation_ticks_until_next_frame = 0; } static bool animtation_neko_wants_sleep (void) { /* Neko likes to sleep at night. */ const long now = time(NULL); struct tm tm = *localtime(&now); return tm.tm_hour >= 23 || tm.tm_hour <= 6; } /** Returns true if new frame is needed. */ static bool animation_next_state_sleep(void) { switch (current_neko) { case NEKO_SLEEP_1: case NEKO_SLEEP_2: case NEKO_YAWN: animation_neko_do_sleep(); return true; default: animation_neko_do_yawn(); return true; } } /** Returns true if new frame is needed. */ static bool animation_next_state_with_hotspot (uint32_t x) { if ( x < surface.neko_x ) /* Cursor left of neko. */ { switch (current_neko) { case NEKO_SHOCK: case NEKO_RUN_LEFT_1: case NEKO_RUN_LEFT_2: if (!animation_can_run_left(false)) { animation_neko_do_stare(true); return true; } animation_neko_advance_left(false); animation_neko_do_run_left(); return true; default: animation_neko_do_shock(); return true; } } else if ( x > surface.neko_x + neko_size ) /* Cursor right of neko. */ { switch (current_neko) { case NEKO_SHOCK: case NEKO_RUN_RIGHT_1: case NEKO_RUN_RIGHT_2: if (!animation_can_run_right(false)) { animation_neko_do_stare(true); return true; } animation_neko_advance_right(false); animation_neko_do_run_right(); return true; default: animation_neko_do_shock(); return true; } } else /* Cursor on neko. */ { switch (current_neko) { case NEKO_SLEEP_1: case NEKO_SLEEP_2: case NEKO_YAWN: if (animtation_neko_wants_sleep()) animation_neko_do_sleep(); else animation_neko_do_stare(false); return true; case NEKO_STARE: if (animtation_neko_wants_sleep()) { animation_neko_do_yawn(); return true; } else { animation_neko_do_stare(false); return false; } default: animation_neko_do_stare(false); return true; } } } /** Returns true if new frame is needed. */ static bool animation_next_state_awake (void) { /* neko was just suddenly woken up */ if (wakeup_shock) { animation_neko_do_shock(); wakeup_shock = false; return true; } switch (current_neko) { case NEKO_STARE: switch (rand() % 24) { case 0: animation_neko_do_scratch(); break; case 1: case 2: animation_neko_do_yawn(); break; case 3: animation_neko_do_think(); break; case 4: if (!animation_can_run_left(false)) return false; animation_neko_advance_left(false); animation_neko_do_run_left(); break; case 5: if (!animation_can_run_right(false)) return false; animation_neko_advance_right(false); animation_neko_do_run_right(); break; case 6: case 7: case 8: case 9: if (surface.neko_x == 0 && (wall_behaviour & WALL_LEFT)) animation_neko_do_wall_left(); else if (surface.neko_x + neko_size == surface.width && (wall_behaviour & WALL_RIGHT)) animation_neko_do_wall_right(); else return false; break; default: return false; } return true; case NEKO_RUN_RIGHT_1: case NEKO_RUN_RIGHT_2: bool attracted_right = surface.width - surface.neko_x + neko_size < neko_x_wall_attraction; if (!animation_can_run_right(false) && animation_can_run_right(true) && rand() % 8 != 0) { animation_neko_do_run_right(); animation_neko_advance_right(true); } else if ( animation_can_run_right(false) && rand() % (attracted_right ? 8 : 4) != 0 ) { animation_neko_do_run_right(); animation_neko_advance_right(false); } else animation_neko_do_stare(false); return true; case NEKO_RUN_LEFT_1: case NEKO_RUN_LEFT_2: bool attracted_left = surface.neko_x < neko_x_wall_attraction; if (!animation_can_run_left(false) && animation_can_run_left(true) && rand() % 8 != 0) { animation_neko_do_run_left(); animation_neko_advance_left(true); } else if ( animation_can_run_left(false) && rand() % (attracted_left ? 8 : 4) != 0 ) { animation_neko_do_run_left(); animation_neko_advance_left(false); } else animation_neko_do_stare(false); return true; case NEKO_SLEEP_1: case NEKO_SLEEP_2: if ( rand() % 2 == 0 ) animation_neko_do_shock(); else animation_neko_do_yawn(); return true; case NEKO_SCRATCH_1: case NEKO_SCRATCH_2: if ( rand() % 4 == 0 ) animation_neko_do_stare(false); else animation_neko_do_scratch(); return true; case NEKO_WALL_LEFT_1: case NEKO_WALL_LEFT_2: if ( rand() % 12 == 0 ) animation_neko_do_stare(false); else animation_neko_do_wall_left(); return true; case NEKO_WALL_RIGHT_1: case NEKO_WALL_RIGHT_2: if ( rand() % 12 == 0 ) animation_neko_do_stare(false); else animation_neko_do_wall_right(); return true; case NEKO_THINK: if ( rand() % 2 == 0 ) animation_neko_do_stare(false); else animation_neko_do_shock(); return true; case NEKO_YAWN: case NEKO_SHOCK: animation_neko_do_stare(true); return true; } assert(false); /* unreachable. */ return false; } /** called once per tick, checks for a phase change, and changes the phase if required */ static void check_phase(void) { if (ticks_until_phase_change > 0) ticks_until_phase_change--; else { // change phase uint32_t min = 0, max = 0; if (current_phase == PHASE_AWAKE) { current_phase = PHASE_SLEEP; min = phase_sleep_min; max = phase_sleep_max; } else { current_phase = PHASE_AWAKE; min = phase_awake_min; max = phase_awake_max; } uint32_t phase_change_seconds = min + (((uint32_t) rand()) % (max - min)); ticks_until_phase_change = phase_change_seconds * 1000 / animation_timeout; } } /** called as a signal handler to wake up neko */ static void wakeup(int signum) { // already awake if (current_phase == PHASE_AWAKE) return; animation_ticks_until_next_frame = 0; ticks_until_phase_change = 0; // will trigger a phase change to awake wakeup_shock = true; } /** Returns true if new frame is needed. */ static bool animation_next_state (void) { check_phase(); if ( animation_ticks_until_next_frame > 0 ) { animation_ticks_until_next_frame--; return false; } bool idle = false; struct Seat *seat; wl_list_for_each(seat, &seats, link) { if (seat->currently_idle) idle = true; } if (false) { wl_list_for_each(seat, &seats, link) { if (seat->on_surface) return animation_next_state_with_hotspot(seat->surface_x); } } Phase phase = current_phase; if (idle) phase = PHASE_SLEEP; if (phase == PHASE_SLEEP) return animation_next_state_sleep(); else return animation_next_state_awake(); } /************* * * * Surface * * * *************/ static void surface_next_frame (void) { if (!surface.configured) return; assert(surface.wl_surface != NULL); struct Buffer *buffer = buffer_pool_next_buffer(surface.width, surface.height); if ( buffer == NULL ) return; // we have to make the region a bit bigger because we are double buffering // just clearing the last position is not enough pixman_image_fill_rectangles( PIXMAN_OP_CLEAR, buffer->pixman_image, &bg_colour, 1, &(pixman_rectangle16_t){ surface.prev_neko_x >= neko_size ? (int16_t) surface.prev_neko_x - neko_size : (int16_t) 0, (int16_t)0, (uint16_t)neko_size * 3, (uint16_t)neko_size, } ); atlas_composite_neko(buffer, current_neko, surface.neko_x, 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, MIN(surface.neko_x, surface.prev_neko_x), 0, neko_size + neko_x_advance, neko_size ); 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); surface.layer_surface = NULL; } if ( surface.wl_surface != NULL ) { wl_surface_destroy(surface.wl_surface ); surface.wl_surface = NULL; } surface.configured = false; } 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.width = width; surface.height = height; /* Center neko on first configure. */ if (!surface.configured) { surface.neko_x = (uint16_t)((float) (width - neko_size) * start_position); surface.prev_neko_x = surface.neko_x; } surface.configured = true; surface_next_frame(); } static const struct wl_callback_listener sync_callback_listener; static void layer_surface_handle_closed (void *data, struct zwlr_layer_surface_v1 *layer_surface) { (void)data; (void)layer_surface; surface_destroy(); if (recreate_surface_on_close) { sync_callback = wl_display_sync(wl_display); wl_callback_add_listener(sync_callback, &sync_callback_listener, NULL); } else loop = false; } 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, output, layer, "wayneko" ); zwlr_layer_surface_v1_add_listener( surface.layer_surface, &layer_surface_listener, NULL ); zwlr_layer_surface_v1_set_size( surface.layer_surface, 0, desired_surface_height ); zwlr_layer_surface_v1_set_anchor( surface.layer_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT ); if (! follow_pointer) { 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); } static void attempt_surface_creation (void) { // waits until we have seen all outputs if (required_hits <= ++found_hits && !surface.configured) surface_create(); } /********** * * * Main * * * **********/ static void handle_output_name(void *data, struct wl_output *wl_output, const char *name) { if (strcmp(name, output_name) == 0) { output = wl_output; } attempt_surface_creation(); } static const struct wl_output_listener output_listener = { .name = handle_output_name, .geometry = noop, .mode = noop, .done = noop, .scale = noop, .description = noop }; 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); else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 ) idle_notifier = wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1); else if ( strcmp(interface, wl_seat_interface.name) == 0 ) { struct wl_seat *wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 7); seat_new(wl_seat, name); } else if ( strcmp(interface, wl_output_interface.name) == 0 && output_name != NULL) { struct wl_output* output = wl_registry_bind(registry, name, &wl_output_interface, version); required_hits++; // we want to wait for this monitor to be checked before surface creation wl_output_add_listener(output, &output_listener, NULL); } } static void registry_handle_global_remove (void *data, struct wl_registry *registry, uint32_t name) { struct Seat *seat = seat_from_global_name(name); if ( seat != NULL ) seat_destroy(seat); } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; 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; } /* During the initial registry burst, seats may be advertised before * the global idle objects. So we need to go over all seats again and * add the idle_notification. */ if ( idle_notifier != NULL ) { struct Seat *seat; wl_list_for_each(seat, &seats, link) { if ( seat->idle_notification == NULL ) seat_add_idle(seat); } } attempt_surface_creation(); } 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; } } static bool colour_from_hex (pixman_color_t *colour, const char *hex) { if ( strlen(hex) != strlen("0xRRGGBBAA") && strlen(hex) != strlen("0xRRGGBB") ) { fprintf(stderr, "ERROR: Invalid colour: %s\n", hex); return false; } 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 char *get_argument (int argc, char *argv[], int *i) { if ( argc == (*i) + 1 ) { fprintf(stderr, "ERROR: Flag '%s' requires a parameter.\n", argv[(*i)]); return NULL; } (*i)++; return argv[(*i)]; } static bool colour_from_flag (pixman_color_t *colour, int argc, char *argv[], int *i) { const char *hex = get_argument(argc, argv, i); if ( hex == NULL ) return false; if (!colour_from_hex(colour, hex)) return false; return true; } int main (int argc, char *argv[]) { init_signals(); colour_from_hex(&bg_colour, "0xFFFFFF"); colour_from_hex(&border_colour, "0x000000"); srand((unsigned int)time(0)); for (int i = 1; i < argc; i++) { if ( strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-?") == 0 ) { fputs(usage, stderr); return EXIT_SUCCESS; } else if ( strcmp(argv[i], "--survive-close") == 0 ) recreate_surface_on_close = true; else if ( strcmp(argv[i], "--background-colour") == 0 ) { if (!colour_from_flag(&bg_colour, argc, argv, &i)) return EXIT_FAILURE; } else if ( strcmp(argv[i], "--outline-colour") == 0 ) { if (!colour_from_flag(&border_colour, argc, argv, &i)) return EXIT_FAILURE; } else if ( strcmp(argv[i], "--type") == 0 ) { const char *t = get_argument(argc, argv, &i); if ( t == NULL ) return EXIT_FAILURE; if ( strcmp(t, "plain") == 0 ) type = PLAIN; else if ( strcmp(t, "striped") == 0 ) type = STRIPED; else if ( strcmp(t, "random") == 0 ) type = rand() % 2 == 0 ? PLAIN : STRIPED; else { fprintf(stderr, "ERROR: Unknown argument '%s' for flag '--type'.\n", t); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--wall") == 0 ) { const char *t = get_argument(argc, argv, &i); if ( t == NULL ) return EXIT_FAILURE; if (strcmp(t, "none") == 0) wall_behaviour = WALL_NONE; else if (strcmp(t, "right") == 0) wall_behaviour = WALL_RIGHT; else if (strcmp(t, "left") == 0) wall_behaviour = WALL_LEFT; else if (strcmp(t, "both") == 0) wall_behaviour = WALL_BOTH; else { fprintf(stderr, "ERROR: Unknown argument '%s' for flag '--wall'.\n", t); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--follow-pointer") == 0 ) { fprintf(stderr, "ERROR: Functionality for flag '--follow-pointer' is not yet implemented in this fork.\n"); return EXIT_FAILURE; const char *t = get_argument(argc, argv, &i); if ( t == NULL ) return EXIT_FAILURE; if ( strcmp(t, "yes") == 0 || strcmp(t, "on") == 0 || strcmp(t, "true") == 0 ) follow_pointer = true; else if ( strcmp(t, "no") == 0 || strcmp(t, "off") == 0 || strcmp(t, "false") == 0 ) follow_pointer = false; else { fprintf(stderr, "ERROR: Unknown argument '%s' for flag '--follow-pointer'.\n", t); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--layer") == 0 ) { const char *a = get_argument(argc, argv, &i); if ( strcmp(a, "background") == 0 ) layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; else if ( strcmp(a, "bottom") == 0 ) layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; else if ( strcmp(a, "top") == 0 ) layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; else if ( strcmp(a, "overlay") == 0 ) layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; } else if ( strcmp(argv[i], "--position") == 0 ) { char *a = get_argument(argc, argv, &i); if (strcmp(a, "random") == 0) start_position = (float) (rand() % 1000) / (float) 1000; else { char* result = a; float f = strtof(a, &result); if (result == a) { fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--position'.\n", a); return EXIT_FAILURE; } else if (f > 1.0 || f < 0.0) { fprintf(stderr, "ERROR: Number passed for flag '--position' must be between 0 and 1.\n"); return EXIT_FAILURE; } else { start_position = f; } } } else if ( strcmp(argv[i], "--idle-sleep") == 0 ) { const char *a = get_argument(argc, argv, &i); int timeout = atoi(a); if (timeout > 0) neko_idle_timeout_ms = (uint32_t) timeout * 1000; else { fprintf(stderr, "ERROR: Invalid argument '%s' for flag '--idle-sleep'.\n", a); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--awake-phase") == 0 ) { char* a = get_argument(argc, argv, &i); char* b = a; if (a == NULL) return EXIT_FAILURE; while (*b != '-' && *b != 0) b++; if (*b == 0) { fprintf(stderr, "ERROR: Values in '%s' for flag '--awake-phase' are must be split by a dash.\n", a); return EXIT_FAILURE; } *b++ = 0; int min = atoi(a); int max = atoi(b); if (min != 0 && max != 0 && max != min) { phase_awake_min = (uint32_t) abs(min); phase_awake_max = (uint32_t) abs(max); } else { fprintf(stderr, "ERROR: Invalid numbers in argument '%s-%s' for flag '--awake-phase'.\n", a, b); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--sleep-phase") == 0 ) { char* a = get_argument(argc, argv, &i); char* b = a; if (a == NULL) return EXIT_FAILURE; while (*b != '-' && *b != 0) b++; if (*b == 0) { fprintf(stderr, "ERROR: Values in '%s' for flag '--sleep-phase' are must be split by a dash.\n", a); return EXIT_FAILURE; } *b++ = 0; int min = atoi(a); int max = atoi(b); if (min != 0 && max != 0 && max != min) { phase_sleep_min = (uint32_t) abs(min); phase_sleep_max = (uint32_t) abs(max); } else { fprintf(stderr, "ERROR: Invalid numbers in argument '%s-%s' for flag '--sleep-phase'.\n", a, b); return EXIT_FAILURE; } } else if ( strcmp(argv[i], "--currently") == 0 ) { const char *t = get_argument(argc, argv, &i); if ( t == NULL ) return EXIT_FAILURE; /* yes, the phases are swapped because the first thing that will happen is a phase change */ if ( strcmp(t, "awake") == 0 ) current_phase = PHASE_SLEEP; else if ( strcmp(t, "asleep") == 0 ) current_phase = PHASE_AWAKE; else if ( strcmp(t, "random") == 0 ) current_phase = rand() % 2 == 0 ? PHASE_SLEEP : PHASE_AWAKE; else { fprintf(stderr, "ERROR: Unknown argument '%s' for flag '--currently'.\n", t); return EXIT_FAILURE; } if (current_phase == PHASE_AWAKE) current_neko = NEKO_SLEEP_1; } else if ( strcmp(argv[i], "--output") == 0 ) { char *t = get_argument(argc, argv, &i); if ( t == NULL ) return EXIT_FAILURE; output_name = t; } else { fprintf(stderr, "ERROR: Unknown option: %s\n", argv[i]); return EXIT_FAILURE; } } wl_list_init(&seats); wl_list_init(&buffer_pool); 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(); 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 is NOT a signal */ 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 ( idle_notifier != NULL ) ext_idle_notifier_v1_destroy(idle_notifier); 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; }