qt: phase 7 — extract XkbState into qt/src/input/
The XkbState process-singleton (libxkbcommon keymap + state used to compute the unshifted codepoint, sided modifier bits, lock state, and layout-consumed mods for libghostty's kitty encoder) was embedded ~180 lines deep in GhosttySurface.cpp. The class is fully self-contained — pure xkbcommon + XkbTracker, no QWidget — so it moves cleanly into a new qt/src/input/ directory next to its sibling XkbTracker. GhosttySurface.cpp now just #includes "input/XkbState.h" and uses XkbState::instance() at four sendKey callsites. The <xkbcommon/xkbcommon.h> direct include drops out (the surface TU no longer touches xkb directly; the header pulls it in for the xkb_mod_index_t members). GhosttySurface.cpp shrinks ~180 lines. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
c4ab838859
commit
f070cff504
|
|
@ -112,6 +112,7 @@ add_executable(ghastty
|
|||
src/GhosttySurface.cpp
|
||||
src/GlobalShortcuts.cpp
|
||||
src/InspectorWindow.cpp
|
||||
src/input/XkbState.cpp
|
||||
src/MainWindow.cpp
|
||||
src/OverlayScrollbar.cpp
|
||||
src/quickterm/QuickTerminal.cpp
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "GhosttySurface.h"
|
||||
|
||||
#include "config/Config.h"
|
||||
#include "input/XkbState.h"
|
||||
#include "InspectorWindow.h"
|
||||
#include "MainWindow.h"
|
||||
#include "OverlayScrollbar.h"
|
||||
|
|
@ -49,8 +50,6 @@
|
|||
#include <QUrl>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
GhosttySurface::GhosttySurface(ghostty_app_t app, MainWindow *owner,
|
||||
ghostty_surface_t parent_surface)
|
||||
: m_app(app), m_owner(owner), m_parentSurface(parent_surface) {
|
||||
|
|
@ -697,203 +696,6 @@ void GhosttySurface::premultiplyFramebuffer() {
|
|||
|
||||
// --- input ----------------------------------------------------------
|
||||
|
||||
// Wraps a libxkbcommon keymap + state derived from the system's XKB
|
||||
// defaults (XKB_DEFAULT_LAYOUT etc.). We need this for two things:
|
||||
//
|
||||
// 1. The unshifted codepoint a key would produce with no modifiers —
|
||||
// libghostty's kitty encoder uses it to find a key entry for
|
||||
// printable keys (without it, punctuation falls into a fallback
|
||||
// that mis-encodes release events).
|
||||
//
|
||||
// 2. Which modifiers the layout "consumed" to produce the event's
|
||||
// text — e.g. Shift+; → ":" consumes Shift. The encoder uses this
|
||||
// to decide between plain text and a modifier-bearing CSI; without
|
||||
// it Shift+punctuation gets emitted as a kitty CSI the shell can't
|
||||
// decode (Shift+letter happens to work because A-Z survive that
|
||||
// path).
|
||||
//
|
||||
// THREAD SAFETY: this is a process singleton accessed only from the Qt
|
||||
// GUI thread (Qt key events are dispatched there, and so is libghostty's
|
||||
// inputMethodEvent forwarding). consumedMods mutates m_query, so a
|
||||
// second thread would race; do not call from worker threads.
|
||||
class XkbState {
|
||||
public:
|
||||
static XkbState &instance() {
|
||||
static XkbState self;
|
||||
return self;
|
||||
}
|
||||
|
||||
// Level-0 (unshifted) Unicode codepoint for `keycode`, or 0 if the
|
||||
// key has no associated UTF-32 (function keys, modifiers, etc.).
|
||||
//
|
||||
// Uses the live keymap from XkbTracker (synced via wl_keyboard) so
|
||||
// the active layout group is honored. A us+ru user gets the
|
||||
// correct codepoint per active group, instead of always us.
|
||||
uint32_t unshiftedCodepoint(uint32_t keycode) const {
|
||||
syncFromTracker();
|
||||
if (!m_unshifted) return 0;
|
||||
const xkb_keysym_t sym =
|
||||
xkb_state_key_get_one_sym(m_unshifted, keycode);
|
||||
if (sym == XKB_KEY_NoSymbol) return 0;
|
||||
return xkb_keysym_to_utf32(sym);
|
||||
}
|
||||
|
||||
// Side bits for the libghostty mods bitfield, derived from a
|
||||
// keycode — used so that pressing Right-Shift sets BOTH the
|
||||
// unsided GHOSTTY_MODS_SHIFT and the GHOSTTY_MODS_SHIFT_RIGHT bit
|
||||
// (a left-side keycode sets only the unsided bit). macOS and GTK
|
||||
// populate sided bits this way; Qt was leaving them empty so
|
||||
// bindings that distinguish left-vs-right modifier keys couldn't
|
||||
// fire.
|
||||
ghostty_input_mods_e sideBitsForKeycode(uint32_t keycode) const {
|
||||
syncFromTracker();
|
||||
if (!m_unshifted) return GHOSTTY_MODS_NONE;
|
||||
const xkb_keysym_t sym =
|
||||
xkb_state_key_get_one_sym(m_unshifted, keycode);
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
switch (sym) {
|
||||
case XKB_KEY_Shift_R: r |= GHOSTTY_MODS_SHIFT_RIGHT; break;
|
||||
case XKB_KEY_Control_R: r |= GHOSTTY_MODS_CTRL_RIGHT; break;
|
||||
// Both Alt_R and ISO_Level3_Shift (AltGr) are right-Alt physically.
|
||||
case XKB_KEY_Alt_R:
|
||||
case XKB_KEY_ISO_Level3_Shift: r |= GHOSTTY_MODS_ALT_RIGHT; break;
|
||||
case XKB_KEY_Super_R:
|
||||
case XKB_KEY_Hyper_R:
|
||||
case XKB_KEY_Meta_R: r |= GHOSTTY_MODS_SUPER_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
// Caps Lock / Num Lock state from the live wl_keyboard tracker.
|
||||
ghostty_input_mods_e lockMods() const {
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
if (XkbTracker *t = XkbTracker::instance()) {
|
||||
if (t->capsLockOn()) r |= GHOSTTY_MODS_CAPS;
|
||||
if (t->numLockOn()) r |= GHOSTTY_MODS_NUM;
|
||||
}
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
// Modifiers consumed by the layout to produce `keycode`'s text given
|
||||
// `mods` are depressed. Returns the consumed subset, expressed as
|
||||
// ghostty mod bits. Mutates m_query (mutable) — see thread-safety
|
||||
// note on the class.
|
||||
ghostty_input_mods_e consumedMods(uint32_t keycode,
|
||||
ghostty_input_mods_e mods) const {
|
||||
syncFromTracker();
|
||||
if (!m_query) return GHOSTTY_MODS_NONE;
|
||||
xkb_mod_mask_t depressed = 0;
|
||||
if ((mods & GHOSTTY_MODS_SHIFT) && m_idxShift != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxShift);
|
||||
if ((mods & GHOSTTY_MODS_CTRL) && m_idxCtrl != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxCtrl);
|
||||
if ((mods & GHOSTTY_MODS_ALT) && m_idxAlt != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxAlt);
|
||||
if ((mods & GHOSTTY_MODS_SUPER) && m_idxSuper != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxSuper);
|
||||
// Use the live group from the tracker so a layout switch (e.g.
|
||||
// us↔ru) takes effect immediately.
|
||||
const uint32_t group =
|
||||
XkbTracker::instance() ? XkbTracker::instance()->activeGroup() : 0;
|
||||
xkb_state_update_mask(m_query, depressed, 0, 0, 0, 0, group);
|
||||
const xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
|
||||
m_query, keycode, XKB_CONSUMED_MODE_XKB);
|
||||
// Reset so the next query starts from no-mods.
|
||||
xkb_state_update_mask(m_query, 0, 0, 0, 0, 0, group);
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
if (m_idxShift != XKB_MOD_INVALID && (consumed & (1u << m_idxShift)))
|
||||
r |= GHOSTTY_MODS_SHIFT;
|
||||
if (m_idxCtrl != XKB_MOD_INVALID && (consumed & (1u << m_idxCtrl)))
|
||||
r |= GHOSTTY_MODS_CTRL;
|
||||
if (m_idxAlt != XKB_MOD_INVALID && (consumed & (1u << m_idxAlt)))
|
||||
r |= GHOSTTY_MODS_ALT;
|
||||
if (m_idxSuper != XKB_MOD_INVALID && (consumed & (1u << m_idxSuper)))
|
||||
r |= GHOSTTY_MODS_SUPER;
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
private:
|
||||
// Lazy: build/rebuild m_unshifted + m_query from the live keymap.
|
||||
// Called from every public method; cheap when the keymap pointer
|
||||
// hasn't changed (a single comparison + early-return).
|
||||
void syncFromTracker() const {
|
||||
XkbTracker *t = XkbTracker::instance();
|
||||
xkb_keymap *liveKm = t ? t->keymap() : nullptr;
|
||||
xkb_keymap *km = liveKm ? liveKm : m_fallbackKeymap;
|
||||
|
||||
if (!km && t && t->ctx()) {
|
||||
// Compositor hasn't sent a keymap yet (early startup). Build a
|
||||
// throwaway from XKB defaults so the first key event isn't
|
||||
// dropped; it will be replaced on the next syncFromTracker
|
||||
// call once the tracker has the live keymap.
|
||||
m_fallbackKeymap = xkb_keymap_new_from_names(
|
||||
t->ctx(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
km = m_fallbackKeymap;
|
||||
}
|
||||
if (!km || km == m_keymap) {
|
||||
// Already synced (or no keymap available at all).
|
||||
// Update the live group on m_unshifted so the level-0 lookup
|
||||
// honors the active layout, even when the keymap pointer
|
||||
// hasn't changed.
|
||||
if (m_unshifted && t) {
|
||||
xkb_state_update_mask(m_unshifted, 0, 0, 0, 0, 0, t->activeGroup());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The live keymap was rebuilt by the tracker (or we're picking
|
||||
// up the first one). Drop our derived states and rebuild. Take
|
||||
// an extra ref on the keymap while it's our cached identity so
|
||||
// the xkb allocator can't free it and reuse the same address
|
||||
// for a different keymap (the ABA hazard the previous comment
|
||||
// hand-waved away).
|
||||
if (m_unshifted) xkb_state_unref(m_unshifted);
|
||||
if (m_query) xkb_state_unref(m_query);
|
||||
if (m_keymap) xkb_keymap_unref(m_keymap);
|
||||
m_keymap = xkb_keymap_ref(km);
|
||||
m_unshifted = xkb_state_new(km);
|
||||
m_query = xkb_state_new(km);
|
||||
m_idxShift = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_SHIFT);
|
||||
m_idxCtrl = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CTRL);
|
||||
m_idxAlt = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_ALT);
|
||||
m_idxSuper = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_LOGO);
|
||||
if (t)
|
||||
xkb_state_update_mask(m_unshifted, 0, 0, 0, 0, 0, t->activeGroup());
|
||||
}
|
||||
|
||||
XkbState() = default;
|
||||
|
||||
~XkbState() {
|
||||
// Run on process exit when the static is destroyed. The OS would
|
||||
// reclaim regardless, but explicit teardown silences leak checkers
|
||||
// and documents the ownership chain.
|
||||
if (m_query) xkb_state_unref(m_query);
|
||||
if (m_unshifted) xkb_state_unref(m_unshifted);
|
||||
if (m_keymap) xkb_keymap_unref(m_keymap);
|
||||
if (m_fallbackKeymap) xkb_keymap_unref(m_fallbackKeymap);
|
||||
}
|
||||
|
||||
XkbState(const XkbState &) = delete;
|
||||
XkbState &operator=(const XkbState &) = delete;
|
||||
|
||||
// The keymap our derived states were built from. We hold a ref
|
||||
// here (taken in syncFromTracker, released on rebuild and in dtor)
|
||||
// so the xkb allocator can't free + reuse the address while we
|
||||
// still cache it as our identity.
|
||||
mutable struct xkb_keymap *m_keymap = nullptr;
|
||||
// Throwaway keymap from XKB defaults, built when the live keymap
|
||||
// hasn't arrived yet. Owned. Released in dtor; never replaced.
|
||||
mutable struct xkb_keymap *m_fallbackKeymap = nullptr;
|
||||
mutable struct xkb_state *m_unshifted = nullptr; // no-mods state
|
||||
// Reused across consumedMods calls (mutated then reset).
|
||||
mutable struct xkb_state *m_query = nullptr;
|
||||
mutable xkb_mod_index_t m_idxShift = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxCtrl = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxAlt = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxSuper = XKB_MOD_INVALID;
|
||||
};
|
||||
|
||||
void GhosttySurface::sendKey(QKeyEvent *ev, ghostty_input_action_e action) {
|
||||
if (!m_surface) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
#include "XkbState.h"
|
||||
|
||||
#include "../XkbTracker.h"
|
||||
|
||||
XkbState &XkbState::instance() {
|
||||
static XkbState self;
|
||||
return self;
|
||||
}
|
||||
|
||||
XkbState::~XkbState() {
|
||||
// Run on process exit when the static is destroyed. The OS would
|
||||
// reclaim regardless, but explicit teardown silences leak checkers
|
||||
// and documents the ownership chain.
|
||||
if (m_query) xkb_state_unref(m_query);
|
||||
if (m_unshifted) xkb_state_unref(m_unshifted);
|
||||
if (m_keymap) xkb_keymap_unref(m_keymap);
|
||||
if (m_fallbackKeymap) xkb_keymap_unref(m_fallbackKeymap);
|
||||
}
|
||||
|
||||
uint32_t XkbState::unshiftedCodepoint(uint32_t keycode) const {
|
||||
syncFromTracker();
|
||||
if (!m_unshifted) return 0;
|
||||
const xkb_keysym_t sym =
|
||||
xkb_state_key_get_one_sym(m_unshifted, keycode);
|
||||
if (sym == XKB_KEY_NoSymbol) return 0;
|
||||
return xkb_keysym_to_utf32(sym);
|
||||
}
|
||||
|
||||
ghostty_input_mods_e XkbState::sideBitsForKeycode(uint32_t keycode) const {
|
||||
syncFromTracker();
|
||||
if (!m_unshifted) return GHOSTTY_MODS_NONE;
|
||||
const xkb_keysym_t sym =
|
||||
xkb_state_key_get_one_sym(m_unshifted, keycode);
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
switch (sym) {
|
||||
case XKB_KEY_Shift_R: r |= GHOSTTY_MODS_SHIFT_RIGHT; break;
|
||||
case XKB_KEY_Control_R: r |= GHOSTTY_MODS_CTRL_RIGHT; break;
|
||||
// Both Alt_R and ISO_Level3_Shift (AltGr) are right-Alt physically.
|
||||
case XKB_KEY_Alt_R:
|
||||
case XKB_KEY_ISO_Level3_Shift: r |= GHOSTTY_MODS_ALT_RIGHT; break;
|
||||
case XKB_KEY_Super_R:
|
||||
case XKB_KEY_Hyper_R:
|
||||
case XKB_KEY_Meta_R: r |= GHOSTTY_MODS_SUPER_RIGHT; break;
|
||||
default: break;
|
||||
}
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
ghostty_input_mods_e XkbState::lockMods() const {
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
if (XkbTracker *t = XkbTracker::instance()) {
|
||||
if (t->capsLockOn()) r |= GHOSTTY_MODS_CAPS;
|
||||
if (t->numLockOn()) r |= GHOSTTY_MODS_NUM;
|
||||
}
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
ghostty_input_mods_e XkbState::consumedMods(uint32_t keycode,
|
||||
ghostty_input_mods_e mods) const {
|
||||
syncFromTracker();
|
||||
if (!m_query) return GHOSTTY_MODS_NONE;
|
||||
xkb_mod_mask_t depressed = 0;
|
||||
if ((mods & GHOSTTY_MODS_SHIFT) && m_idxShift != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxShift);
|
||||
if ((mods & GHOSTTY_MODS_CTRL) && m_idxCtrl != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxCtrl);
|
||||
if ((mods & GHOSTTY_MODS_ALT) && m_idxAlt != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxAlt);
|
||||
if ((mods & GHOSTTY_MODS_SUPER) && m_idxSuper != XKB_MOD_INVALID)
|
||||
depressed |= (1u << m_idxSuper);
|
||||
// Use the live group from the tracker so a layout switch (e.g.
|
||||
// us↔ru) takes effect immediately.
|
||||
const uint32_t group =
|
||||
XkbTracker::instance() ? XkbTracker::instance()->activeGroup() : 0;
|
||||
xkb_state_update_mask(m_query, depressed, 0, 0, 0, 0, group);
|
||||
const xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2(
|
||||
m_query, keycode, XKB_CONSUMED_MODE_XKB);
|
||||
// Reset so the next query starts from no-mods.
|
||||
xkb_state_update_mask(m_query, 0, 0, 0, 0, 0, group);
|
||||
int r = GHOSTTY_MODS_NONE;
|
||||
if (m_idxShift != XKB_MOD_INVALID && (consumed & (1u << m_idxShift)))
|
||||
r |= GHOSTTY_MODS_SHIFT;
|
||||
if (m_idxCtrl != XKB_MOD_INVALID && (consumed & (1u << m_idxCtrl)))
|
||||
r |= GHOSTTY_MODS_CTRL;
|
||||
if (m_idxAlt != XKB_MOD_INVALID && (consumed & (1u << m_idxAlt)))
|
||||
r |= GHOSTTY_MODS_ALT;
|
||||
if (m_idxSuper != XKB_MOD_INVALID && (consumed & (1u << m_idxSuper)))
|
||||
r |= GHOSTTY_MODS_SUPER;
|
||||
return static_cast<ghostty_input_mods_e>(r);
|
||||
}
|
||||
|
||||
void XkbState::syncFromTracker() const {
|
||||
XkbTracker *t = XkbTracker::instance();
|
||||
xkb_keymap *liveKm = t ? t->keymap() : nullptr;
|
||||
xkb_keymap *km = liveKm ? liveKm : m_fallbackKeymap;
|
||||
|
||||
if (!km && t && t->ctx()) {
|
||||
// Compositor hasn't sent a keymap yet (early startup). Build a
|
||||
// throwaway from XKB defaults so the first key event isn't
|
||||
// dropped; it will be replaced on the next syncFromTracker
|
||||
// call once the tracker has the live keymap.
|
||||
m_fallbackKeymap = xkb_keymap_new_from_names(
|
||||
t->ctx(), nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
km = m_fallbackKeymap;
|
||||
}
|
||||
if (!km || km == m_keymap) {
|
||||
// Already synced (or no keymap available at all).
|
||||
// Update the live group on m_unshifted so the level-0 lookup
|
||||
// honors the active layout, even when the keymap pointer
|
||||
// hasn't changed.
|
||||
if (m_unshifted && t) {
|
||||
xkb_state_update_mask(m_unshifted, 0, 0, 0, 0, 0, t->activeGroup());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The live keymap was rebuilt by the tracker (or we're picking
|
||||
// up the first one). Drop our derived states and rebuild. Take
|
||||
// an extra ref on the keymap while it's our cached identity so
|
||||
// the xkb allocator can't free it and reuse the same address
|
||||
// for a different keymap (the ABA hazard the previous comment
|
||||
// hand-waved away).
|
||||
if (m_unshifted) xkb_state_unref(m_unshifted);
|
||||
if (m_query) xkb_state_unref(m_query);
|
||||
if (m_keymap) xkb_keymap_unref(m_keymap);
|
||||
m_keymap = xkb_keymap_ref(km);
|
||||
m_unshifted = xkb_state_new(km);
|
||||
m_query = xkb_state_new(km);
|
||||
m_idxShift = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_SHIFT);
|
||||
m_idxCtrl = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_CTRL);
|
||||
m_idxAlt = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_ALT);
|
||||
m_idxSuper = xkb_keymap_mod_get_index(km, XKB_MOD_NAME_LOGO);
|
||||
if (t)
|
||||
xkb_state_update_mask(m_unshifted, 0, 0, 0, 0, 0, t->activeGroup());
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
#include "ghostty.h"
|
||||
|
||||
// Wraps a libxkbcommon keymap + state derived from the live keymap
|
||||
// XkbTracker syncs via wl_keyboard (with a fallback to the system
|
||||
// XKB defaults until the compositor's keymap arrives). We need this
|
||||
// for two things:
|
||||
//
|
||||
// 1. The unshifted codepoint a key would produce with no modifiers —
|
||||
// libghostty's kitty encoder uses it to find a key entry for
|
||||
// printable keys (without it, punctuation falls into a fallback
|
||||
// that mis-encodes release events).
|
||||
//
|
||||
// 2. Which modifiers the layout "consumed" to produce the event's
|
||||
// text — e.g. Shift+; → ":" consumes Shift. The encoder uses
|
||||
// this to decide between plain text and a modifier-bearing CSI;
|
||||
// without it Shift+punctuation gets emitted as a kitty CSI the
|
||||
// shell can't decode (Shift+letter happens to work because A-Z
|
||||
// survive that path).
|
||||
//
|
||||
// THREAD SAFETY: this is a process singleton accessed only from the
|
||||
// Qt GUI thread (Qt key events are dispatched there, and so is
|
||||
// libghostty's inputMethodEvent forwarding). consumedMods mutates
|
||||
// internal state; do not call from worker threads.
|
||||
class XkbState {
|
||||
public:
|
||||
static XkbState &instance();
|
||||
|
||||
// Level-0 (unshifted) Unicode codepoint for `keycode`, or 0 if the
|
||||
// key has no associated UTF-32 (function keys, modifiers, etc.).
|
||||
// Honors the active layout group from the live tracker so a us+ru
|
||||
// user gets the correct codepoint per active group, not always us.
|
||||
uint32_t unshiftedCodepoint(uint32_t keycode) const;
|
||||
|
||||
// Side bits for the libghostty mods bitfield, derived from a
|
||||
// keycode — pressing Right-Shift sets BOTH the unsided
|
||||
// GHOSTTY_MODS_SHIFT and GHOSTTY_MODS_SHIFT_RIGHT bit (a left-side
|
||||
// keycode sets only the unsided bit). macOS and GTK populate sided
|
||||
// bits this way; Qt was leaving them empty so bindings that
|
||||
// distinguish left-vs-right modifier keys couldn't fire.
|
||||
ghostty_input_mods_e sideBitsForKeycode(uint32_t keycode) const;
|
||||
|
||||
// Caps Lock / Num Lock state from the live wl_keyboard tracker.
|
||||
ghostty_input_mods_e lockMods() const;
|
||||
|
||||
// Modifiers consumed by the layout to produce `keycode`'s text
|
||||
// given `mods` are depressed. Returns the consumed subset
|
||||
// expressed as ghostty mod bits.
|
||||
ghostty_input_mods_e consumedMods(uint32_t keycode,
|
||||
ghostty_input_mods_e mods) const;
|
||||
|
||||
XkbState(const XkbState &) = delete;
|
||||
XkbState &operator=(const XkbState &) = delete;
|
||||
|
||||
private:
|
||||
XkbState() = default;
|
||||
~XkbState();
|
||||
|
||||
// Build / rebuild the derived states from the live keymap. Cheap
|
||||
// when the keymap pointer is unchanged (one comparison + return).
|
||||
void syncFromTracker() const;
|
||||
|
||||
// The keymap our derived states were built from. A ref taken in
|
||||
// syncFromTracker (released on rebuild and in dtor) keeps the xkb
|
||||
// allocator from freeing + reusing the address while we still
|
||||
// cache it as our identity.
|
||||
mutable struct xkb_keymap *m_keymap = nullptr;
|
||||
// Throwaway keymap from XKB defaults, built when the live keymap
|
||||
// hasn't arrived yet. Owned. Released in dtor; never replaced.
|
||||
mutable struct xkb_keymap *m_fallbackKeymap = nullptr;
|
||||
mutable struct xkb_state *m_unshifted = nullptr; // no-mods state
|
||||
// Reused across consumedMods calls (mutated then reset).
|
||||
mutable struct xkb_state *m_query = nullptr;
|
||||
mutable xkb_mod_index_t m_idxShift = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxCtrl = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxAlt = XKB_MOD_INVALID;
|
||||
mutable xkb_mod_index_t m_idxSuper = XKB_MOD_INVALID;
|
||||
};
|
||||
Loading…
Reference in New Issue