libdraw

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

libdraw/src/x11/draw.c (288 lines, 9.9 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
// 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 <stdio.h>
#include <stdlib.h>
#include <string.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; }

	xcb_void_cookie_t cookie;
	xcb_generic_error_t *error = NULL;

	(*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_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
	uint32_t values[4] = {
		0, 0,
		XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS |
		XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_EXPOSURE |
		XCB_EVENT_MASK_STRUCTURE_NOTIFY, XCB_VISUAL_CLASS_TRUE_COLOR
	};

	/**/
	xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator((*window)->screen);
	xcb_visualtype_t *visual = NULL;

	for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
		if (depth_iter.data->depth != 32) continue;
		xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data);
		for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) {
			visual = visual_iter.data;
			break;
		}
		if (visual != NULL) break;
	}

	if (visual == NULL) { xcb_disconnect((*window)->connection); return -1; }

	xcb_colormap_t colormap = xcb_generate_id((*window)->connection);
	cookie = xcb_create_colormap(
		(*window)->connection, XCB_COLORMAP_ALLOC_NONE, colormap, (*window)->screen->root, visual->visual_id
	);
	error = xcb_request_check((*window)->connection, cookie);
	if (error != NULL) {
		printf("Error %d while creating colormap\n", error->error_code);
		free(error); xcb_disconnect((*window)->connection); return -1;
	}

	values[3] = colormap;

	cookie = xcb_create_window_checked(
		(*window)->connection, 32, (*window)->window, (*window)->screen->root, 0, 0, w, h, 0,
		XCB_WINDOW_CLASS_INPUT_OUTPUT, visual->visual_id, mask, values
	);
	error = xcb_request_check((*window)->connection, cookie);
	if (error != NULL) {
		printf("Error %d while creating window\n", error->error_code);
		free(error); xcb_disconnect((*window)->connection); return -1;
	}

	xcb_change_property(
		(*window)->connection, XCB_PROP_MODE_REPLACE, (*window)->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8,
		strlen(title), title
	);

	xcb_change_property(
		(*window)->connection, XCB_PROP_MODE_REPLACE, (*window)->window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8,
		strlen(title), title
	);

	(*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;
			if (draw_debug_verbose) { printf("KeyPress (%d, %d, %d)\n", e->detail, e->event_x, e->event_y); }

			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;
			if (draw_debug_verbose) { printf("KeyRelease (%d, %d, %d)\n", e->detail, e->event_x, e->event_y); }

			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;
			if (draw_debug_verbose) { printf("ButtonPress (%d, %d, %d)\n", e->detail, e->event_x, e->event_y); }

			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;
			if (draw_debug_verbose) { printf("ButtonRelease (%d, %d, %d)\n", e->detail, e->event_x, e->event_y); }

			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;
			if (draw_debug_verbose) { printf("MotionNotify (%d, %d)\n", e->event_x, e->event_y); }

			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;
			if (draw_debug) { printf("Expose (%d, %d, %d, %d)\n", e->x, e->y, e->width, e->height); }

			event = (draw_event){ .kind = DRAW_EVENT_PAINT, .paint = { e->x, e->y, e->width, e->height }};
		} break;
		case XCB_UNMAP_NOTIFY: {
			xcb_unmap_notify_event_t *e = (xcb_unmap_notify_event_t *)_e;
			if (draw_debug) { printf("UnmapNotify\n"); }
			event = (draw_event){ .kind = DRAW_EVENT_HIDE };
		} break;
		case XCB_MAP_NOTIFY: {
			xcb_map_notify_event_t *e = (xcb_map_notify_event_t *)_e;
			if (draw_debug) { printf("MapNotify\n"); }
			event = (draw_event){ .kind = DRAW_EVENT_SHOW };
		} break;
		case XCB_CONFIGURE_NOTIFY: {
			xcb_configure_notify_event_t *e = (xcb_configure_notify_event_t *)_e;
			if (draw_debug) { printf("ConfigureNotify (%d, %d, %d, %d)\n", e->x, e->y, e->width, e->height); }

			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 }};
			}
		} break;
		case XCB_CLIENT_MESSAGE: {
			xcb_client_message_event_t *e = (xcb_client_message_event_t *)_e;
			switch (e->type) {
			case ATOM_REPAINT: {
				if (draw_debug) { printf("ClientMessage (REPAINT)\n"); }
				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 ", _e->response_type & ~0x80);
			printf("Sequence: %u ", _e->sequence);
			printf("Full sequence: %u ", _e->full_sequence);
			printf("Pad0: %u ", _e->pad0);
			printf("Pad: %u\n", *_e->pad);
		}} break;
	}

	free(_e); return event;
}

void draw_window_set_title(draw_window *window, const char *title) {
	xcb_change_property(
		window->connection, XCB_PROP_MODE_REPLACE, window->window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, strlen(title),
		title
	);
	xcb_flush(window->connection);
}

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, 32,
		(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;
}