#include "Jtk.h" #ifdef JTK_X #include #include #include #include #include "Jtk_internal.h" #include /** Do not wait longer than 100ms */ #define MAX_WAIT_TIME 100000u static std::unordered_map g_repeating_keys; static std::unordered_map g_key_repeat_timers; #if 0 static void StopKeyRepeat(unsigned int x_keycode) { auto it = g_repeating_keys.find(x_keycode); if (it != g_repeating_keys.end()) { size_t timer_id = it->second; Jtk_RemoveTimer(timer_id); g_repeating_keys.erase(x_keycode); g_key_repeat_timers.erase(timer_id); } } #endif #if 0 static unsigned int GetXState() { Window win; int i; unsigned int state; XQueryPointer(g_display, RootWindow(g_display, DefaultScreen(g_display)), &win, &win, &i, &i, &i, &i, &state); return state; } #endif static uint32_t XStateToJtkKeyMods(unsigned int x_state) { uint32_t mods = 0u; /* OR in the modifier states */ if (x_state & ShiftMask) { mods |= JES_KEY_MODS_SHIFT; } if (x_state & LockMask) { mods |= JES_KEY_MODS_LOCK; } if (x_state & ControlMask) { mods |= JES_KEY_MODS_CTRL; } return mods; } static uint32_t XKeyToJtkKey(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 = JES_KEY_UNKNOWN; if (XLookupString(&x_key_event, &buffer, 1, &keysym, nullptr) > 0) { if (buffer == '\r') { key = '\n'; } else { key = buffer; } } else { switch (keysym) { case XK_F1: key = JES_KEY_F1; break; case XK_F2: key = JES_KEY_F2; break; case XK_F3: key = JES_KEY_F3; break; case XK_F4: key = JES_KEY_F4; break; case XK_F5: key = JES_KEY_F5; break; case XK_F6: key = JES_KEY_F6; break; case XK_F7: key = JES_KEY_F7; break; case XK_F8: key = JES_KEY_F8; break; case XK_F9: key = JES_KEY_F9; break; case XK_F10: key = JES_KEY_F10; break; case XK_F11: key = JES_KEY_F11; break; case XK_F12: key = JES_KEY_F12; break; case XK_F13: key = JES_KEY_F13; break; case XK_F14: key = JES_KEY_F14; break; case XK_F15: key = JES_KEY_F15; break; case XK_F16: key = JES_KEY_F16; break; case XK_F17: key = JES_KEY_F17; break; case XK_F18: key = JES_KEY_F18; break; case XK_F19: key = JES_KEY_F19; break; case XK_F20: key = JES_KEY_F20; break; case XK_F21: key = JES_KEY_F21; break; case XK_F22: key = JES_KEY_F22; break; case XK_F23: key = JES_KEY_F23; break; case XK_F24: key = JES_KEY_F24; break; case XK_F25: key = JES_KEY_F25; break; case XK_F26: key = JES_KEY_F26; break; case XK_F27: key = JES_KEY_F27; break; case XK_F28: key = JES_KEY_F28; break; case XK_F29: key = JES_KEY_F29; break; case XK_F30: key = JES_KEY_F30; break; case XK_F31: key = JES_KEY_F31; break; case XK_Shift_L: key = JES_KEY_SHIFT_L; break; case XK_Shift_R: key = JES_KEY_SHIFT_R; break; case XK_Control_L: key = JES_KEY_CTRL_L; break; case XK_Control_R: key = JES_KEY_CTRL_R; break; case XK_Caps_Lock: key = JES_KEY_CAPS_LOCK; break; case XK_Shift_Lock: key = JES_KEY_SHIFT_LOCK;break; case XK_Meta_L: key = JES_KEY_META_L; break; case XK_Meta_R: key = JES_KEY_META_R; break; case XK_Alt_L: key = JES_KEY_ALT_L; break; case XK_Alt_R: key = JES_KEY_ALT_R; break; case XK_Super_L: key = JES_KEY_SUPER_L; break; case XK_Super_R: key = JES_KEY_SUPER_R; break; case XK_Home: key = JES_KEY_HOME; break; case XK_Left: key = JES_KEY_LEFT; break; case XK_Up: key = JES_KEY_UP; break; case XK_Right: key = JES_KEY_RIGHT; break; case XK_Down: key = JES_KEY_DOWN; break; case XK_Page_Up: key = JES_KEY_PAGE_UP; break; case XK_Page_Down: key = JES_KEY_PAGE_DOWN; break; case XK_End: key = JES_KEY_END; break; case XK_Begin: key = JES_KEY_BEGIN; break; case XK_Select: key = JES_KEY_SELECT; break; case XK_Print: key = JES_KEY_PRINT; break; case XK_Execute: key = JES_KEY_EXECUTE; break; case XK_Insert: key = JES_KEY_INSERT; break; case XK_Undo: key = JES_KEY_UNDO; break; case XK_Redo: key = JES_KEY_REDO; break; case XK_Menu: key = JES_KEY_MENU; break; case XK_Find: key = JES_KEY_FIND; break; case XK_Cancel: key = JES_KEY_CANCEL; break; case XK_Help: key = JES_KEY_HELP; break; case XK_Break: key = JES_KEY_BREAK; break; case XK_Num_Lock: key = JES_KEY_NUM_LOCK; break; case XK_KP_Space: key = JES_KEY_KP_SPACE; break; case XK_KP_Tab: key = JES_KEY_KP_TAB; break; case XK_KP_Enter: key = JES_KEY_KP_ENTER; break; case XK_KP_F1: key = JES_KEY_KP_F1; break; case XK_KP_F2: key = JES_KEY_KP_F2; break; case XK_KP_F3: key = JES_KEY_KP_F3; break; case XK_KP_F4: key = JES_KEY_KP_F4; break; case XK_KP_Home: key = JES_KEY_KP_HOME; break; case XK_KP_Left: key = JES_KEY_KP_LEFT; break; case XK_KP_Up: key = JES_KEY_KP_UP; break; case XK_KP_Right: key = JES_KEY_KP_RIGHT; break; case XK_KP_Down: key = JES_KEY_KP_DOWN; break; case XK_KP_Page_Up: key = JES_KEY_KP_PAGE_UP; break; case XK_KP_Page_Down: key = JES_KEY_KP_PAGE_DOWN; break; case XK_KP_End: key = JES_KEY_KP_END; break; case XK_KP_Begin: key = JES_KEY_KP_BEGIN; break; case XK_KP_Insert: key = JES_KEY_KP_INSERT; break; case XK_KP_Delete: key = JES_KEY_KP_DELETE; break; case XK_KP_Equal: key = JES_KEY_KP_EQUAL; break; case XK_KP_Multiply: key = JES_KEY_KP_MULTIPLY; break; case XK_KP_Add: key = JES_KEY_KP_ADD; break; case XK_KP_Separator: key = JES_KEY_KP_SEPARATOR; break; case XK_KP_Subtract: key = JES_KEY_KP_SUBTRACT; break; case XK_KP_Decimal: key = JES_KEY_KP_DECIMAL; break; case XK_KP_Divide: key = JES_KEY_KP_DIVIDE; break; case XK_KP_0: key = JES_KEY_KP_0; break; case XK_KP_1: key = JES_KEY_KP_1; break; case XK_KP_2: key = JES_KEY_KP_2; break; case XK_KP_3: key = JES_KEY_KP_3; break; case XK_KP_4: key = JES_KEY_KP_4; break; case XK_KP_5: key = JES_KEY_KP_5; break; case XK_KP_6: key = JES_KEY_KP_6; break; case XK_KP_7: key = JES_KEY_KP_7; break; case XK_KP_8: key = JES_KEY_KP_8; break; case XK_KP_9: key = JES_KEY_KP_9; break; } } /* OR in the modifier states */ key |= XStateToJtkKeyMods(x_state); return key; } #if 0 static Bool KeyRepeatCheckIfEvent(Display * display, XEvent * chkev, XPointer arg) { XEvent * release_event = (XEvent *)arg; if (chkev->type == KeyPress && chkev->xkey.keycode == release_event->xkey.keycode && chkev->xkey.time - release_event->xkey.time < 2) return True; return False; } /** * Check to see if this is a repeated key. */ static bool IsRepeatKey(Display * display, XEvent * event) { XEvent dummyev; if (XPending(display)) { if (XCheckIfEvent(display, &dummyev, KeyRepeatCheckIfEvent, (XPointer)event) == True) { return true; } } return false; } #endif static Bool MatchKeyPress(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 ProcessXKeyPressEvent(XEvent * x_event, Jtk_Event * event) { unsigned int x_keycode = x_event->xkey.keycode; event->type = JTK_EVENT_KEY_PRESS; event->key.repeat = false; event->key.key = XKeyToJtkKey(x_keycode, x_event->xkey.state); event->key.x_keycode = x_keycode; /* Remove any following keypress events for the same keycode from the X * queue. */ XEvent remove_event; while (XCheckIfEvent(g_display, &remove_event, MatchKeyPress, (XPointer)x_event) == True) { } return true; } /** * Process an X key release event. * * @param x_event * Pointer to the X event. * @param event * Pointer to the Jtk event. */ static bool ProcessXKeyReleaseEvent(XEvent * x_event, Jtk_Event * event) { #if 0 if (IsRepeatKey(g_display, x_event)) { return false; } StopKeyRepeat(x_event->xkey.keycode); #endif return false; } static Bool MatchButtonPress(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 ProcessXButtonPressEvent(XEvent * x_event, Jtk_Event * event) { event->type = JTK_EVENT_BUTTON_PRESS; event->button.mods = XStateToJtkKeyMods(x_event->xbutton.state); event->button.button = x_event->xbutton.button; /* If this is a mouse wheel scroll event, remove any following scroll * events. */ if ((event->button.button == 4) || (event->button.button == 5)) { XEvent remove_event; while (XCheckIfEvent(g_display, &remove_event, MatchButtonPress, (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 ProcessConfigureEvent(XEvent * x_event, Jtk_Event * event) { event->type = JTK_EVENT_WINDOW_RESIZE; event->resize.width = x_event->xconfigure.width; event->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 ProcessXClientMessageEvent(XEvent * x_event, Jtk_Event * 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 ProcessXEvent(XEvent * x_event, Jtk_Event * event) { switch (x_event->type) { case KeyPress: return ProcessXKeyPressEvent(x_event, event); case KeyRelease: return ProcessXKeyReleaseEvent(x_event, event); case ButtonPress: return ProcessXButtonPressEvent(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 ProcessConfigureEvent(x_event, event); case ClientMessage: return ProcessXClientMessageEvent(x_event, event); case MappingNotify: XRefreshKeyboardMapping(&x_event->xmapping); return false; } return false; } bool Jtk_CheckEvent(Jtk_Event * event) { /* First check for an X event. */ while (XPending(g_display) > 0) { XEvent x_event; XNextEvent(g_display, &x_event); if (ProcessXEvent(&x_event, event)) return true; } /* Next check if any timer has expired. */ size_t timer_id = Jtk_GetExpiredTimer(); if (timer_id != (size_t)-1) { #if 0 auto it = g_key_repeat_timers.find(timer_id); if (it != g_key_repeat_timers.end()) { event->type = JTK_EVENT_KEY_PRESS; event->key.repeat = true; event->key.key = XKeyToJtkKey(it->second); event->key.x_keycode = it->second; } else { #endif event->type = JTK_EVENT_TIMER; event->timer.timer_id = timer_id; event->timer.user1 = Jtk_GetTimerUserData(timer_id, 0u); event->timer.user2 = Jtk_GetTimerUserData(timer_id, 1u); #if 0 } #endif Jtk_ServiceTimer(timer_id); return true; } return false; } void Jtk_WaitEvent(Jtk_Event * event) { for (;;) { if (Jtk_CheckEvent(event)) return; /* Wait for something to happen. */ uint64_t time_to_wait = Jtk_TimeToNextTimerExpiration(); if (time_to_wait > MAX_WAIT_TIME) { 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, nullptr, nullptr, &tv); } } #if 0 void Jtk_BeginKeyRepeat(Jtk_KeyEvent * key_event, uint32_t delay, uint32_t interval) { StopKeyRepeat(key_event->x_keycode); size_t timer_id = Jtk_AddTimer(delay, interval, nullptr, nullptr); g_repeating_keys[key_event->x_keycode] = timer_id; g_key_repeat_timers[timer_id] = key_event->x_keycode; } #endif #endif