libdraw

Minimal window and drawing library
git clone http://git.omkov.net/libdraw
Log | Tree | Refs | Download

AuthorJakob Wakeling <[email protected]>
Date2024-03-10 06:35:51
Commitccd60e6699b2d7bee0da5001872d696de05ba521
Parent7fd7b2117f51ff042e2fc3f38c6fd00a7c3c005b

Implement basic X11 windows, events, and drawing

Diffstat

A src/draw.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
A src/draw.h | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A src/keys.h | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A src/x11/draw.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A src/x11/keys.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

5 files changed, 612 insertions, 0 deletions

diff --git a/src/draw.c b/src/draw.c
new file mode 100644
index 0000000..f236fb5
--- /dev/null
+++ b/src/draw.c
@@ -0,0 +1,48 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+#include "draw.h"
+
+#include <stdlib.h>
+
+bool draw_debug = false;
+
+draw_buffer *draw_buffer_init(int32_t w, int32_t h) {
+	draw_buffer *buf = calloc(1, sizeof(draw_buffer)); if (buf == NULL) { return NULL; }
+	*buf = (draw_buffer){ .bounds = {0, 0, w, h}, .buf = malloc((w * h) * sizeof(uint32_t)) };
+	if (buf->buf == NULL) { free(buf); return NULL; } return buf;
+}
+
+void draw_buffer_free(draw_buffer **buf) {
+	free((*buf)->buf); free(*buf); *buf = NULL;
+}
+
+void draw_buffer_copy(draw_buffer *dst, draw_buffer *src, point dp, rect sr) {
+	for (int32_t x = 0; x < dst->bounds.w && x < (src->bounds.w - src->bounds.x); x += 1) {
+		for (int32_t y = 0; y < dst->bounds.h && y < (src->bounds.h - src->bounds.y); y += 1) {
+			dst->buf[((y + dp.y) * dst->bounds.w) + (x + dp.x)] = src->buf[((y + sr.y) * src->bounds.w) + (x + sr.x)];
+		}
+	}
+}
+
+void draw_buffer_clear(draw_buffer *buf, uint32_t colour) {
+	for (int64_t i = 0; i < buf->bounds.w * buf->bounds.h; i += 1) { buf->buf[i] = colour; }
+}
+
+void draw_buffer_rect(draw_buffer *buf, point p, rect r, uint32_t colour) {
+	for (int32_t x = p.x + r.x; x < p.x + r.w; x += 1) for (int32_t y = p.y + r.y; y < p.y + r.h; y += 1) {
+		if (x >= 0 && x < buf->bounds.w && y >= 0 && y < buf->bounds.h) {
+			buf->buf[(y * buf->bounds.w) + x] = colour;
+		}
+	}
+}
+
+void draw_buffer_circle(draw_buffer *buf, point p, int32_t r, uint32_t colour) {
+	for (int32_t x = -r; x < r; x += 1) for (int32_t y = -r; y < r; y += 1) {
+		if ((x * x) + (y * y) < (r * r)) {
+			if (p.x + x >= 0 && p.x + x < buf->bounds.w && p.y + y >= 0 && p.y + y < buf->bounds.h) {
+				buf->buf[((p.y + y) * buf->bounds.w) + (p.x + x)] = colour;
+			}
+		}
+	}
+}
diff --git a/src/draw.h b/src/draw.h
new file mode 100644
index 0000000..c7777d6
--- /dev/null
+++ b/src/draw.h
@@ -0,0 +1,87 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+#ifndef DRAW_DRAW_H_RNC24Y6G
+#define DRAW_DRAW_H_RNC24Y6G
+
+#include "keys.h"
+
+#include <stdint.h>
+
+typedef enum {
+	DRAW_MOD_SHIFT     = 0x01,
+	DRAW_MOD_CONTROL   = 0x02,
+	DRAW_MOD_ALT       = 0x04,
+	DRAW_MOD_SUPER     = 0x08,
+	DRAW_MOD_CAPS_LOCK = 0x10,
+	DRAW_MOD_NUM_LOCK  = 0x20,
+} draw_mod_k;
+
+typedef enum {
+	DRAW_MOUSE_NONE         = 0,
+	DRAW_MOUSE_LEFT         = 1,
+	DRAW_MOUSE_MIDDLE       = 2,
+	DRAW_MOUSE_RIGHT        = 3,
+	DRAW_MOUSE_SCROLL_UP    = 4,
+	DRAW_MOUSE_SCROLL_DOWN  = 5,
+	DRAW_MOUSE_SCROLL_LEFT  = 6,
+	DRAW_MOUSE_SCROLL_RIGHT = 7,
+	DRAW_MOUSE_BACK         = 8,
+	DRAW_MOUSE_FORWARD      = 9,
+} draw_mouse_k;
+
+typedef enum {
+	DRAW_EVENT_EXIT = -1,
+	DRAW_EVENT_NONE = 0,
+	DRAW_EVENT_KEY,
+	DRAW_EVENT_MOUSE,
+	DRAW_EVENT_PAINT,
+	DRAW_EVENT_MOVE,
+	DRAW_EVENT_RESIZE,
+} draw_event_k;
+
+typedef enum { DRAW_PRESS, DRAW_RELEASE, DRAW_REPEAT } draw_direction;
+
+typedef struct { draw_key_k code; draw_mod_k modifiers; draw_direction direction; } draw_event_key;
+typedef struct { int32_t x, y; draw_mouse_k button; draw_mod_k modifiers; draw_direction direction; } draw_event_mouse;
+
+typedef struct { int32_t x, y, w, h; } draw_event_paint;
+typedef struct { int32_t w, h; } draw_event_resize;
+
+typedef struct {
+	draw_event_k kind;
+	union {
+		draw_event_key key;
+		draw_event_mouse mouse;
+		draw_event_paint paint;
+		draw_event_resize resize;
+	};
+} draw_event;
+
+typedef struct draw_window_s draw_window;
+
+typedef struct { int32_t x, y; } point;
+typedef struct { int32_t x, y, w, h; } rect;
+typedef struct { rect bounds; uint32_t *buf; } draw_buffer;
+
+extern bool draw_debug;
+
+extern int draw_window_init(draw_window **window, int32_t w, int32_t h, const char *title, uint32_t flags);
+extern void draw_window_free(draw_window **window);
+
+extern draw_event draw_window_event(draw_window *window);
+extern void draw_window_repaint(draw_window *window);
+
+extern int draw_window_draw(draw_window *w, point dp, draw_buffer *buf, rect br);
+extern void draw_window_clear(draw_window *w, uint32_t colour);
+
+extern draw_buffer *draw_buffer_init(int32_t w, int32_t h);
+extern void draw_buffer_free(draw_buffer **buf);
+
+extern void draw_buffer_copy(draw_buffer *dst, draw_buffer *src, point dp, rect sr);
+
+extern void draw_buffer_clear(draw_buffer *buf, uint32_t colour);
+extern void draw_buffer_rect(draw_buffer *buf, point p, rect r, uint32_t colour);
+extern void draw_buffer_circle(draw_buffer *buf, point p, int32_t r, uint32_t colour);
+
+#endif // DRAW_DRAW_H_RNC24Y6G
diff --git a/src/keys.h b/src/keys.h
new file mode 100644
index 0000000..97cdc94
--- /dev/null
+++ b/src/keys.h
@@ -0,0 +1,136 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+#ifndef DRAW_KEYS_H_YB0TUA76
+#define DRAW_KEYS_H_YB0TUA76
+
+typedef enum {
+	DRAW_KEY_UNKNOWN       = -1,
+
+	DRAW_KEY_SPACE         = 0x0020,
+	DRAW_KEY_APOSTROPHE    = 0x0027,
+	DRAW_KEY_COMMA         = 0x002c,
+	DRAW_KEY_MINUS         = 0x002d,
+	DRAW_KEY_PERIOD        = 0x002e,
+	DRAW_KEY_SLASH         = 0x002f,
+
+	DRAW_KEY_0             = 0x0030,
+	DRAW_KEY_1             = 0x0031,
+	DRAW_KEY_2             = 0x0032,
+	DRAW_KEY_3             = 0x0033,
+	DRAW_KEY_4             = 0x0034,
+	DRAW_KEY_5             = 0x0035,
+	DRAW_KEY_6             = 0x0036,
+	DRAW_KEY_7             = 0x0037,
+	DRAW_KEY_8             = 0x0038,
+	DRAW_KEY_9             = 0x0039,
+
+	DRAW_KEY_SEMICOLON     = 0x003b,
+	DRAW_KEY_EQUAL         = 0x003d,
+
+	DRAW_KEY_A             = 0x0041,
+	DRAW_KEY_B             = 0x0042,
+	DRAW_KEY_C             = 0x0043,
+	DRAW_KEY_D             = 0x0044,
+	DRAW_KEY_E             = 0x0045,
+	DRAW_KEY_F             = 0x0046,
+	DRAW_KEY_G             = 0x0047,
+	DRAW_KEY_H             = 0x0048,
+	DRAW_KEY_I             = 0x0049,
+	DRAW_KEY_J             = 0x004a,
+	DRAW_KEY_K             = 0x004b,
+	DRAW_KEY_L             = 0x004c,
+	DRAW_KEY_M             = 0x004d,
+	DRAW_KEY_N             = 0x004e,
+	DRAW_KEY_O             = 0x004f,
+	DRAW_KEY_P             = 0x0050,
+	DRAW_KEY_Q             = 0x0051,
+	DRAW_KEY_R             = 0x0052,
+	DRAW_KEY_S             = 0x0053,
+	DRAW_KEY_T             = 0x0054,
+	DRAW_KEY_U             = 0x0055,
+	DRAW_KEY_V             = 0x0056,
+	DRAW_KEY_W             = 0x0057,
+	DRAW_KEY_X             = 0x0058,
+	DRAW_KEY_Y             = 0x0059,
+	DRAW_KEY_Z             = 0x005a,
+
+	DRAW_KEY_LEFT_BRACKET  = 0x005b,
+	DRAW_KEY_BACKSLASH     = 0x005c,
+	DRAW_KEY_RIGHT_BRACKET = 0x005d,
+	DRAW_KEY_TILDE         = 0x0060,
+
+	DRAW_KEY_ESCAPE	       = 0x0100,
+	DRAW_KEY_BACKSPACE     = 0x0101,
+	DRAW_KEY_TAB           = 0x0102,
+	DRAW_KEY_CAPS_LOCK     = 0x0103,
+	DRAW_KEY_ENTER         = 0x0104,
+
+	DRAW_KEY_F1            = 0x0105,
+	DRAW_KEY_F2            = 0x0106,
+	DRAW_KEY_F3            = 0x0107,
+	DRAW_KEY_F4            = 0x0108,
+	DRAW_KEY_F5            = 0x0109,
+	DRAW_KEY_F6            = 0x010a,
+	DRAW_KEY_F7            = 0x010b,
+	DRAW_KEY_F8            = 0x010c,
+	DRAW_KEY_F9            = 0x010d,
+	DRAW_KEY_F10           = 0x010e,
+	DRAW_KEY_F11           = 0x010f,
+	DRAW_KEY_F12           = 0x0110,
+	DRAW_KEY_F13           = 0x0111,
+	DRAW_KEY_F14           = 0x0112,
+	DRAW_KEY_F15           = 0x0113,
+	DRAW_KEY_F16           = 0x0114,
+	DRAW_KEY_F17           = 0x0115,
+	DRAW_KEY_F18           = 0x0116,
+	DRAW_KEY_F19           = 0x0117,
+	DRAW_KEY_F20           = 0x0118,
+	DRAW_KEY_F21           = 0x0119,
+	DRAW_KEY_F22           = 0x011a,
+	DRAW_KEY_F23           = 0x011b,
+	DRAW_KEY_F24           = 0x011c,
+
+	DRAW_KEY_SYSRQ         = 0x011d,
+	DRAW_KEY_SCROLL_LOCK   = 0x011e,
+	DRAW_KEY_PAUSE         = 0x011f,
+	DRAW_KEY_INSERT        = 0x0120,
+	DRAW_KEY_DELETE        = 0x0121,
+	DRAW_KEY_HOME          = 0x0122,
+	DRAW_KEY_END           = 0x0123,
+	DRAW_KEY_PAGE_UP       = 0x0124,
+	DRAW_KEY_PAGE_DOWN     = 0x0125,
+	DRAW_KEY_LEFT          = 0x0126,
+	DRAW_KEY_RIGHT         = 0x0127,
+	DRAW_KEY_UP            = 0x0128,
+	DRAW_KEY_DOWN          = 0x0129,
+
+	DRAW_KEY_NUM_LOCK      = 0x0200,
+	DRAW_KEY_KP_SLASH      = 0x0201,
+	DRAW_KEY_KP_ASTERISK   = 0x0202,
+	DRAW_KEY_KP_MINUS      = 0x0203,
+	DRAW_KEY_KP_PLUS       = 0x0204,
+	DRAW_KEY_KP_ENTER      = 0x0205,
+	DRAW_KEY_KP_0          = 0x0210,
+	DRAW_KEY_KP_1          = 0x0211,
+	DRAW_KEY_KP_2          = 0x0212,
+	DRAW_KEY_KP_3          = 0x0213,
+	DRAW_KEY_KP_4          = 0x0214,
+	DRAW_KEY_KP_5          = 0x0215,
+	DRAW_KEY_KP_6          = 0x0216,
+	DRAW_KEY_KP_7          = 0x0217,
+	DRAW_KEY_KP_8          = 0x0218,
+	DRAW_KEY_KP_9          = 0x0219,
+	DRAW_KEY_KP_DOT        = 0x021a,
+
+	DRAW_KEY_LEFT_SHIFT    = 0x0f00,
+	DRAW_KEY_LEFT_CONTROL  = 0x0f01,
+	DRAW_KEY_LEFT_ALT      = 0x0f02,
+	DRAW_KEY_LEFT_SUPER    = 0x0f03,
+	DRAW_KEY_RIGHT_SHIFT   = 0x0f04,
+	DRAW_KEY_RIGHT_CONTROL = 0x0f05,
+	DRAW_KEY_RIGHT_ALT     = 0x0f06,
+	DRAW_KEY_RIGHT_SUPER   = 0x0f07,
+} draw_key_k;
+
+#endif // DRAW_KEYS_H_YB0TUA76
diff --git a/src/x11/draw.c b/src/x11/draw.c
new file mode 100644
index 0000000..bbe3365
--- /dev/null
+++ b/src/x11/draw.c
@@ -0,0 +1,204 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+#include "../draw.h"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_keysyms.h>
+#include <xcb/xproto.h>
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+enum : xcb_atom_t {
+	ATOM_REPAINT = 0x0f00,
+};
+
+struct keymap { xcb_keysym_t keysym; draw_key_k key; };
+
+struct draw_window_s {
+	xcb_connection_t *connection;
+	xcb_screen_t *screen;
+	xcb_window_t window;
+	xcb_key_symbols_t *symbols;
+
+	uint32_t x, y, w, h;
+};
+
+extern const struct keymap keymap[];
+extern const size_t keymap_size;
+
+static inline draw_mod_k mod(uint16_t state);
+static inline draw_key_k keymap_find(draw_window *window, xcb_keycode_t keycode);
+
+int draw_window_init(draw_window **window, int32_t w, int32_t h, const char *title, uint32_t flags) {
+	(*window) = calloc(1, sizeof(draw_window)); if ((*window) == NULL) { return -1; }
+
+	(*window)->connection = xcb_connect(NULL, NULL);
+	if (xcb_connection_has_error((*window)->connection)) { return -1; }
+
+	(*window)->screen = xcb_setup_roots_iterator(xcb_get_setup((*window)->connection)).data;
+
+	(*window)->window = xcb_generate_id((*window)->connection);
+	if ((*window)->window == -1) { xcb_disconnect((*window)->connection); return -1; }
+
+	uint32_t mask = XCB_CW_EVENT_MASK;
+	uint32_t values[1] = {
+		XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
+		XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
+		XCB_EVENT_MASK_BUTTON_MOTION
+	};
+
+	xcb_create_window(
+		(*window)->connection, XCB_COPY_FROM_PARENT, (*window)->window, (*window)->screen->root, 0, 0, w, h, 0,
+		XCB_WINDOW_CLASS_INPUT_OUTPUT, (*window)->screen->root_visual, mask, values
+	);
+
+	(*window)->symbols = xcb_key_symbols_alloc((*window)->connection);
+	(*window)->w = w; (*window)->h = h;
+
+	xcb_map_window((*window)->connection, (*window)->window);
+	xcb_flush((*window)->connection);
+
+	return 0;
+}
+
+void draw_window_free(draw_window **window) {
+	if (*window == NULL) { return; }
+
+	xcb_key_symbols_free((*window)->symbols);
+	xcb_destroy_window((*window)->connection, (*window)->window);
+	xcb_disconnect((*window)->connection);
+
+	free(*window); *window = NULL;
+}
+
+draw_event draw_window_event(draw_window *window) {
+	xcb_generic_event_t *_e = xcb_wait_for_event(window->connection);
+	if (_e == NULL) {
+		if (draw_debug) { printf("xcb_wait_for_event returned NULL\n"); }
+		return (draw_event){ .kind = DRAW_EVENT_EXIT };
+	}
+
+	draw_event event = {};
+
+	/* TODO handle modifiers */
+	switch (_e->response_type  & ~0x80) {
+		case XCB_KEY_PRESS: {
+			xcb_key_press_event_t *e = (xcb_key_press_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_KEY, .key = {
+				.code = keymap_find(window, e->detail), .modifiers = mod(e->state), .direction = DRAW_PRESS,
+			}};
+		} break;
+		case XCB_KEY_RELEASE: {
+			xcb_key_release_event_t *e = (xcb_key_release_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_KEY, .key = {
+				.code = keymap_find(window, e->detail), .modifiers = mod(e->state), .direction = DRAW_RELEASE,
+			}};
+		} break;
+		case XCB_BUTTON_PRESS: {
+			xcb_button_press_event_t *e = (xcb_button_press_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_MOUSE, .mouse = {
+				.x = e->event_x, .y = e->event_y, .button = e->detail, .modifiers = mod(e->state),
+				.direction = DRAW_PRESS,
+			}};
+		} break;
+		case XCB_BUTTON_RELEASE: {
+			xcb_button_release_event_t *e = (xcb_button_release_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_MOUSE, .mouse = {
+				.x = e->event_x, .y = e->event_y, .button = e->detail, .modifiers = mod(e->state),
+				.direction = DRAW_RELEASE,
+			}};
+		} break;
+		case XCB_MOTION_NOTIFY: {
+			xcb_motion_notify_event_t *e = (xcb_motion_notify_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_MOUSE, .mouse = {
+				.x = e->event_x, .y = e->event_y, .button = e->detail, .modifiers = mod(e->state),
+				.direction = DRAW_REPEAT,
+			}};
+		} break;
+		case XCB_EXPOSE: {
+			xcb_expose_event_t *e = (xcb_expose_event_t *)_e;
+			event = (draw_event){ .kind = DRAW_EVENT_PAINT, .paint = { e->x, e->y, e->width, e->height }};
+			if (draw_debug) { printf("Exposed %d, %d, %d, %d\n", e->x, e->y, e->width, e->height); }
+		} break;
+		case XCB_CONFIGURE_NOTIFY: {
+			xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *)_e;
+
+			if (e->width != window->w || e->height != window->h) {
+				window->w = e->width; window->h = e->height;
+				event = (draw_event){ .kind = DRAW_EVENT_RESIZE, .resize = { e->width, e->height }};
+				if (draw_debug) { printf("Resized to %d, %d\n", e->width, e->height); }
+			}
+		} break;
+		case XCB_CLIENT_MESSAGE: {
+			xcb_client_message_event_t *e = (xcb_client_message_event_t *)_e;
+			switch (e->type) {
+			case ATOM_REPAINT: {
+				event = (draw_event){ .kind = DRAW_EVENT_PAINT, .paint = { 0, 0, window->w, window->h }};
+			} break;
+			}
+		} break;
+		default: { if (draw_debug) { printf("Unhandled X11 event: %d\n", _e->response_type & ~0x80); }} break;
+	}
+
+	free(_e); return event;
+}
+
+void draw_window_repaint(draw_window *window) {
+	xcb_client_message_event_t e = {
+		.response_type = XCB_CLIENT_MESSAGE, .format = 32, .window = window->window, .type = ATOM_REPAINT,
+		.data.data32 = {}
+	};
+
+	xcb_send_event(window->connection, 0, window->window, XCB_EVENT_MASK_NO_EVENT, (const char *)&e);
+	xcb_flush(window->connection);
+}
+
+int draw_window_draw(draw_window *w, point dp, draw_buffer *buf, rect br) {
+	if (w == NULL) { return 1; }
+
+	xcb_gcontext_t gc = xcb_generate_id(w->connection);
+
+
+	xcb_create_gc(w->connection, gc, w->window, 0, NULL);
+	xcb_put_image(
+		w->connection, XCB_IMAGE_FORMAT_Z_PIXMAP, w->window, gc, br.w, br.h, dp.x, dp.y, 0, 24,
+		(buf->bounds.w * buf->bounds.h) * sizeof (*buf->buf), (uint8_t *)buf->buf
+	);
+	xcb_free_gc(w->connection, gc);
+	xcb_flush(w->connection);
+
+	return 0;
+}
+
+void draw_window_clear(draw_window *w, uint32_t colour) {
+	if (w == NULL) { return; }
+
+	xcb_gcontext_t gc = xcb_generate_id(w->connection);
+	xcb_create_gc(w->connection, gc, w->window, 0, NULL);
+	xcb_change_gc(w->connection, gc, XCB_GC_FOREGROUND, (uint32_t[]){colour});
+	xcb_poly_fill_rectangle(w->connection, w->window, gc, 1, (xcb_rectangle_t[]){{
+		.x = 0, .y = 0, .width = w->w, .height = w->h,
+	}});
+	xcb_free_gc(w->connection, gc);
+	xcb_flush(w->connection);
+}
+
+static inline draw_mod_k mod(uint16_t state) {
+	draw_mod_k mod = 0;
+	mod |= ((state & XCB_MOD_MASK_SHIFT) != 0) * DRAW_MOD_SHIFT;
+	mod |= ((state & XCB_MOD_MASK_CONTROL) != 0) * DRAW_MOD_CONTROL;
+	mod |= ((state & XCB_MOD_MASK_1) != 0) * DRAW_MOD_ALT;
+	mod |= ((state & XCB_MOD_MASK_4) != 0) * DRAW_MOD_SUPER;
+	mod |= ((state & XCB_MOD_MASK_LOCK) != 0) * DRAW_MOD_CAPS_LOCK;
+	mod |= ((state & XCB_MOD_MASK_2) != 0) * DRAW_MOD_NUM_LOCK;
+	return mod;
+}
+
+static inline draw_key_k keymap_find(draw_window *window, xcb_keycode_t keycode) {
+	xcb_keysym_t keysym = xcb_key_symbols_get_keysym(window->symbols, keycode, 0);
+	for (size_t i = 0; i < keymap_size; i += 1) { if (keymap[i].keysym == keysym) { return keymap[i].key; }}
+	return DRAW_KEY_UNKNOWN;
+}
diff --git a/src/x11/keys.c b/src/x11/keys.c
new file mode 100644
index 0000000..be76519
--- /dev/null
+++ b/src/x11/keys.c
@@ -0,0 +1,137 @@
+// Copyright (C) 2024, Jakob Wakeling
+// All rights reserved.
+
+#include "../keys.h"
+
+#include <xcb/xcb_keysyms.h>
+#include <X11/keysym.h>
+
+struct keymap { xcb_keysym_t keysym; draw_key_k key; };
+
+const struct keymap keymap[] = {
+	{ XK_space,      DRAW_KEY_SPACE      },
+    { XK_apostrophe, DRAW_KEY_APOSTROPHE },
+    { XK_comma,      DRAW_KEY_COMMA      },
+    { XK_minus,      DRAW_KEY_MINUS      },
+    { XK_period,     DRAW_KEY_PERIOD     },
+    { XK_slash,      DRAW_KEY_SLASH      },
+
+	{ XK_0, DRAW_KEY_0 },
+	{ XK_1, DRAW_KEY_1 },
+	{ XK_2, DRAW_KEY_2 },
+	{ XK_3, DRAW_KEY_3 },
+	{ XK_4, DRAW_KEY_4 },
+	{ XK_5, DRAW_KEY_5 },
+	{ XK_6, DRAW_KEY_6 },
+	{ XK_7, DRAW_KEY_7 },
+	{ XK_8, DRAW_KEY_8 },
+	{ XK_9, DRAW_KEY_9 },
+
+	{ XK_semicolon, DRAW_KEY_SEMICOLON },
+	{ XK_equal,     DRAW_KEY_EQUAL     },
+
+	{ XK_a, DRAW_KEY_A },
+	{ XK_b, DRAW_KEY_B },
+	{ XK_c, DRAW_KEY_C },
+	{ XK_d, DRAW_KEY_D },
+	{ XK_e, DRAW_KEY_E },
+	{ XK_f, DRAW_KEY_F },
+	{ XK_g, DRAW_KEY_G },
+	{ XK_h, DRAW_KEY_H },
+	{ XK_i, DRAW_KEY_I },
+	{ XK_j, DRAW_KEY_J },
+	{ XK_k, DRAW_KEY_K },
+	{ XK_l, DRAW_KEY_L },
+	{ XK_m, DRAW_KEY_M },
+	{ XK_n, DRAW_KEY_N },
+	{ XK_o, DRAW_KEY_O },
+	{ XK_p, DRAW_KEY_P },
+	{ XK_q, DRAW_KEY_Q },
+	{ XK_r, DRAW_KEY_R },
+	{ XK_s, DRAW_KEY_S },
+	{ XK_t, DRAW_KEY_T },
+	{ XK_u, DRAW_KEY_U },
+	{ XK_v, DRAW_KEY_V },
+	{ XK_w, DRAW_KEY_W },
+	{ XK_x, DRAW_KEY_X },
+	{ XK_y, DRAW_KEY_Y },
+	{ XK_z, DRAW_KEY_Z },
+
+	{ XK_bracketleft,  DRAW_KEY_LEFT_BRACKET  },
+	{ XK_backslash,    DRAW_KEY_BACKSLASH     },
+	{ XK_bracketright, DRAW_KEY_RIGHT_BRACKET },
+	{ XK_grave,        DRAW_KEY_TILDE         },
+	{ XK_Escape,       DRAW_KEY_ESCAPE	      },
+	{ XK_BackSpace,    DRAW_KEY_BACKSPACE     },
+	{ XK_Tab,          DRAW_KEY_TAB           },
+	{ XK_Caps_Lock,    DRAW_KEY_CAPS_LOCK     },
+	{ XK_Return,       DRAW_KEY_ENTER         },
+
+	{ XK_F1,  DRAW_KEY_F1  },
+	{ XK_F2,  DRAW_KEY_F2  },
+	{ XK_F3,  DRAW_KEY_F3  },
+	{ XK_F4,  DRAW_KEY_F4  },
+	{ XK_F5,  DRAW_KEY_F5  },
+	{ XK_F6,  DRAW_KEY_F6  },
+	{ XK_F7,  DRAW_KEY_F7  },
+	{ XK_F8,  DRAW_KEY_F8  },
+	{ XK_F9,  DRAW_KEY_F9  },
+	{ XK_F10, DRAW_KEY_F10 },
+	{ XK_F11, DRAW_KEY_F11 },
+	{ XK_F12, DRAW_KEY_F12 },
+	{ XK_F13, DRAW_KEY_F13 },
+	{ XK_F14, DRAW_KEY_F14 },
+	{ XK_F15, DRAW_KEY_F15 },
+	{ XK_F16, DRAW_KEY_F16 },
+	{ XK_F17, DRAW_KEY_F17 },
+	{ XK_F18, DRAW_KEY_F18 },
+	{ XK_F19, DRAW_KEY_F19 },
+	{ XK_F20, DRAW_KEY_F20 },
+	{ XK_F21, DRAW_KEY_F21 },
+	{ XK_F22, DRAW_KEY_F22 },
+	{ XK_F23, DRAW_KEY_F23 },
+	{ XK_F24, DRAW_KEY_F24 },
+
+	{ XK_Sys_Req,     DRAW_KEY_SYSRQ       },
+	{ XK_Scroll_Lock, DRAW_KEY_SCROLL_LOCK },
+	{ XK_Pause,       DRAW_KEY_PAUSE       },
+	{ XK_Insert,      DRAW_KEY_INSERT      },
+	{ XK_Delete,      DRAW_KEY_DELETE      },
+	{ XK_Home,        DRAW_KEY_HOME        },
+	{ XK_End,         DRAW_KEY_END         },
+	{ XK_Page_Up,     DRAW_KEY_PAGE_UP     },
+	{ XK_Page_Down,   DRAW_KEY_PAGE_DOWN   },
+	{ XK_Left,        DRAW_KEY_LEFT        },
+	{ XK_Up,          DRAW_KEY_UP          },
+	{ XK_Right,       DRAW_KEY_RIGHT       },
+	{ XK_Down,        DRAW_KEY_DOWN        },
+
+	{ XK_Num_Lock,     DRAW_KEY_NUM_LOCK    },
+	{ XK_KP_Divide,    DRAW_KEY_KP_SLASH    },
+	{ XK_KP_Multiply,  DRAW_KEY_KP_ASTERISK },
+	{ XK_KP_Subtract,  DRAW_KEY_KP_MINUS    },
+	{ XK_KP_Add,       DRAW_KEY_KP_PLUS     },
+	{ XK_KP_Enter,     DRAW_KEY_KP_ENTER    },
+	{ XK_KP_Insert,    DRAW_KEY_KP_0        },
+	{ XK_KP_End,       DRAW_KEY_KP_1        },
+	{ XK_KP_Down,      DRAW_KEY_KP_2        },
+	{ XK_KP_Page_Down, DRAW_KEY_KP_3        },
+	{ XK_KP_Left,      DRAW_KEY_KP_4        },
+	{ XK_KP_Begin,     DRAW_KEY_KP_5        },
+	{ XK_KP_Right,     DRAW_KEY_KP_6        },
+	{ XK_KP_Home,      DRAW_KEY_KP_7        },
+	{ XK_KP_Up,        DRAW_KEY_KP_8        },
+	{ XK_KP_Page_Up,   DRAW_KEY_KP_9        },
+	{ XK_KP_Delete,    DRAW_KEY_KP_DOT      },
+
+	{ XK_Shift_L,   DRAW_KEY_LEFT_SHIFT    },
+	{ XK_Control_L, DRAW_KEY_LEFT_CONTROL  },
+	{ XK_Alt_L,     DRAW_KEY_LEFT_ALT      },
+	{ XK_Super_L,   DRAW_KEY_LEFT_SUPER    },
+	{ XK_Shift_R,   DRAW_KEY_RIGHT_SHIFT   },
+	{ XK_Control_R, DRAW_KEY_RIGHT_CONTROL },
+	{ XK_Alt_R,     DRAW_KEY_RIGHT_ALT     },
+	{ XK_Super_R,   DRAW_KEY_RIGHT_SUPER   },
+};
+
+const size_t keymap_size = sizeof(keymap) / sizeof(*keymap);