summaryrefslogtreecommitdiff
path: root/keyview.c
diff options
context:
space:
mode:
Diffstat (limited to 'keyview.c')
-rw-r--r--keyview.c330
1 files changed, 330 insertions, 0 deletions
diff --git a/keyview.c b/keyview.c
new file mode 100644
index 0000000..2424686
--- /dev/null
+++ b/keyview.c
@@ -0,0 +1,330 @@
+#include <errno.h>
+#include <fcntl.h>
+#define _GNU_SOURCE /* memfd_create */
+#include <sys/mman.h>
+#undef _GNU_SOURCE
+#include <unistd.h>
+#include <png.h>
+#include <linux/input.h>
+#include <wayland-client.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/strerr.h>
+#include "xdg-shell-client-protocol.h"
+
+#define WIDTH 472
+#define HEIGHT 96
+
+static int
+allocate_shm_file(size_t size)
+{
+ int fd = memfd_create("wl_shm", 0);
+ if (fd < 0)
+ return -1;
+ int ret;
+ do {
+ ret = ftruncate(fd, size);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ fd_close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+struct key_state {
+ int demo;
+ int dash;
+ int grab;
+ enum { JUMP2 = 1 << 0, JUMP1 = 1 << 1 } jump;
+ enum { UP = 1 << 0, LEFT = 1 << 1, DOWN = 1 << 2, RIGHT = 1 << 3 } arrows;
+};
+
+struct texture_info {
+ uint32_t width;
+ uint32_t height;
+ genalloc pixels;
+};
+
+struct client_state {
+ /* Globals */
+ struct wl_display *wl_display;
+ struct wl_registry *wl_registry;
+ struct wl_shm *wl_shm;
+ struct wl_compositor *wl_compositor;
+ struct xdg_wm_base *xdg_wm_base;
+ /* Objects */
+ struct wl_surface *wl_surface;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
+ /* Buffers */
+ struct wl_buffer *buffers[2];
+ uint32_t *buffers_data[2];
+ int buffers_ready[2];
+ /* Application state */
+ struct key_state last_rendered_keys;
+ struct key_state keys;
+ struct texture_info texture;
+};
+
+static void
+wl_buffer_release(void *data, struct wl_buffer *buffer)
+{
+ struct client_state *state = data;
+ for (size_t i = 0; i < sizeof(state->buffers) / sizeof(state->buffers[0]); ++i) {
+ if (buffer == state->buffers[i])
+ state->buffers_ready[i] = 1;
+ }
+}
+
+static const struct wl_buffer_listener wl_buffer_listener = {
+ .release = wl_buffer_release,
+};
+
+static void
+copy_texture_square(uint32_t *dest, struct client_state *state, int index, int variant)
+{
+ uint32_t *pixels = genalloc_s(uint32_t, &state->texture.pixels);
+ for (uint32_t y = 0; y < 96; ++y)
+ memcpy(&dest[y * WIDTH + 94 * index],
+ &pixels[(96 * variant + y) * state->texture.width + 96 * index],
+ 96 * sizeof(uint32_t));
+ wl_surface_damage_buffer(state->wl_surface, 94 * index, 0, 96, 96);
+}
+
+static void
+draw_frame(struct client_state *state, int refresh)
+{
+ struct wl_buffer *buffer = NULL;
+ uint32_t *data = NULL;
+ for (size_t i = 0; i < sizeof(state->buffers) / sizeof(state->buffers[0]); ++i) {
+ if (state->buffers_ready[i]) {
+ buffer = state->buffers[i];
+ data = state->buffers_data[i];
+ state->buffers_ready[i] = 0;
+ break;
+ }
+ }
+ if (buffer == NULL || data == NULL)
+ return;
+
+ if (refresh || state->last_rendered_keys.demo != state->keys.demo)
+ copy_texture_square(data, state, 0, state->keys.demo);
+ if (refresh || state->last_rendered_keys.dash != state->keys.dash)
+ copy_texture_square(data, state, 1, state->keys.dash);
+ if (refresh || state->last_rendered_keys.grab != state->keys.grab)
+ copy_texture_square(data, state, 2, state->keys.grab);
+ if (refresh || state->last_rendered_keys.jump != state->keys.jump)
+ copy_texture_square(data, state, 3, state->keys.jump);
+ if (refresh || state->last_rendered_keys.arrows != state->keys.arrows)
+ copy_texture_square(data, state, 4, state->keys.arrows);
+
+ memcpy(&state->last_rendered_keys, &state->keys, sizeof(state->keys));
+
+ wl_surface_attach(state->wl_surface, buffer, 0, 0);
+ wl_surface_commit(state->wl_surface);
+}
+
+static void
+xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
+{
+ struct client_state *state = data;
+ xdg_surface_ack_configure(xdg_surface, serial);
+ draw_frame(state, 1);
+}
+
+static const struct xdg_surface_listener xdg_surface_listener = {
+ .configure = xdg_surface_configure,
+};
+
+static void
+xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
+{
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = xdg_wm_base_ping,
+};
+
+static void
+registry_global(void *data, struct wl_registry *wl_registry,
+ uint32_t name, const char *interface, uint32_t version)
+{
+ struct client_state *state = data;
+ if (strcmp(interface, wl_shm_interface.name) == 0) {
+ state->wl_shm = wl_registry_bind(wl_registry, name, &wl_shm_interface, 1);
+ } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
+ state->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, 4);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ state->xdg_wm_base = wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(state->xdg_wm_base, &xdg_wm_base_listener, state);
+ }
+}
+
+static void
+registry_global_remove(void *data, struct wl_registry *wl_registry, uint32_t name)
+{
+ /* This space deliberately left blank */
+}
+
+static const struct wl_registry_listener wl_registry_listener = {
+ .global = registry_global,
+ .global_remove = registry_global_remove,
+};
+
+void
+redraw(void *data, struct wl_callback *callback, uint32_t callback_data)
+{
+ struct client_state *state = data;
+ draw_frame(state, 0);
+}
+
+static const struct wl_callback_listener frame_listener = { .done = redraw };
+
+void
+process_keyevent(struct input_event *ev, struct key_state *keys)
+{
+ if (ev->type != EV_KEY)
+ return;
+ switch (ev->code) {
+ case KEY_V:
+ keys->demo = ev->value > 0;
+ break;
+ case KEY_X:
+ keys->dash = ev->value > 0;
+ break;
+ case KEY_Z:
+ keys->grab = ev->value > 0;
+ break;
+ case KEY_C:
+ keys->jump = ev->value ? keys->jump | JUMP1 : keys->jump & ~JUMP1;
+ break;
+ case KEY_SPACE:
+ keys->jump = ev->value ? keys->jump | JUMP2 : keys->jump & ~JUMP2;
+ break;
+ case KEY_LEFT:
+ keys->arrows = ev->value ? keys->arrows | LEFT : keys->arrows & ~LEFT;
+ break;
+ case KEY_RIGHT:
+ keys->arrows = ev->value ? keys->arrows | RIGHT : keys->arrows & ~RIGHT;
+ break;
+ case KEY_UP:
+ keys->arrows = ev->value ? keys->arrows | UP : keys->arrows & ~UP;
+ break;
+ case KEY_DOWN:
+ keys->arrows = ev->value ? keys->arrows | DOWN : keys->arrows & ~DOWN;
+ break;
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ PROG = "keyview";
+ if (argc != 1)
+ strerr_dieusage(100, PROG);
+
+ struct client_state state = { 0 };
+
+ {
+ png_image img = { .version = PNG_IMAGE_VERSION, .opaque = NULL };
+ char *path = "texture.png";
+ if (!png_image_begin_read_from_file(&img, path))
+ strerr_diefu(111, "read PNG texture from ", path, ": ", img.message);
+ state.texture.width = img.width;
+ state.texture.height = img.height;
+ img.format = PNG_FORMAT_RGBA;
+ int32_t row_stride = PNG_IMAGE_ROW_STRIDE(img);
+ size_t bufsize = PNG_IMAGE_BUFFER_SIZE(img, row_stride);
+ state.texture.pixels = (genalloc)GENALLOC_ZERO;
+ if (!genalloc_ready_tuned(uint32_t, &state.texture.pixels, bufsize, 0, 0, 1))
+ strerr_diefusys(111, "allocate pixel buffer");
+ if (!png_image_finish_read(&img, 0, genalloc_s(uint32_t, &state.texture.pixels), row_stride, NULL))
+ strerr_diefu(111, "read PNG texture from ", path, ": ", img.message);
+ genalloc_setlen(uint32_t, &state.texture.pixels, bufsize);
+ png_image_free(&img);
+ }
+
+ state.wl_display = wl_display_connect(NULL);
+ if (state.wl_display == NULL)
+ strerr_diefusys(111, "connect to Wayland display");
+ state.wl_registry = wl_display_get_registry(state.wl_display);
+ wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state);
+ wl_display_roundtrip(state.wl_display);
+
+ state.wl_surface = wl_compositor_create_surface(state.wl_compositor);
+ state.xdg_surface = xdg_wm_base_get_xdg_surface(
+ state.xdg_wm_base, state.wl_surface);
+ xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);
+ state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
+ xdg_toplevel_set_title(state.xdg_toplevel, PROG);
+ wl_surface_commit(state.wl_surface);
+
+ {
+ size_t stride = WIDTH * 4;
+ size_t bufsize = stride * HEIGHT;
+ size_t size = bufsize * 2;
+
+ int fd = allocate_shm_file(size);
+ if (fd < 0)
+ strerr_diefusys(111, "open shm file");
+
+ uint32_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED)
+ strerr_diefusys(111, "mmap shm file");
+
+ struct wl_shm_pool *pool = wl_shm_create_pool(state.wl_shm, fd, size);
+ for (size_t i = 0; i < sizeof(state.buffers) / sizeof(state.buffers[0]); ++i) {
+ state.buffers[i] = wl_shm_pool_create_buffer(pool, i * bufsize,
+ WIDTH, HEIGHT, stride, WL_SHM_FORMAT_ARGB8888);
+ wl_buffer_add_listener(state.buffers[i], &wl_buffer_listener, &state);
+ state.buffers_data[i] = data + i * bufsize / sizeof(uint32_t);
+ state.buffers_ready[i] = 1;
+ }
+ wl_shm_pool_destroy(pool);
+ fd_close(fd);
+ }
+
+ iopause_fd fds[] = {
+ { .fd = wl_display_get_fd(state.wl_display), .events = IOPAUSE_READ },
+ { .fd = 0, .events = IOPAUSE_READ }
+ };
+ if (ndelay_on(0) < 0)
+ strerr_diefusys(111, "set stdin as non-blocking");
+ struct input_event event;
+ while (1) {
+ wl_display_dispatch_pending(state.wl_display);
+ if (sanitize_read(wl_display_flush(state.wl_display)) < 0 && errno != EPIPE)
+ strerr_diefusys(111, "flush Wayland queue");
+
+ if (iopause(fds, sizeof(fds) / sizeof(fds[0]), NULL, NULL) < 0)
+ strerr_diefusys(111, "poll in event loop");
+
+ if (fds[0].revents & IOPAUSE_READ) {
+ if (wl_display_dispatch(state.wl_display) < 0)
+ strerr_diefusys(111, "dispatch Wayland events");
+ }
+
+ if (fds[1].revents & IOPAUSE_READ) {
+ while (1) {
+ size_t w = 0;
+ int r = buffer_getall(buffer_0, (char *)&event, sizeof(event), &w);
+ if (!r) break;
+ if (r < 0)
+ strerr_diefusys(111, "read from stdin");
+ process_keyevent(&event, &state.keys);
+ }
+ if (memcmp(&state.last_rendered_keys, &state.keys, sizeof(state.keys))) {
+ struct wl_callback *frame_callback = wl_surface_frame(state.wl_surface);
+ wl_callback_add_listener(frame_callback, &frame_listener, &state);
+ wl_surface_commit(state.wl_surface);
+ }
+ }
+ }
+ return 0;
+}
+