aboutsummaryrefslogtreecommitdiff
path: root/src/platform/gui/xcb/Input.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/gui/xcb/Input.cpp')
-rw-r--r--src/platform/gui/xcb/Input.cpp279
1 files changed, 279 insertions, 0 deletions
diff --git a/src/platform/gui/xcb/Input.cpp b/src/platform/gui/xcb/Input.cpp
new file mode 100644
index 00000000..9b6d76c2
--- /dev/null
+++ b/src/platform/gui/xcb/Input.cpp
@@ -0,0 +1,279 @@
+#include "cru/platform/gui/xcb/Input.h"
+#include "cru/base/Exception.h"
+#include "cru/base/Guard.h"
+#include "cru/platform/gui/xcb/UiApplication.h"
+
+#include <xcb/xcb.h>
+#include <xkbcommon/xkbcommon-x11.h>
+#include <xkbcommon/xkbcommon.h>
+#include <bitset>
+#include <climits>
+#include <unordered_map>
+#include <utility>
+
+namespace cru::platform::gui::xcb {
+// Refer to
+// https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#keysym_encoding
+KeyCode XorgKeysymToCruKeyCode(xcb_keysym_t keysym) {
+ if (keysym >= 'A' && keysym <= 'Z') {
+ return KeyCode(static_cast<int>(KeyCode::A) + (keysym - 'A'));
+ }
+
+ if (keysym >= 'a' && keysym <= 'z') {
+ return KeyCode(static_cast<int>(KeyCode::A) + (keysym - 'a'));
+ }
+
+ if (keysym >= '0' && keysym <= '9') {
+ return KeyCode(static_cast<int>(KeyCode::N0) + (keysym - '0'));
+ }
+
+ if (keysym >= 0xFFB0 && keysym <= 0xFFB9) {
+ return KeyCode(static_cast<int>(KeyCode::NumPad0) + (keysym - 0xFFB0));
+ }
+
+ if (keysym >= 0xFFBE && keysym <= 0xFFC9) {
+ return KeyCode(static_cast<int>(KeyCode::F1) + (keysym - 0xFFBE));
+ }
+
+ switch (keysym) {
+#define CRU_DEFINE_KEYCODE_MAP(keysym, cru) \
+ case keysym: \
+ return cru;
+
+ CRU_DEFINE_KEYCODE_MAP(',', KeyCode::Comma)
+ CRU_DEFINE_KEYCODE_MAP('.', KeyCode::Period)
+ CRU_DEFINE_KEYCODE_MAP('/', KeyCode::Slash)
+ CRU_DEFINE_KEYCODE_MAP(';', KeyCode::Semicolon)
+ CRU_DEFINE_KEYCODE_MAP('\'', KeyCode::Quote)
+ CRU_DEFINE_KEYCODE_MAP('{', KeyCode::LeftSquareBracket)
+ CRU_DEFINE_KEYCODE_MAP('}', KeyCode::RightSquareBracket)
+ CRU_DEFINE_KEYCODE_MAP('-', KeyCode::Minus)
+ CRU_DEFINE_KEYCODE_MAP('=', KeyCode::Equal)
+ CRU_DEFINE_KEYCODE_MAP('\\', KeyCode::BackSlash)
+ CRU_DEFINE_KEYCODE_MAP(0xFF1B, KeyCode::Escape)
+ CRU_DEFINE_KEYCODE_MAP(0xFF09, KeyCode::Tab)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE5, KeyCode::CapsLock)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE1, KeyCode::LeftShift)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE2, KeyCode::RightShift)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE3, KeyCode::LeftCtrl)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE4, KeyCode::RightCtrl)
+ CRU_DEFINE_KEYCODE_MAP(0xFFE9, KeyCode::LeftAlt)
+ CRU_DEFINE_KEYCODE_MAP(0xFFEA, KeyCode::RightAlt)
+ CRU_DEFINE_KEYCODE_MAP(0xFF08, KeyCode::Backspace)
+ CRU_DEFINE_KEYCODE_MAP(0xFF0D, KeyCode::Return)
+ CRU_DEFINE_KEYCODE_MAP(0xFFFF, KeyCode::Delete)
+ CRU_DEFINE_KEYCODE_MAP(0xFF50, KeyCode::Home)
+ CRU_DEFINE_KEYCODE_MAP(0xFF57, KeyCode::End)
+ CRU_DEFINE_KEYCODE_MAP(0xFF55, KeyCode::PageUp)
+ CRU_DEFINE_KEYCODE_MAP(0xFF56, KeyCode::PageDown)
+ CRU_DEFINE_KEYCODE_MAP(0xFF51, KeyCode::Left)
+ CRU_DEFINE_KEYCODE_MAP(0xFF53, KeyCode::Right)
+ CRU_DEFINE_KEYCODE_MAP(0xFF52, KeyCode::Up)
+ CRU_DEFINE_KEYCODE_MAP(0xFF54, KeyCode::Down)
+ CRU_DEFINE_KEYCODE_MAP(' ', KeyCode::Space)
+ default:
+ return KeyCode::Unknown;
+ }
+}
+
+std::vector<xcb_keysym_t> XorgKeycodeToKeysyms(XcbUiApplication *application,
+ xcb_keycode_t keycode) {
+ auto connection = application->GetXcbConnection();
+ auto setup = xcb_get_setup(connection);
+ auto min_keycode = setup->min_keycode;
+ auto max_keycode = setup->max_keycode;
+
+ // Get keyboard mapping
+ auto mapping_cookie = xcb_get_keyboard_mapping(connection, keycode, 1);
+ auto mapping_reply = FreeLater(
+ xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL));
+
+ if (!mapping_reply) {
+ throw XcbException("Cannot get keyboard mapping.");
+ }
+
+ auto keysyms_per_keycode = mapping_reply->keysyms_per_keycode;
+ auto *keysyms = xcb_get_keyboard_mapping_keysyms(mapping_reply);
+
+ std::vector<xcb_keysym_t> result;
+ for (int i = 0; i < keysyms_per_keycode; i++) {
+ result.push_back(keysyms[i]);
+ }
+ return result;
+}
+
+KeyCode XorgKeycodeToCruKeyCode(XcbUiApplication *application,
+ xcb_keycode_t keycode) {
+ auto keysyms = XorgKeycodeToKeysyms(application, keycode);
+
+ for (auto keysym : keysyms) {
+ auto result = XorgKeysymToCruKeyCode(keysym);
+ if (result != KeyCode::Unknown) return result;
+ }
+
+ return KeyCode::Unknown;
+}
+
+namespace {
+using KeymapBitset =
+ std::bitset<sizeof(std::declval<xcb_query_keymap_reply_t>().keys) *
+ CHAR_BIT>;
+
+KeymapBitset GetXorgKeymap(xcb_connection_t *connection) {
+ auto keymap_cookie = xcb_query_keymap(connection);
+ auto keymap_reply =
+ FreeLater(xcb_query_keymap_reply(connection, keymap_cookie, NULL));
+
+ if (!keymap_reply) {
+ throw XcbException("Cannot get keymap.");
+ }
+
+ KeymapBitset result;
+ int counter = 0;
+ for (auto member : keymap_reply->keys) {
+ for (int i = 0; i < sizeof(member); i++) {
+ result[counter] = member & (1 << i);
+ counter++;
+ }
+ }
+
+ return result;
+}
+} // namespace
+
+std::unordered_map<KeyCode, bool> GetKeyboardState(
+ XcbUiApplication *application) {
+ auto connection = application->GetXcbConnection();
+ auto setup = xcb_get_setup(connection);
+ auto min_keycode = setup->min_keycode;
+ auto max_keycode = setup->max_keycode;
+
+ // Get keyboard mapping
+ auto mapping_cookie = xcb_get_keyboard_mapping(connection, min_keycode,
+ max_keycode - min_keycode + 1);
+ auto mapping_reply = FreeLater(
+ xcb_get_keyboard_mapping_reply(connection, mapping_cookie, NULL));
+
+ if (!mapping_reply) {
+ throw XcbException("Cannot get keyboard mapping.");
+ }
+
+ auto keysyms_per_keycode = mapping_reply->keysyms_per_keycode;
+ auto *keysyms = xcb_get_keyboard_mapping_keysyms(mapping_reply);
+
+ auto keymap = GetXorgKeymap(connection);
+
+ std::unordered_map<KeyCode, bool> result;
+
+ for (xcb_keycode_t i = min_keycode; i <= max_keycode; i++) {
+ auto keysyms_for_this = keysyms + (i - min_keycode) * keysyms_per_keycode;
+ for (int j = 0; j < keysyms_per_keycode; j++) {
+ auto keycode = XorgKeysymToCruKeyCode(keysyms_for_this[j]);
+ if (keycode != KeyCode::Unknown) {
+ result[keycode] = keymap[i];
+ }
+ }
+ }
+
+ return result;
+}
+
+// Though X provides GetModifierMapping, it cannot get ALT state.
+KeyModifier GetCurrentKeyModifiers(XcbUiApplication *application) {
+ KeyModifier result{};
+ auto state = GetKeyboardState(application);
+ if (state[KeyCode::LeftShift] || state[KeyCode::RightShift]) {
+ result |= KeyModifiers::Shift;
+ }
+ if (state[KeyCode::LeftCtrl] || state[KeyCode::RightCtrl]) {
+ result |= KeyModifiers::Ctrl;
+ }
+ if (state[KeyCode::LeftAlt] || state[KeyCode::RightAlt]) {
+ result |= KeyModifiers::Alt;
+ }
+ return result;
+}
+
+KeyModifier ConvertModifiersOfEvent(uint32_t mask) {
+ // const char *MODIFIERS[] = {
+ // "Shift", "Lock", "Ctrl", "Alt", "Mod2", "Mod3", "Mod4",
+ // "Mod5", "Button1", "Button2", "Button3", "Button4", "Button5"};
+ constexpr KeyModifier MODIFIERS[] = {
+ KeyModifiers::Shift, KeyModifiers::None, KeyModifiers::Ctrl,
+ KeyModifiers::Alt, KeyModifiers::None, KeyModifiers::None,
+ KeyModifiers::None, KeyModifiers::None, KeyModifiers::None,
+ KeyModifiers::None, KeyModifiers::None, KeyModifiers::None,
+ KeyModifiers::None,
+ };
+
+ KeyModifier result;
+ for (auto iter = std::begin(MODIFIERS); mask; mask >>= 1, ++iter) {
+ if (mask & 1) {
+ result |= *iter;
+ }
+ }
+ return result;
+}
+
+XcbKeyboardManager::XcbKeyboardManager(XcbUiApplication *application)
+ : application_(application) {
+ xkb_x11_setup_xkb_extension(
+ application->GetXcbConnection(), XKB_X11_MIN_MAJOR_XKB_VERSION,
+ XKB_X11_MIN_MINOR_XKB_VERSION, XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS,
+ nullptr, nullptr, nullptr, nullptr);
+
+ xkb_context_ = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (!xkb_context_) {
+ throw PlatformException("Failed to call xkb_context_new.");
+ }
+
+ auto device_id =
+ xkb_x11_get_core_keyboard_device_id(application->GetXcbConnection());
+ if (device_id == -1) {
+ throw PlatformException(
+ "Failed to call xkb_x11_get_core_keyboard_device_id.");
+ }
+
+ xkb_keymap_ = xkb_x11_keymap_new_from_device(
+ xkb_context_, application->GetXcbConnection(), device_id,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!xkb_keymap_) {
+ throw PlatformException("Failed to call xkb_x11_keymap_new_from_device.");
+ }
+
+ xkb_state_ = xkb_x11_state_new_from_device(
+ xkb_keymap_, application->GetXcbConnection(), device_id);
+ if (!xkb_state_) {
+ throw PlatformException("Failed to call xkb_x11_state_new_from_device.");
+ }
+}
+
+XcbKeyboardManager::~XcbKeyboardManager() {
+ xkb_state_unref(xkb_state_);
+ xkb_keymap_unref(xkb_keymap_);
+ xkb_context_unref(xkb_context_);
+}
+
+std::string XcbKeyboardManager::KeycodeToUtf8(xcb_keycode_t keycode) {
+ auto size = xkb_state_key_get_utf8(xkb_state_, keycode, NULL, 0);
+ if (size <= 0) {
+ return {};
+ }
+ std::string buffer(size + 1, 0);
+ xkb_state_key_get_utf8(xkb_state_, keycode, buffer.data(), size + 1);
+ buffer.resize(size);
+ return buffer;
+}
+
+std::string XcbKeyboardManager::KeysymToUtf8(xcb_keysym_t keysym) {
+ auto size = xkb_keysym_to_utf8(keysym, NULL, 0);
+ if (size <= 0) {
+ return {};
+ }
+ std::string buffer(size + 1, 0);
+ xkb_keysym_to_utf8(keysym, buffer.data(), size + 1);
+ buffer.resize(size);
+ return buffer;
+}
+
+} // namespace cru::platform::gui::xcb