// Copyright (C) 2024, Jakob Wakeling // All rights reserved. #include "../draw.h" #include #include #include #include #include #include #include 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; }