#include #include #define _GNU_SOURCE /* memfd_create */ #include #undef _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #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; }