From 9302ac91b7a125d227a6d288271b11221855ddba Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Sat, 15 Apr 2023 21:29:10 +0300 Subject: [PATCH] very basic text input --- Makefile | 6 +- src/main.c | 14 +++ src/node.h | 2 + src/text-input-node.c | 47 ++++++++ src/text-input-node.h | 17 +++ src/window.c | 262 +++++++++++++++++++++++++++++++++++++++++- src/window.h | 10 ++ 7 files changed, 349 insertions(+), 9 deletions(-) create mode 100644 src/text-input-node.c create mode 100644 src/text-input-node.h diff --git a/Makefile b/Makefile index 9fff823..7d816e8 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -CC=clang -LIBS:=`pkg-config --libs xcb cairo pangocairo` -lm -CFLAGS:=$(CFLAGS) -pipe -Wall -Wextra -Wshadow -std=gnu99 -pedantic `pkg-config --cflags xcb cairo pangocairo` +CC?=cc +LIBS:=`pkg-config --libs xcb xcb-xkb cairo pangocairo xkbcommon xkbcommon-x11` -lm +CFLAGS:=$(CFLAGS) -pipe -Wall -Wextra -Wshadow -std=gnu99 -pedantic `pkg-config --cflags xcb xcb-xkb cairo pangocairo xkbcommon xkbcommon-x11` BUILD=build OBJ=$(BUILD) diff --git a/src/main.c b/src/main.c index bc5b15f..0361a13 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include "color.h" #include "node.h" #include "scrollable-node.h" +#include "text-input-node.h" #include "window.h" #include "text-node.h" #include "background-node.h" @@ -102,6 +103,19 @@ int app_handle(struct UINode *node, void *data, int event_type, size_t d, void * dispatcher_new(button, UI_EVENT_UNPRESSED, DECREMENT_COUNT, state, app_handle); } + /* text input test */ + { + + UITextInputNode *text_input = text_input_new(node); + box_layout_new(&text_input->node, UI_DIRECTION_HORIZONTAL); + background_node_new(&text_input->node, UIZinc800, 6); + text_input->node.width_policy = UI_SIZE_POLICY_STATIC; + text_input->node.height_policy = UI_SIZE_POLICY_STATIC; + text_input->node.rect.w = 120; + text_input->node.rect.h = 32; + text_input->text_node = text_node_new(&text_input->node, state->font, UINeutral50, NULL); + } + break; } case SIDEBAR_BEGIN_ANIMATE: { diff --git a/src/node.h b/src/node.h index a9178e8..a77bc46 100644 --- a/src/node.h +++ b/src/node.h @@ -22,6 +22,7 @@ enum UIEvent { UI_EVENT_BUTTON_LEFT_UPDATE, // d == 1 if pressed, d == 0 if released UI_EVENT_BUTTON_RIGHT_UPDATE, // d == 1 if pressed, d == 0 if released UI_EVENT_SCROLL, // p (double*) = delta + UI_EVENT_KEY_DOWN, // d (unsigned int) = keycode, p (struct xkb_state*) = xkb state }; enum UISizePolicy { @@ -37,6 +38,7 @@ enum UIDirection { }; #define UI_NODE_COMPONENT (1 << 0) +#define UI_NODE_RETAINS_PRESS (1 << 1) typedef struct UINode { const char *text; diff --git a/src/text-input-node.c b/src/text-input-node.c new file mode 100644 index 0000000..8d7cc34 --- /dev/null +++ b/src/text-input-node.c @@ -0,0 +1,47 @@ +#include +#include +#include "node.h" +#include "text-node.h" +#include "text-input-node.h" + +UITextInputNode *text_input_new(UINode *parent) +{ + UITextInputNode *n = malloc(sizeof(UITextInputNode)); + node_init(&n->node); + n->text_node = NULL; + n->node.flags = UI_NODE_RETAINS_PRESS; + n->node.text = "text_input"; + n->node.handle_proto = text_input_handle; + n->text = calloc(1, 1); + n->text_size = 1; + node_attach(parent, (UINode*)n); + return n; +} + +int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p) +{ + UITextInputNode *n = (UITextInputNode *)node; + + switch (ev) { + case UI_EVENT_KEY_DOWN: { + unsigned int keycode = d; + struct xkb_state *xkb_state = (struct xkb_state*)p; + + size_t key_size = xkb_state_key_get_utf8(xkb_state, keycode, NULL, 0) + 1; + size_t new_text_size = n->text_size + key_size - 1; + n->text = realloc(n->text, new_text_size); + xkb_state_key_get_utf8(xkb_state, keycode, n->text + n->text_size - 1, key_size); + n->text_size = new_text_size; + + if (n->text_node) { + n->text_node->pending_text = n->text; + node_request_relayout(&n->text_node->node); + } + return 1; + } + default: { + break; + } + } + return 0; +} diff --git a/src/text-input-node.h b/src/text-input-node.h new file mode 100644 index 0000000..4c73d24 --- /dev/null +++ b/src/text-input-node.h @@ -0,0 +1,17 @@ +#ifndef _UI__TEXT_INPUT_NODE_H +#define _UI__TEXT_INPUT_NODE_H + +#include "node.h" +#include "text-node.h" + +typedef struct UITextInputNode { + UINode node; + UITextNode *text_node; + char *text; + size_t text_size; +} UITextInputNode; + +UITextInputNode *text_input_new(UINode *parent); +int text_input_handle(UINode *node, enum UIEvent ev, size_t d, void *p); + +#endif // _UI__TEXT_INPUT_NODE_H diff --git a/src/window.c b/src/window.c index e1c202d..09d42be 100644 --- a/src/window.c +++ b/src/window.c @@ -4,6 +4,7 @@ #include "window.h" #include "node.h" #include "defs.h" +#include #ifdef _UI_DEBUG #define PROF #endif @@ -15,8 +16,16 @@ #include #include #include +#include +#include -void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event); +static void window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event); +static int window_select_xkb_events_for_device(UIWindow *window, int32_t device_id); +static int window_keyboard_update_keymap(UIWindow *window, UIWindowXKBKeyboard *kb); +static int window_keyboard_init(UIWindow *window, UIWindowXKBKeyboard *kb, uint8_t first_xkb_event, int32_t device_id, struct xkb_context *ctx); +static int window_init_core_xkb_keyboard(UIWindow *window); +static void window_keyboard_process_event(UIWindow *window, UIWindowXKBKeyboard *kb, xcb_generic_event_t *gevent); +static void window_keyboard_deinit(UIWindowXKBKeyboard *kb); int window_invalidate_node(UIWindow *window, UINode *node) { @@ -43,6 +52,7 @@ void window_free(UIWindow *window) if (window->_cairo_surface) cairo_surface_destroy(window->_cairo_surface); if (window->drw) cairo_destroy(window->drw); if (window->_xcb_connection) xcb_disconnect(window->_xcb_connection); + window_keyboard_deinit(window->xkb_core_keyboard); node_free(window->root); free(window); @@ -112,7 +122,7 @@ UIWindow *window_new(int width, int height) XCB_WINDOW_CLASS_INPUT_OUTPUT, // class screen->root_visual, // visual XCB_CW_EVENT_MASK, // value mask - (uint32_t[]){ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE } // value list + (uint32_t[]){ XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_KEY_PRESS } // value list ); xcb_map_window(window->_xcb_connection, window->_xcb_window_id); if (xcb_flush(window->_xcb_connection) <= 0) { @@ -131,6 +141,10 @@ UIWindow *window_new(int width, int height) goto done; } + if (window_init_core_xkb_keyboard(window) < 0) { + fprintf(stderr, "err: window_new: failed to initialize keyboard (_window_init_core_xkb_keyboard failed). Keyboard input may not be available.\n"); + } + done: if (fail) { window_free(window); @@ -171,7 +185,222 @@ int window_attach_root(UIWindow *window, UINode *root) return 0; } -void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event) +// Thanks: https://github.com/xkbcommon/libxkbcommon/blob/003fdee1378382d4fef77089f5f4652cd2422c6a/tools/interactive-x11.c#L67 +static int window_select_xkb_events_for_device(UIWindow *window, int32_t device_id) +{ + if (!window->_xcb_connection) { + return -1; + } + + enum { + required_events = + (XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | + XCB_XKB_EVENT_TYPE_MAP_NOTIFY | + XCB_XKB_EVENT_TYPE_STATE_NOTIFY), + + required_nkn_details = + (XCB_XKB_NKN_DETAIL_KEYCODES), + + required_map_parts = + (XCB_XKB_MAP_PART_KEY_TYPES | + XCB_XKB_MAP_PART_KEY_SYMS | + XCB_XKB_MAP_PART_MODIFIER_MAP | + XCB_XKB_MAP_PART_EXPLICIT_COMPONENTS | + XCB_XKB_MAP_PART_KEY_ACTIONS | + XCB_XKB_MAP_PART_VIRTUAL_MODS | + XCB_XKB_MAP_PART_VIRTUAL_MOD_MAP), + + required_state_details = + (XCB_XKB_STATE_PART_MODIFIER_BASE | + XCB_XKB_STATE_PART_MODIFIER_LATCH | + XCB_XKB_STATE_PART_MODIFIER_LOCK | + XCB_XKB_STATE_PART_GROUP_BASE | + XCB_XKB_STATE_PART_GROUP_LATCH | + XCB_XKB_STATE_PART_GROUP_LOCK), + }; + + static const xcb_xkb_select_events_details_t details = { + .affectNewKeyboard = required_nkn_details, + .newKeyboardDetails = required_nkn_details, + .affectState = required_state_details, + .stateDetails = required_state_details, + }; + + xcb_void_cookie_t cookie = xcb_xkb_select_events_aux_checked( + window->_xcb_connection, + device_id, + required_events, /* affectWhich */ + 0, /* clear */ + 0, /* selectAll */ + required_map_parts, /* affectMap */ + required_map_parts, /* map */ + &details /* details */ + ); + + xcb_generic_error_t *error = xcb_request_check(window->_xcb_connection, cookie); + if (error) { + free(error); + return -1; + } + + return 0; +} + +static int window_keyboard_update_keymap(UIWindow *window, UIWindowXKBKeyboard *kb) +{ + struct xkb_keymap *keymap = xkb_x11_keymap_new_from_device(kb->ctx, window->_xcb_connection, kb->device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); + if (!keymap) { + return -1; + } + + struct xkb_state *state = xkb_x11_state_new_from_device(keymap, window->_xcb_connection, kb->device_id); + if (!state) { + xkb_keymap_unref(keymap); + return -1; + } + + xkb_state_unref(kb->state); + xkb_keymap_unref(kb->keymap); + kb->state = state; + kb->keymap = keymap; + + return 0; +} + +static int window_keyboard_init(UIWindow *window, UIWindowXKBKeyboard *kb, uint8_t first_xkb_event, int32_t device_id, struct xkb_context *ctx) +{ + kb->first_xkb_event = first_xkb_event; + kb->device_id = device_id; + kb->ctx = ctx; + kb->keymap = NULL; + kb->state = NULL; + + if (window_keyboard_update_keymap(window, kb) < 0) { + return -1; + } + + if (window_select_xkb_events_for_device(window, device_id) < 0) { + xkb_state_unref(kb->state); + xkb_keymap_unref(kb->keymap); + return -1; + } + + return 0; +} + +static int window_init_core_xkb_keyboard(UIWindow *window) +{ + uint8_t first_xkb_event; + int ret = 0; + UIWindowXKBKeyboard *kb = NULL; + struct xkb_context *ctx = NULL; + + if (!xkb_x11_setup_xkb_extension( + window->_xcb_connection, + XKB_X11_MIN_MAJOR_XKB_VERSION, + XKB_X11_MIN_MINOR_XKB_VERSION, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + NULL, NULL, &first_xkb_event, NULL + )) { + fprintf(stderr, "err: _window_init_core_xkb_keyboard: failed to set up xkb extension (xkb_x11_setup_xkb_extension)\n"); + return -1; + } + + kb = malloc(sizeof(UIWindowXKBKeyboard)); + if (!kb) { + fprintf(stderr, "err: _window_init_core_xkb_keyboard: failed to malloc keyboard struct\n"); + ret = -1; + goto defer_error; + } + + ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + if (!ctx) { + fprintf(stderr, "err: _window_init_core_xkb_keyboard: failed to create xkb context\n"); + ret = -1; + goto defer_error; + } + + int32_t core_kbd_device_id = xkb_x11_get_core_keyboard_device_id(window->_xcb_connection); + if (core_kbd_device_id < 0) { + fprintf(stderr, "err: _window_init_core_xkb_keyboard: failed to find xkb core keyboard device_id\n"); + ret = -1; + goto defer_error; + } + + if (window_keyboard_init(window, kb, first_xkb_event, core_kbd_device_id, ctx) < 0) { + fprintf(stderr, "err: _window_init_core_xkb_keyboard: failed to initialize keyboard (_window_keyboard_init)\n"); + ret = -1; + goto defer_error; + } + + goto success; + +defer_error: + free(kb); + xkb_context_unref(ctx); + return ret; +success: + window->xkb_core_keyboard = kb; + return ret; +} + +static void window_keyboard_deinit(UIWindowXKBKeyboard *kb) +{ + if (!kb) { + return; + } + + xkb_state_unref(kb->state); + xkb_keymap_unref(kb->keymap); + free(kb); +} + +static void window_keyboard_process_event(UIWindow *window, UIWindowXKBKeyboard *kb, xcb_generic_event_t *gevent) +{ + union xkb_event { + struct { + uint8_t response_type; + uint8_t xkbType; + uint16_t sequence; + xcb_timestamp_t time; + uint8_t deviceID; + } any; + xcb_xkb_new_keyboard_notify_event_t new_keyboard_notify; + xcb_xkb_map_notify_event_t map_notify; + xcb_xkb_state_notify_event_t state_notify; + } *event = (union xkb_event *) gevent; + + if (event->any.deviceID != kb->device_id) { + return; + } + + switch (event->any.xkbType) { + case XCB_XKB_NEW_KEYBOARD_NOTIFY: { + if (event->new_keyboard_notify.changed && XCB_XKB_NKN_DETAIL_KEYCODES) { + window_keyboard_update_keymap(window, kb); + } + break; + } + case XCB_XKB_MAP_NOTIFY: { + window_keyboard_update_keymap(window, kb); + break; + } + case XCB_XKB_STATE_NOTIFY: { + xkb_state_update_mask( + kb->state, + event->state_notify.baseMods, + event->state_notify.latchedMods, + event->state_notify.lockedMods, + event->state_notify.baseGroup, + event->state_notify.latchedGroup, + event->state_notify.lockedGroup + ); + break; + } + } +} + +static void window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event) { begin_clock("Process XCB event"); uint8_t event_type = event->response_type & ~0x80; @@ -221,6 +450,24 @@ void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event) break; } + case XCB_KEY_PRESS: { + begin_clock("XCB_KEY_PRESS"); + xcb_key_press_event_t *ev = (xcb_key_press_event_t*)event; + + if (window->xkb_core_keyboard) { + UINode *n = window->pressed; + while (n) { + if (node_dispatch(n, UI_EVENT_KEY_DOWN, ev->detail, window->xkb_core_keyboard->state) > 0) { + break; + } + n = n->parent; + } + } + + end_clock(); + break; + } + case XCB_BUTTON_RELEASE: /* through */ case XCB_BUTTON_PRESS: { begin_clock("XCB_BUTTON_PRESS"); @@ -267,8 +514,8 @@ void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event) node_dispatch(node, UI_EVENT_PRESSED, 0, NULL); } else { UINode *previously_pressed = window->pressed; - window->pressed = NULL; - if (previously_pressed) { + if (previously_pressed && !(previously_pressed->flags & UI_NODE_RETAINS_PRESS)) { + window->pressed = NULL; node_dispatch(previously_pressed, UI_EVENT_UNPRESSED, 0, NULL); } } @@ -279,6 +526,9 @@ void _window_process_xcb_event(UIWindow *window, xcb_generic_event_t *event) } default: { + if (window->xkb_core_keyboard && event->response_type == window->xkb_core_keyboard->first_xkb_event) { + window_keyboard_process_event(window, window->xkb_core_keyboard, event); + } break; } } @@ -368,7 +618,7 @@ int window_turn(UIWindow *window) if ((i == 0 && fds[i].revents & POLLIN) || !has_looped_once) { xcb_generic_event_t *xcb_event; while ((xcb_event = xcb_poll_for_event(window->_xcb_connection))) { - _window_process_xcb_event(window, xcb_event); + window_process_xcb_event(window, xcb_event); free(xcb_event); } } diff --git a/src/window.h b/src/window.h index 8b07038..593b360 100644 --- a/src/window.h +++ b/src/window.h @@ -18,6 +18,15 @@ typedef struct UIWindowTimer { UINode *target; // TODO: what if the node gets deleted while the timer is still running? } UIWindowTimer; +typedef struct UIWindowXKBKeyboard { + uint8_t first_xkb_event; + struct xkb_context *ctx; + + struct xkb_keymap *keymap; + struct xkb_state *state; + int32_t device_id; +} UIWindowXKBKeyboard; + #define UI_WINDOW_MAX_TIMERS 4 #define UI_WINDOW_MAX_POLL (1) @@ -30,6 +39,7 @@ typedef struct UIWindow { cairo_surface_t *_cairo_surface; cairo_t *drw; UIWindowTimer timers[UI_WINDOW_MAX_TIMERS]; + UIWindowXKBKeyboard *xkb_core_keyboard; } UIWindow; int window_invalidate_node(UIWindow *window, UINode *node);