From d3d6fd0fbfbd3ba308563e865e999ae85a1cc331 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Tue, 30 Jun 2020 17:16:34 -0400 Subject: [PATCH] add jtk event functionality C backend --- src/jtk/event.d | 11 +- src/jtk/jtk.c | 502 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 506 insertions(+), 7 deletions(-) diff --git a/src/jtk/event.d b/src/jtk/event.d index 99df147..ff4983f 100644 --- a/src/jtk/event.d +++ b/src/jtk/event.d @@ -7,7 +7,6 @@ struct Event WINDOW_CLOSE, WINDOW_EXPOSE, KEY_PRESS, - KEY_RELEASE, BUTTON_PRESS, BUTTON_RELEASE, TIMER, @@ -16,9 +15,7 @@ struct Event struct KeyEvent { - bool repeat; uint key; - uint x_keycode; }; struct ButtonEvent @@ -43,9 +40,9 @@ struct Event Type type; union { - KeyEvent key; - ButtonEvent button; - TimerEvent timer; - WindowResizeEvent resize; + KeyEvent key_event; + ButtonEvent button_event; + TimerEvent timer_event; + WindowResizeEvent resize_event; }; } diff --git a/src/jtk/jtk.c b/src/jtk/jtk.c index 681b016..2cb6274 100644 --- a/src/jtk/jtk.c +++ b/src/jtk/jtk.c @@ -164,3 +164,505 @@ void jtk_window_set_icon(void * window, const uint8_t * data, XFlush(g_display); free(property_data); } + +/************************************************************************** + * Event + *************************************************************************/ + +/** Do not wait longer than 100ms */ +#define MAX_WAIT_TIME 100000u + +#define JTK_KEY_MODS_MASK 0xFF000000u +#define JTK_KEY_KEYCODE_MASK 0x00FFFFFFu + +#define JTK_KEY_MODS_SHIFT 0x01000000u +#define JTK_KEY_MODS_LOCK 0x02000000u +#define JTK_KEY_MODS_CTRL 0x04000000u + +#define JTK_KEY_F1 0x00000080u +#define JTK_KEY_F2 0x00000081u +#define JTK_KEY_F3 0x00000082u +#define JTK_KEY_F4 0x00000083u +#define JTK_KEY_F5 0x00000084u +#define JTK_KEY_F6 0x00000085u +#define JTK_KEY_F7 0x00000086u +#define JTK_KEY_F8 0x00000087u +#define JTK_KEY_F9 0x00000088u +#define JTK_KEY_F10 0x00000089u +#define JTK_KEY_F11 0x0000008Au +#define JTK_KEY_F12 0x0000008Bu +#define JTK_KEY_F13 0x0000008Cu +#define JTK_KEY_F14 0x0000008Du +#define JTK_KEY_F15 0x0000008Eu +#define JTK_KEY_F16 0x0000008Fu +#define JTK_KEY_F17 0x00000090u +#define JTK_KEY_F18 0x00000091u +#define JTK_KEY_F19 0x00000092u +#define JTK_KEY_F20 0x00000093u +#define JTK_KEY_F21 0x00000094u +#define JTK_KEY_F22 0x00000095u +#define JTK_KEY_F23 0x00000096u +#define JTK_KEY_F24 0x00000097u +#define JTK_KEY_F25 0x00000098u +#define JTK_KEY_F26 0x00000099u +#define JTK_KEY_F27 0x0000009Au +#define JTK_KEY_F28 0x0000009Bu +#define JTK_KEY_F29 0x0000009Cu +#define JTK_KEY_F30 0x0000009Du +#define JTK_KEY_F31 0x0000009Eu + +#define JTK_KEY_SHIFT_L 0x0000009Fu +#define JTK_KEY_SHIFT_R 0x000000A0u +#define JTK_KEY_CTRL_L 0x000000A1u +#define JTK_KEY_CTRL_R 0x000000A2u +#define JTK_KEY_CAPS_LOCK 0x000000A3u +#define JTK_KEY_SHIFT_LOCK 0x000000A4u + +#define JTK_KEY_META_L 0x000000A5u +#define JTK_KEY_META_R 0x000000A6u +#define JTK_KEY_ALT_L 0x000000A7u +#define JTK_KEY_ALT_R 0x000000A8u +#define JTK_KEY_SUPER_L 0x000000A9u +#define JTK_KEY_SUPER_R 0x000000AAu + +#define JTK_KEY_HOME 0x000000ABu +#define JTK_KEY_LEFT 0x000000ACu +#define JTK_KEY_UP 0x000000ADu +#define JTK_KEY_RIGHT 0x000000AEu +#define JTK_KEY_DOWN 0x000000AFu +#define JTK_KEY_PAGE_UP 0x000000B1u +#define JTK_KEY_PAGE_DOWN 0x000000B3u +#define JTK_KEY_END 0x000000B4u +#define JTK_KEY_BEGIN 0x000000B5u + +#define JTK_KEY_SELECT 0x000000B6u +#define JTK_KEY_PRINT 0x000000B7u +#define JTK_KEY_EXECUTE 0x000000B8u +#define JTK_KEY_INSERT 0x000000B9u +#define JTK_KEY_UNDO 0x000000BAu +#define JTK_KEY_REDO 0x000000BBu +#define JTK_KEY_MENU 0x000000BCu +#define JTK_KEY_FIND 0x000000BDu +#define JTK_KEY_CANCEL 0x000000BEu +#define JTK_KEY_HELP 0x000000BFu +#define JTK_KEY_BREAK 0x000000C0u +#define JTK_KEY_NUM_LOCK 0x000000C3u + +#define JTK_KEY_KP_SPACE 0x000000C4u +#define JTK_KEY_KP_TAB 0x000000C5u +#define JTK_KEY_KP_ENTER 0x000000C6u +#define JTK_KEY_KP_F1 0x000000C7u +#define JTK_KEY_KP_F2 0x000000C8u +#define JTK_KEY_KP_F3 0x000000C9u +#define JTK_KEY_KP_F4 0x000000CAu +#define JTK_KEY_KP_HOME 0x000000CBu +#define JTK_KEY_KP_LEFT 0x000000CCu +#define JTK_KEY_KP_UP 0x000000CDu +#define JTK_KEY_KP_RIGHT 0x000000CEu +#define JTK_KEY_KP_DOWN 0x000000CFu +#define JTK_KEY_KP_PAGE_UP 0x000000D1u +#define JTK_KEY_KP_PAGE_DOWN 0x000000D3u +#define JTK_KEY_KP_END 0x000000D4u +#define JTK_KEY_KP_BEGIN 0x000000D5u +#define JTK_KEY_KP_INSERT 0x000000D6u +#define JTK_KEY_KP_DELETE 0x000000D7u +#define JTK_KEY_KP_EQUAL 0x000000D8u +#define JTK_KEY_KP_MULTIPLY 0x000000D9u +#define JTK_KEY_KP_ADD 0x000000DAu +#define JTK_KEY_KP_SEPARATOR 0x000000DBu +#define JTK_KEY_KP_SUBTRACT 0x000000DCu +#define JTK_KEY_KP_DECIMAL 0x000000DDu +#define JTK_KEY_KP_DIVIDE 0x000000DEu + +#define JTK_KEY_KP_0 0x000000DFu +#define JTK_KEY_KP_1 0x000000E0u +#define JTK_KEY_KP_2 0x000000E1u +#define JTK_KEY_KP_3 0x000000E2u +#define JTK_KEY_KP_4 0x000000E3u +#define JTK_KEY_KP_5 0x000000E4u +#define JTK_KEY_KP_6 0x000000E5u +#define JTK_KEY_KP_7 0x000000E6u +#define JTK_KEY_KP_8 0x000000E7u +#define JTK_KEY_KP_9 0x000000E8u + +#define JTK_KEY_UNKNOWN 0x00FFFFFFu + +#define JTK_EVENT_WINDOW_CLOSE 0u +#define JTK_EVENT_WINDOW_EXPOSE 1u +#define JTK_EVENT_KEY_PRESS 2u +#define JTK_EVENT_BUTTON_PRESS 3u +#define JTK_EVENT_BUTTON_RELEASE 4u +#define JTK_EVENT_TIMER 5u +#define JTK_EVENT_WINDOW_RESIZE 6u + +typedef struct { + uint32_t key; +} jtk_key_event_t; + +typedef struct { + uint32_t mods; + uint8_t button; +} jtk_button_event_t; + +typedef struct { + uint32_t width; + uint32_t height; +} jtk_window_resize_event_t; + +typedef struct { + uint8_t type; + union { + jtk_key_event_t key_event; + jtk_button_event_t button_event; + jtk_window_resize_event_t window_resize; + }; +} jtk_event_t; + +static uint32_t x_state_to_jtk_key_mods(unsigned int x_state) +{ + uint32_t mods = 0u; + /* OR in the modifier states */ + if (x_state & ShiftMask) + { + mods |= JTK_KEY_MODS_SHIFT; + } + if (x_state & LockMask) + { + mods |= JTK_KEY_MODS_LOCK; + } + if (x_state & ControlMask) + { + mods |= JTK_KEY_MODS_CTRL; + } + return mods; +} + +static uint32_t x_key_to_jtk_key(unsigned int x_keycode, unsigned int x_state) +{ + XKeyEvent x_key_event; + x_key_event.type = KeyPress; + x_key_event.display = g_display; + /* Turn off the ControlMask bit for looking up keys. We'll handle control + * keys ourselves. */ + x_key_event.state = x_state & ~ControlMask; + x_key_event.keycode = x_keycode; + char buffer; + KeySym keysym; + uint32_t key = JTK_KEY_UNKNOWN; + + if (XLookupString(&x_key_event, &buffer, 1, &keysym, NULL) > 0) + { + if (buffer == '\r') + { + key = '\n'; + } + else + { + key = buffer; + } + } + else + { + switch (keysym) + { + case XK_F1: key = JTK_KEY_F1; break; + case XK_F2: key = JTK_KEY_F2; break; + case XK_F3: key = JTK_KEY_F3; break; + case XK_F4: key = JTK_KEY_F4; break; + case XK_F5: key = JTK_KEY_F5; break; + case XK_F6: key = JTK_KEY_F6; break; + case XK_F7: key = JTK_KEY_F7; break; + case XK_F8: key = JTK_KEY_F8; break; + case XK_F9: key = JTK_KEY_F9; break; + case XK_F10: key = JTK_KEY_F10; break; + case XK_F11: key = JTK_KEY_F11; break; + case XK_F12: key = JTK_KEY_F12; break; + case XK_F13: key = JTK_KEY_F13; break; + case XK_F14: key = JTK_KEY_F14; break; + case XK_F15: key = JTK_KEY_F15; break; + case XK_F16: key = JTK_KEY_F16; break; + case XK_F17: key = JTK_KEY_F17; break; + case XK_F18: key = JTK_KEY_F18; break; + case XK_F19: key = JTK_KEY_F19; break; + case XK_F20: key = JTK_KEY_F20; break; + case XK_F21: key = JTK_KEY_F21; break; + case XK_F22: key = JTK_KEY_F22; break; + case XK_F23: key = JTK_KEY_F23; break; + case XK_F24: key = JTK_KEY_F24; break; + case XK_F25: key = JTK_KEY_F25; break; + case XK_F26: key = JTK_KEY_F26; break; + case XK_F27: key = JTK_KEY_F27; break; + case XK_F28: key = JTK_KEY_F28; break; + case XK_F29: key = JTK_KEY_F29; break; + case XK_F30: key = JTK_KEY_F30; break; + case XK_F31: key = JTK_KEY_F31; break; + + case XK_Shift_L: key = JTK_KEY_SHIFT_L; break; + case XK_Shift_R: key = JTK_KEY_SHIFT_R; break; + case XK_Control_L: key = JTK_KEY_CTRL_L; break; + case XK_Control_R: key = JTK_KEY_CTRL_R; break; + case XK_Caps_Lock: key = JTK_KEY_CAPS_LOCK; break; + case XK_Shift_Lock: key = JTK_KEY_SHIFT_LOCK;break; + + case XK_Meta_L: key = JTK_KEY_META_L; break; + case XK_Meta_R: key = JTK_KEY_META_R; break; + case XK_Alt_L: key = JTK_KEY_ALT_L; break; + case XK_Alt_R: key = JTK_KEY_ALT_R; break; + case XK_Super_L: key = JTK_KEY_SUPER_L; break; + case XK_Super_R: key = JTK_KEY_SUPER_R; break; + + case XK_Home: key = JTK_KEY_HOME; break; + case XK_Left: key = JTK_KEY_LEFT; break; + case XK_Up: key = JTK_KEY_UP; break; + case XK_Right: key = JTK_KEY_RIGHT; break; + case XK_Down: key = JTK_KEY_DOWN; break; + case XK_Page_Up: key = JTK_KEY_PAGE_UP; break; + case XK_Page_Down: key = JTK_KEY_PAGE_DOWN; break; + case XK_End: key = JTK_KEY_END; break; + case XK_Begin: key = JTK_KEY_BEGIN; break; + + case XK_Select: key = JTK_KEY_SELECT; break; + case XK_Print: key = JTK_KEY_PRINT; break; + case XK_Execute: key = JTK_KEY_EXECUTE; break; + case XK_Insert: key = JTK_KEY_INSERT; break; + case XK_Undo: key = JTK_KEY_UNDO; break; + case XK_Redo: key = JTK_KEY_REDO; break; + case XK_Menu: key = JTK_KEY_MENU; break; + case XK_Find: key = JTK_KEY_FIND; break; + case XK_Cancel: key = JTK_KEY_CANCEL; break; + case XK_Help: key = JTK_KEY_HELP; break; + case XK_Break: key = JTK_KEY_BREAK; break; + case XK_Num_Lock: key = JTK_KEY_NUM_LOCK; break; + + case XK_KP_Space: key = JTK_KEY_KP_SPACE; break; + case XK_KP_Tab: key = JTK_KEY_KP_TAB; break; + case XK_KP_Enter: key = JTK_KEY_KP_ENTER; break; + case XK_KP_F1: key = JTK_KEY_KP_F1; break; + case XK_KP_F2: key = JTK_KEY_KP_F2; break; + case XK_KP_F3: key = JTK_KEY_KP_F3; break; + case XK_KP_F4: key = JTK_KEY_KP_F4; break; + case XK_KP_Home: key = JTK_KEY_KP_HOME; break; + case XK_KP_Left: key = JTK_KEY_KP_LEFT; break; + case XK_KP_Up: key = JTK_KEY_KP_UP; break; + case XK_KP_Right: key = JTK_KEY_KP_RIGHT; break; + case XK_KP_Down: key = JTK_KEY_KP_DOWN; break; + case XK_KP_Page_Up: key = JTK_KEY_KP_PAGE_UP; break; + case XK_KP_Page_Down: key = JTK_KEY_KP_PAGE_DOWN; break; + case XK_KP_End: key = JTK_KEY_KP_END; break; + case XK_KP_Begin: key = JTK_KEY_KP_BEGIN; break; + case XK_KP_Insert: key = JTK_KEY_KP_INSERT; break; + case XK_KP_Delete: key = JTK_KEY_KP_DELETE; break; + case XK_KP_Equal: key = JTK_KEY_KP_EQUAL; break; + case XK_KP_Multiply: key = JTK_KEY_KP_MULTIPLY; break; + case XK_KP_Add: key = JTK_KEY_KP_ADD; break; + case XK_KP_Separator: key = JTK_KEY_KP_SEPARATOR; break; + case XK_KP_Subtract: key = JTK_KEY_KP_SUBTRACT; break; + case XK_KP_Decimal: key = JTK_KEY_KP_DECIMAL; break; + case XK_KP_Divide: key = JTK_KEY_KP_DIVIDE; break; + + case XK_KP_0: key = JTK_KEY_KP_0; break; + case XK_KP_1: key = JTK_KEY_KP_1; break; + case XK_KP_2: key = JTK_KEY_KP_2; break; + case XK_KP_3: key = JTK_KEY_KP_3; break; + case XK_KP_4: key = JTK_KEY_KP_4; break; + case XK_KP_5: key = JTK_KEY_KP_5; break; + case XK_KP_6: key = JTK_KEY_KP_6; break; + case XK_KP_7: key = JTK_KEY_KP_7; break; + case XK_KP_8: key = JTK_KEY_KP_8; break; + case XK_KP_9: key = JTK_KEY_KP_9; break; + } + } + + /* OR in the modifier states */ + key |= x_state_to_jtk_key_mods(x_state); + + return key; +} + +static Bool match_key_press(Display * display, XEvent * event, XPointer arg) +{ + XEvent * match_event = (XEvent *)arg; + return (event->type == match_event->type) && + (event->xkey.window == match_event->xkey.window) && + (event->xkey.state == match_event->xkey.state) && + (event->xkey.keycode == match_event->xkey.keycode); +} + +/** + * Process an X key press event. + * + * @param x_event + * Pointer to the X event. + * @param event + * Pointer to the Jtk event. + */ +static bool process_x_key_press_event(XEvent * x_event, jtk_event_t * event) +{ + unsigned int x_keycode = x_event->xkey.keycode; + event->type = JTK_EVENT_KEY_PRESS; + event->key_event.key = x_key_to_jtk_key(x_keycode, x_event->xkey.state); + /* Remove any following keypress events for the same keycode from the X + * queue. */ + XEvent remove_event; + while (XCheckIfEvent(g_display, &remove_event, match_key_press, (XPointer)x_event) == True) + { + } + return true; +} + +static Bool match_button_press(Display * display, XEvent * event, XPointer arg) +{ + XEvent * match_event = (XEvent *)arg; + return (event->type == match_event->type) && + (event->xbutton.window == match_event->xbutton.window) && + (event->xbutton.state == match_event->xbutton.state) && + (event->xbutton.button == match_event->xbutton.button); +} + +/** + * Process an X button press event. + */ +static bool process_x_button_press_event(XEvent * x_event, jtk_event_t * event) +{ + event->type = JTK_EVENT_BUTTON_PRESS; + event->button_event.mods = x_state_to_jtk_key_mods(x_event->xbutton.state); + event->button_event.button = x_event->xbutton.button; + /* If this is a mouse wheel scroll event, remove any following scroll + * events. */ + if ((event->button_event.button == 4) || (event->button_event.button == 5)) + { + XEvent remove_event; + while (XCheckIfEvent(g_display, &remove_event, match_button_press, (XPointer)x_event) == True) + { + } + } + return true; +} + +/** + * Process an X configure event. + * + * @param x_event + * Pointer to the X event. + * @param event + * Pointer to the Jtk event. + */ +static bool process_x_configure_event(XEvent * x_event, jtk_event_t * event) +{ + event->type = JTK_EVENT_WINDOW_RESIZE; + event->window_resize.width = x_event->xconfigure.width; + event->window_resize.height = x_event->xconfigure.height; + return true; +} + +/** + * Process an X ClientMessage event. + * + * @param x_event + * Pointer to the X event. + * @param event + * Pointer to the Jtk event. + */ +static bool process_x_client_message_event(XEvent * x_event, jtk_event_t * event) +{ + Atom wm_delete_window_atom = XInternAtom(g_display, "WM_DELETE_WINDOW", False); + if (x_event->xclient.data.l[0] == (long)wm_delete_window_atom) + { + event->type = JTK_EVENT_WINDOW_CLOSE; + return true; + } + return false; +} + +/** + * Process an X event. + * + * @param x_event + * Pointer to the X event. + * @param event + * Pointer to the Jtk event. + * + * @retval true + * The event should be passed to the user and event has been filled in. + * @retval false + * The event should not be passed to the user. + */ +static bool process_x_event(XEvent * x_event, jtk_event_t * event) +{ + switch (x_event->type) + { + case KeyPress: + return process_x_key_press_event(x_event, event); + + case KeyRelease: + return false; + + case ButtonPress: + return process_x_button_press_event(x_event, event); + + case ButtonRelease: + break; + + case Expose: + event->type = JTK_EVENT_WINDOW_EXPOSE; + return true; + + case GraphicsExpose: + event->type = JTK_EVENT_WINDOW_EXPOSE; + return true; + + case MapNotify: + event->type = JTK_EVENT_WINDOW_EXPOSE; + return true; + + case ConfigureNotify: + return process_x_configure_event(x_event, event); + + case ClientMessage: + return process_x_client_message_event(x_event, event); + + case MappingNotify: + XRefreshKeyboardMapping(&x_event->xmapping); + return false; + } + + return false; +} + +bool jtk_check_event(jtk_event_t * event) +{ + /* First check for an X event. */ + while (XPending(g_display) > 0) + { + XEvent x_event; + XNextEvent(g_display, &x_event); + if (process_x_event(&x_event, event)) + return true; + } + + return false; +} + +void jtk_wait_event(jtk_event_t * event, uint64_t time_to_wait) +{ + for (;;) + { + if (jtk_check_event(event)) + return; + + /* Wait for something to happen. */ + if ((time_to_wait > MAX_WAIT_TIME) || (time_to_wait == 0u)) + { + time_to_wait = MAX_WAIT_TIME; + } + + int x_fd = ConnectionNumber(g_display); + fd_set fds; + FD_ZERO(&fds); + FD_SET(x_fd, &fds); + struct timeval tv; + tv.tv_sec = time_to_wait / 1000000u; + tv.tv_usec = time_to_wait % 1000000u; + select(x_fd + 1, &fds, NULL, NULL, &tv); + } +}