wayneko/wayneko.c

1578 lines
38 KiB
C
Raw Normal View History

2023-08-13 01:37:13 +02:00
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <pixman.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#ifdef __linux__
#include <features.h>
#ifdef __GLIBC__
#include <execinfo.h>
#endif
#endif
2023-08-13 03:08:56 +02:00
#define MIN(A, B) (A < B ? A : B)
2023-08-13 01:37:13 +02:00
#include "wlr-layer-shell-unstable-v1.h"
#include "ext-idle-notify-v1.h"
2023-08-13 01:37:13 +02:00
const char usage[] =
"Usage: wayneko [options...]\n"
" -h, --help\n"
" --background-colour 0xRRGGBB[AA]\n"
" --outline-colour 0xRRGGBB[AA]\n"
2023-10-19 06:45:19 +02:00
" --type neko|inu|random\n"
2024-02-18 21:18:06 +01:00
" --layer background|bottom|top|overlay\n"
" --follow-pointer true|false\n"
2023-11-17 15:27:56 +01:00
" --survive-close\n"
"\n";
2023-08-13 03:08:56 +02:00
pixman_color_t bg_colour;
pixman_color_t border_colour;
2023-08-13 01:37:13 +02:00
/* Note: Atlas width must be divisable by 4. */
#include "neko-bitmap.xbm"
const int neko_bitmap_stride = neko_bitmap_width / 8;
2023-08-13 03:08:56 +02:00
const uint8_t neko_size = 32;
2023-08-13 01:37:13 +02:00
pixman_image_t *neko_atlas = NULL;
pixman_image_t *neko_atlas_bg_fill = NULL;
pixman_image_t *neko_atlas_border_fill = NULL;
2023-10-19 06:45:19 +02:00
enum Type
{
NEKO = 0,
INU = 2,
};
2023-08-13 01:37:13 +02:00
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;
2023-10-20 02:51:29 +02:00
enum Type type = NEKO;
bool follow_pointer = true;
2023-11-17 15:27:56 +01:00
bool recreate_surface_on_close = false;
2024-02-18 21:18:06 +01:00
enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
2023-08-13 01:37:13 +02:00
struct ext_idle_notifier_v1 *idle_notifier = NULL;
2024-03-24 15:59:07 +01:00
uint32_t neko_idle_timeout_ms = 180000; /* 3 minutes. */
2023-10-10 19:48:54 +02:00
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;
2023-10-10 19:48:54 +02:00
};
2023-08-13 01:37:13 +02:00
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;
2023-08-13 03:08:56 +02:00
uint32_t width, height;
uint16_t neko_x, prev_neko_x;
2023-08-13 01:37:13 +02:00
bool configured;
};
struct Surface surface = { 0 };
2023-08-13 03:08:56 +02:00
const uint32_t desired_surface_height = neko_size;
const uint8_t neko_x_advance = 15;
2023-08-13 01:37:13 +02:00
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;
2023-10-10 19:48:54 +02:00
struct wl_list seats;
2023-08-13 01:37:13 +02:00
/* 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.
*/
2023-08-13 03:28:33 +02:00
const int max_buffer_multiplicity = 3;
const int surface_amount = 1;
2023-08-13 01:37:13 +02:00
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;
}
2023-08-14 01:58:20 +02:00
static void buffer_pool_cull_buffers (struct Buffer *skip)
2023-08-13 03:28:33 +02:00
{
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)
{
2023-08-14 01:58:20 +02:00
if ( buffer == skip )
continue;
if ( to_remove == 0 )
2023-08-13 03:28:33 +02:00
break;
if (buffer->busy)
continue;
buffer_finish(buffer);
buffer_destroy(buffer);
to_remove--;
}
}
2023-08-13 01:37:13 +02:00
/**
* 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);
2023-08-13 03:28:33 +02:00
if ( wl_list_length(&buffer_pool) > max_buffer_multiplicity * surface_amount )
2023-08-14 01:58:20 +02:00
buffer_pool_cull_buffers(ret);
2023-08-13 01:37:13 +02:00
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);
}
}
2023-08-14 01:58:20 +02:00
2023-10-10 19:48:54 +02:00
/**********
* *
* 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
);
}
2023-10-10 19:48:54 +02:00
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);
2023-10-10 19:48:54 +02:00
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);
2023-10-10 19:48:54 +02:00
seat_release_pointer(seat);
wl_seat_destroy(seat->wl_seat);
wl_list_remove(&seat->link);
free(seat);
}
2023-08-13 01:37:13 +02:00
/***********
* *
* 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. */
2023-10-19 06:45:19 +02:00
neko_size + (uint16_t)type * neko_size, /* Mask y. */
2023-08-13 01:37:13 +02:00
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,
2023-10-19 06:45:19 +02:00
(uint16_t)type * neko_size,
2023-08-13 01:37:13 +02:00
x,
y,
neko_size,
neko_size
);
}
2023-08-13 03:08:56 +02:00
static bool animation_can_run_left (void)
{
return surface.neko_x > 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;
}
2023-08-13 03:08:56 +02:00
static bool animation_can_run_right (void)
{
2023-08-14 01:58:20 +02:00
return surface.neko_x < surface.width - neko_size - neko_x_advance;
2023-08-13 03:08:56 +02:00
}
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 (void)
{
surface.prev_neko_x = surface.neko_x;
surface.neko_x -= neko_x_advance;
}
static void animation_neko_advance_right (void)
{
surface.prev_neko_x = surface.neko_x;
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;
}
2023-10-11 03:09:49 +02:00
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_with_idle (void)
{
/* If no one is there (system is idle), neko gets bored and will sleep. */
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;
}
}
2023-08-13 01:37:13 +02:00
/** Returns true if new frame is needed. */
2023-10-10 19:48:54 +02:00
static bool animation_next_state_with_hotspot (uint32_t x)
2023-08-13 01:37:13 +02:00
{
2023-10-11 03:09:49 +02:00
if ( x < surface.neko_x ) /* Cursor left of neko. */
2023-08-13 01:37:13 +02:00
{
2023-10-11 03:09:49 +02:00
switch (current_neko)
{
case NEKO_SHOCK:
case NEKO_RUN_LEFT_1:
case NEKO_RUN_LEFT_2:
2023-10-10 19:48:54 +02:00
if (!animation_can_run_left())
{
animation_neko_do_stare(true);
2023-10-11 03:09:49 +02:00
return true;
2023-10-10 19:48:54 +02:00
}
animation_neko_advance_left();
animation_neko_do_run_left();
return true;
2023-10-11 03:09:49 +02:00
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:
2023-10-10 19:48:54 +02:00
if (!animation_can_run_right())
{
animation_neko_do_stare(true);
2023-10-11 03:09:49 +02:00
return true;
2023-10-10 19:48:54 +02:00
}
animation_neko_advance_right();
animation_neko_do_run_right();
return true;
2023-10-11 03:09:49 +02:00
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;
}
2023-10-10 19:48:54 +02:00
2023-08-13 01:37:13 +02:00
}
2023-10-10 19:48:54 +02:00
}
2023-08-13 01:37:13 +02:00
2023-10-10 19:48:54 +02:00
/** Returns true if new frame is needed. */
static bool animation_next_state_normal (void)
{
2023-10-11 03:09:49 +02:00
/* Sleep at night, but with a small chance to wake up and do something.
* If the neko is already awake, slightly higher chance to stay awake.
*/
const bool neko_is_sleeping = current_neko == NEKO_SLEEP_1 || current_neko == NEKO_SLEEP_2;
if ( animtation_neko_wants_sleep() && (( neko_is_sleeping && rand() % 5 != 0 ) || ( !neko_is_sleeping && rand() % 2 != 0 )) )
2023-10-11 02:36:51 +02:00
{
2023-10-11 03:09:49 +02:00
switch (current_neko)
{
case NEKO_RUN_RIGHT_1:
case NEKO_RUN_RIGHT_2:
case NEKO_RUN_LEFT_1:
case NEKO_RUN_LEFT_2:
animation_neko_do_stare(true);
return true;
case NEKO_SLEEP_1:
case NEKO_SLEEP_2:
animation_neko_do_sleep();
return true;
default:
2024-03-24 18:52:16 +01:00
if (rand() % 3 == 0)
animation_neko_do_yawn();
else
animation_neko_do_stare(false);
2023-10-11 03:09:49 +02:00
return true;
}
2023-10-11 02:36:51 +02:00
}
2023-08-13 01:37:13 +02:00
switch (current_neko)
{
case NEKO_STARE:
switch (rand() % 24)
2023-08-13 01:37:13 +02:00
{
case 0:
animation_neko_do_scratch();
break;
2023-08-13 01:37:13 +02:00
case 1:
animation_neko_do_sleep();
break;
2023-08-13 01:37:13 +02:00
case 2:
animation_neko_do_yawn();
break;
2023-08-13 01:37:13 +02:00
2023-08-13 01:55:30 +02:00
case 3:
animation_neko_do_think();
break;
2023-08-13 01:37:13 +02:00
2023-08-13 03:08:56 +02:00
case 4:
if (!animation_can_run_left())
return false;
animation_neko_advance_left();
animation_neko_do_run_left();
break;
2023-08-13 03:08:56 +02:00
case 5:
if (!animation_can_run_right())
return false;
animation_neko_advance_right();
animation_neko_do_run_right();
break;
2023-08-13 03:08:56 +02:00
2023-08-13 01:37:13 +02:00
default:
return false;
}
return true;
2023-08-13 01:37:13 +02:00
2023-08-13 03:08:56 +02:00
case NEKO_RUN_RIGHT_1:
case NEKO_RUN_RIGHT_2:
if ( animation_can_run_right() && rand() % 4 != 0 )
2023-08-13 03:08:56 +02:00
{
animation_neko_do_run_right();
animation_neko_advance_right();
2023-08-13 03:08:56 +02:00
}
else
animation_neko_do_stare(false);
return true;
2023-08-13 03:08:56 +02:00
case NEKO_RUN_LEFT_1:
case NEKO_RUN_LEFT_2:
if ( animation_can_run_left() && rand() % 4 != 0 )
2023-08-13 03:08:56 +02:00
{
animation_neko_do_run_left();
animation_neko_advance_left();
2023-08-13 03:08:56 +02:00
}
else
animation_neko_do_stare(false);
return true;
case NEKO_SLEEP_1:
case NEKO_SLEEP_2:
if ( rand() % 4 == 0 )
2023-08-13 03:08:56 +02:00
{
if ( rand() % 2 == 0 )
animation_neko_do_shock();
2023-08-13 03:08:56 +02:00
else
animation_neko_do_stare(false);
2023-08-13 03:08:56 +02:00
}
else
animation_neko_do_sleep();
2023-08-13 03:08:56 +02:00
return true;
2023-08-13 01:37:13 +02:00
case NEKO_SCRATCH_1:
case NEKO_SCRATCH_2:
2023-08-13 03:08:56 +02:00
if ( rand() % 4 == 0 )
animation_neko_do_stare(false);
2023-08-13 01:55:30 +02:00
else
animation_neko_do_scratch();
return true;
2023-08-13 01:37:13 +02:00
case NEKO_THINK:
2023-10-10 19:48:54 +02:00
if ( rand() % 2 == 0 )
animation_neko_do_stare(false);
2023-08-13 01:55:30 +02:00
else
animation_neko_do_shock();
return true;
2023-08-13 01:55:30 +02:00
case NEKO_YAWN:
2023-10-10 19:48:54 +02:00
if ( rand() % 2 == 0 )
animation_neko_do_stare(false);
2023-08-13 01:55:30 +02:00
else
animation_neko_do_sleep();
return true;
2023-08-13 01:55:30 +02:00
case NEKO_SHOCK:
animation_neko_do_stare(true);
2023-08-13 01:37:13 +02:00
return true;
}
assert(false); /* unreachable. */
return false;
2023-08-13 01:37:13 +02:00
}
2023-10-10 19:48:54 +02:00
/** 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;
}
struct Seat *seat;
wl_list_for_each(seat, &seats, link)
{
if (seat->currently_idle)
return animation_next_state_with_idle();
}
wl_list_for_each(seat, &seats, link)
2023-10-10 19:48:54 +02:00
{
if (seat->on_surface)
return animation_next_state_with_hotspot(seat->surface_x);
}
return animation_next_state_normal();
}
2023-08-13 01:37:13 +02:00
/*************
* *
* Surface *
* *
*************/
static void surface_next_frame (void)
{
if (!surface.configured)
return;
assert(surface.wl_surface != NULL);
2023-08-13 03:08:56 +02:00
struct Buffer *buffer = buffer_pool_next_buffer(surface.width, surface.height);
2023-08-13 01:37:13 +02:00
if ( buffer == NULL )
return;
2023-08-14 01:58:20 +02:00
pixman_image_fill_rectangles(
PIXMAN_OP_CLEAR, buffer->pixman_image, &bg_colour,
1, &(pixman_rectangle16_t){
(int16_t)surface.prev_neko_x, (int16_t)0,
(uint16_t)neko_size, (uint16_t)neko_size,
}
);
2023-08-13 03:08:56 +02:00
atlas_composite_neko(buffer, current_neko, surface.neko_x, 0);
2023-08-13 01:37:13 +02:00
wl_surface_set_buffer_scale(surface.wl_surface, 1);
wl_surface_attach(surface.wl_surface, buffer->wl_buffer, 0, 0);
2023-08-13 03:08:56 +02:00
wl_surface_damage_buffer(
surface.wl_surface,
MIN(surface.neko_x, surface.prev_neko_x), 0,
neko_size + neko_x_advance, neko_size
);
2023-08-13 01:37:13 +02:00
buffer->busy = true;
wl_surface_commit(surface.wl_surface);
}
static void surface_destroy (void)
{
if ( surface.layer_surface != NULL )
{
2023-08-13 01:37:13 +02:00
zwlr_layer_surface_v1_destroy(surface.layer_surface);
surface.layer_surface = NULL;
}
2023-08-13 01:37:13 +02:00
if ( surface.wl_surface != NULL )
{
2023-08-13 01:37:13 +02:00
wl_surface_destroy(surface.wl_surface );
surface.wl_surface = NULL;
}
2023-11-17 15:27:56 +01:00
surface.configured = false;
2023-08-13 01:37:13 +02:00
}
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);
2023-08-13 03:08:56 +02:00
surface.width = width;
surface.height = height;
2023-08-13 01:37:13 +02:00
2023-08-13 03:08:56 +02:00
/* Center neko on first configure. */
if (!surface.configured)
{
surface.neko_x = (uint16_t)((width / 2) - neko_size);
surface.prev_neko_x = surface.neko_x;
}
surface.configured = true;
2023-08-13 01:37:13 +02:00
surface_next_frame();
}
2023-11-17 15:27:56 +01:00
static const struct wl_callback_listener sync_callback_listener;
2023-08-13 01:37:13 +02:00
static void layer_surface_handle_closed (void *data, struct zwlr_layer_surface_v1 *layer_surface)
{
(void)data;
(void)layer_surface;
surface_destroy();
2023-11-17 15:27:56 +01:00
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;
2023-08-13 01:37:13 +02:00
}
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,
2024-02-18 21:18:06 +01:00
layer,
2023-08-13 01:37:13 +02:00
"wayneko"
);
zwlr_layer_surface_v1_add_listener(
surface.layer_surface,
&layer_surface_listener,
NULL
);
zwlr_layer_surface_v1_set_size(
surface.layer_surface,
2023-08-13 03:08:56 +02:00
0, desired_surface_height
2023-08-13 01:37:13 +02:00
);
zwlr_layer_surface_v1_set_anchor(
surface.layer_surface,
2023-08-13 03:08:56 +02:00
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT
2023-08-13 01:37:13 +02:00
);
2023-10-20 02:51:29 +02:00
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);
}
2023-08-13 01:37:13 +02:00
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);
else if ( strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 )
idle_notifier = wl_registry_bind(registry, name, &ext_idle_notifier_v1_interface, 1);
2023-10-10 19:48:54 +02:00
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);
}
}
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);
2023-08-13 01:37:13 +02:00
}
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
2023-10-10 19:48:54 +02:00
.global_remove = registry_handle_global_remove,
2023-08-13 01:37:13 +02:00
};
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);
}
}
2023-08-13 01:37:13 +02:00
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;
}
}
2023-08-13 03:08:56 +02:00
static bool colour_from_hex (pixman_color_t *colour, const char *hex)
{
2023-08-14 14:48:35 +02:00
if ( strlen(hex) != strlen("0xRRGGBBAA") && strlen(hex) != strlen("0xRRGGBB") )
{
fprintf(stderr, "ERROR: Invalid colour: %s\n", hex);
return false;
}
2023-08-13 03:08:56 +02:00
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;
}
2023-10-20 02:51:29 +02:00
static char *get_argument (int argc, char *argv[], int *i)
2023-08-13 01:37:13 +02:00
{
if ( argc == (*i) + 1 )
{
fprintf(stderr, "ERROR: Flag '%s' requires a parameter.\n", argv[(*i)]);
2023-10-20 02:51:29 +02:00
return NULL;
}
(*i)++;
2023-10-20 02:51:29 +02:00
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;
}
2023-08-13 01:37:13 +02:00
int main (int argc, char *argv[])
{
init_signals();
2023-08-13 01:37:13 +02:00
2023-08-13 03:08:56 +02:00
colour_from_hex(&bg_colour, "0xFFFFFF");
colour_from_hex(&border_colour, "0x000000");
2023-10-19 06:45:19 +02:00
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;
}
2023-11-17 15:27:56 +01:00
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;
}
2023-10-19 06:45:19 +02:00
else if ( strcmp(argv[i], "--type") == 0 )
{
2023-10-20 02:51:29 +02:00
const char *t = get_argument(argc, argv, &i);
if ( t == NULL )
2023-10-19 06:45:19 +02:00
return EXIT_FAILURE;
2023-10-20 02:51:29 +02:00
if ( strcmp(t, "neko") == 0 )
2023-10-19 06:45:19 +02:00
type = NEKO;
2023-10-20 02:51:29 +02:00
else if ( strcmp(t, "inu") == 0 )
2023-10-19 06:45:19 +02:00
type = INU;
2023-10-20 02:51:29 +02:00
else if ( strcmp(t, "random") == 0 )
2023-10-19 06:45:19 +02:00
type = rand() % 2 == 0 ? NEKO : INU;
else
{
2023-10-20 02:51:29 +02:00
fprintf(stderr, "ERROR: Unknown argument '%s' for flag '--type'.\n", t);
2023-10-19 06:45:19 +02:00
return EXIT_FAILURE;
}
2023-10-20 02:51:29 +02:00
}
else if ( strcmp(argv[i], "--follow-pointer") == 0 )
{
const char *t = get_argument(argc, argv, &i);
if ( t == NULL )
return EXIT_FAILURE;
2023-10-19 06:45:19 +02:00
2023-10-20 02:51:29 +02:00
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;
}
2023-10-19 06:45:19 +02:00
}
2024-02-18 21:18:06 +01:00
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;
}
2024-03-24 15:59:07 +01:00
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
{
fprintf(stderr, "ERROR: Unknown option: %s\n", argv[i]);
return EXIT_FAILURE;
}
}
2023-10-10 19:48:54 +02:00
wl_list_init(&seats);
wl_list_init(&buffer_pool);
2023-08-13 01:37:13 +02:00
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, &registry_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: 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 ( idle_notifier != NULL )
ext_idle_notifier_v1_destroy(idle_notifier);
2023-08-13 01:37:13 +02:00
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;
}