summaryrefslogtreecommitdiff
path: root/src/xcb_xkb.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@opera.com>2026-01-12 23:06:20 +0100
committerJoel Klinghed <the_jk@opera.com>2026-01-12 23:06:20 +0100
commitdfeb19b0a83b8ce57d28bf94a4f8d129993d1064 (patch)
treed352908df286058059e306c350d89a07c67049eb /src/xcb_xkb.cc
Initial commit
Diffstat (limited to 'src/xcb_xkb.cc')
-rw-r--r--src/xcb_xkb.cc167
1 files changed, 167 insertions, 0 deletions
diff --git a/src/xcb_xkb.cc b/src/xcb_xkb.cc
new file mode 100644
index 0000000..3d00537
--- /dev/null
+++ b/src/xcb_xkb.cc
@@ -0,0 +1,167 @@
+#include "xcb_xkb.hh"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <xcb/xcb.h>
+#include <xcb/xcb_event.h>
+#define explicit dont_use_cxx_explicit
+#include <xcb/xkb.h>
+#undef explicit
+#include <utility>
+#include <xcb/xproto.h>
+#include <xkbcommon/xkbcommon-x11.h>
+#include <xkbcommon/xkbcommon.h>
+
+namespace xcb {
+
+namespace {
+
+struct KeymapDeleter {
+ void operator()(xkb_keymap* keymap) const {
+ if (keymap)
+ xkb_keymap_unref(keymap);
+ }
+};
+
+struct StateDeleter {
+ void operator()(xkb_state* state) const {
+ if (state)
+ xkb_state_unref(state);
+ }
+};
+
+struct ContextDeleter {
+ void operator()(xkb_context* context) const {
+ if (context)
+ xkb_context_unref(context);
+ }
+};
+
+class KeyboardImpl : public Keyboard {
+ public:
+ KeyboardImpl() = default;
+
+ bool init(xcb_connection_t* conn) {
+ if (!xkb_x11_setup_xkb_extension(
+ conn, XKB_X11_MIN_MAJOR_XKB_VERSION, XKB_X11_MIN_MINOR_XKB_VERSION,
+ XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, nullptr, nullptr,
+ &first_xkb_event_, nullptr))
+ return false;
+
+ ctx_.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
+ if (!ctx_)
+ return false;
+ device_id_ = xkb_x11_get_core_keyboard_device_id(conn);
+ if (device_id_ == -1)
+ return false;
+ if (!update_keymap(conn))
+ return false;
+ select_events(conn);
+ return true;
+ }
+
+ bool handle_event(xcb_connection_t* conn,
+ xcb_generic_event_t* event) override {
+ if (XCB_EVENT_RESPONSE_TYPE(event) == first_xkb_event_) {
+ auto* xkb_event = reinterpret_cast<xkb_generic_event_t*>(event);
+ if (std::cmp_equal(xkb_event->deviceID, device_id_)) {
+ switch (xkb_event->xkbType) {
+ case XCB_XKB_NEW_KEYBOARD_NOTIFY: {
+ auto* e =
+ reinterpret_cast<xcb_xkb_new_keyboard_notify_event_t*>(event);
+ if (e->changed & XCB_XKB_NKN_DETAIL_KEYCODES)
+ update_keymap(conn);
+ break;
+ }
+ case XCB_XKB_MAP_NOTIFY:
+ update_keymap(conn);
+ break;
+ case XCB_XKB_STATE_NOTIFY: {
+ auto* e = reinterpret_cast<xcb_xkb_state_notify_event_t*>(event);
+ xkb_state_update_mask(state_.get(), e->baseMods, e->latchedMods,
+ e->lockedMods, e->baseGroup, e->latchedGroup,
+ e->lockedGroup);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ std::string get_utf8(xcb_key_press_event_t* event) override {
+ char tmp[16];
+ xkb_state_key_get_utf8(state_.get(), event->detail, tmp, sizeof(tmp));
+ return {tmp};
+ }
+
+ private:
+ struct xkb_generic_event_t {
+ uint8_t response_type;
+ uint8_t xkbType;
+ uint16_t sequence;
+ xcb_timestamp_t time;
+ uint8_t deviceID;
+ };
+
+ bool update_keymap(xcb_connection_t* conn) {
+ auto* keymap = xkb_x11_keymap_new_from_device(ctx_.get(), conn, device_id_,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ if (!keymap)
+ return false;
+ auto* state = xkb_x11_state_new_from_device(keymap, conn, device_id_);
+ if (!state) {
+ xkb_keymap_unref(keymap);
+ return false;
+ }
+ keymap_.reset(keymap);
+ state_.reset(state);
+ return true;
+ }
+
+ void select_events(xcb_connection_t* conn) const {
+ static const uint16_t new_keyboard_details = XCB_XKB_NKN_DETAIL_KEYCODES;
+ static const uint16_t 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;
+ static const uint16_t 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;
+
+ xcb_xkb_select_events_details_t details = {};
+ details.affectNewKeyboard = new_keyboard_details;
+ details.newKeyboardDetails = new_keyboard_details;
+ details.affectState = state_details;
+ details.stateDetails = state_details;
+
+ xcb_xkb_select_events_aux(conn, device_id_,
+ XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+ XCB_XKB_EVENT_TYPE_MAP_NOTIFY |
+ XCB_XKB_EVENT_TYPE_STATE_NOTIFY,
+ 0, 0, map_parts, map_parts, &details);
+ }
+
+ std::unique_ptr<xkb_context, ContextDeleter> ctx_;
+ std::unique_ptr<xkb_keymap, KeymapDeleter> keymap_;
+ std::unique_ptr<xkb_state, StateDeleter> state_;
+ uint8_t first_xkb_event_;
+ int32_t device_id_;
+};
+
+} // namespace
+
+std::unique_ptr<Keyboard> Keyboard::create(xcb_connection_t* conn) {
+ auto ret = std::make_unique<KeyboardImpl>();
+ if (ret->init(conn))
+ return ret;
+ return nullptr;
+}
+
+} // namespace xcb