diff --git a/README.md b/README.md index 50f8c18..450c5ab 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,9 @@ iPad-style floating on-screen keyboard for Plasma Wayland. Qt 6, C++ core, QML U - Letters follow the system keyboard layout; the globe key switches it - Auto-capitalization, double-space → ". " (both optional, ⚙ on the bottom bar), backspace repeat → word delete - Slide sideways on space to move the cursor; long-press space = trackpad (lines too); slide from `.?123` onto a symbol and release -- Compact (iPad) or full (iPad Pro) keyboard: number row, punctuation, tab, caps lock, ctrl/alt/super, dead keys +- Compact (iPad) or full (iPad Pro) keyboard: number row, punctuation, tab, caps lock, ctrl/alt/super/alt gr, dead keys - Ctrl/Alt/Super stick like shift (tap, double-tap, hold) and turn keys into shortcuts; a tapped or held shift joins them (Shift+F5, Shift+Tab, Shift+arrows), auto-shift and caps lock don't +- Alt Gr (right of space in full mode) types the keymap's AltGr symbols (sk: Alt Gr+J = '), sticky like the others - Optional PC key blocks: function row, navigation (ins/home/pg up, del/end/pg dn, arrows), numeric keypad; any mix, typing keys included (⚙) - Floating: drag the bottom bar, resize from the corner; geometry is remembered per screen - Follows Plasma's virtual keyboard setting (tray toggle): off never pops up on focus, the default only after touch or a pen; "Always Show" and D-Bus `Show`/`Toggle` still show it @@ -46,7 +47,7 @@ Dev hooks: `SLOPOSK_AUTOTYPE=text` commits on focus; `SLOPOSK_SCREENSHOT=file.pn Letters come from the system keymap (what KWin sends to every client): its Q/A/Z rows become the three letter rows. Letters elsewhere, e.g. Slovak ľščťž on the number row, become long-press alternates. The globe key switches the system layout over `org.kde.keyboard`, so the physical keyboard follows. -In full mode every typing key comes from the keymap (number row, `[ ] \ ; ' , . /` or the layout's equivalents), with the shifted symbol shown above. Dead keys compose with the locale's XKB compose table. +In full mode every typing key comes from the keymap (number row, `[ ] \ ; ' , . /` or the layout's equivalents), with the shifted symbol shown above (and flickable); letters, whose shift only capitalizes, show their AltGr symbol instead. Dead keys compose with the locale's XKB compose table. Everything else is `layouts/template.json`; `~/.local/share/sloposk/template.json` replaces it. @@ -59,7 +60,7 @@ Everything else is `layouts/template.json`; `~/.local/share/sloposk/template.jso - `keysym`: sends the XKB keysym named by `keysym` (`Home`, `F5`, `Left`...) on press; `repeat` auto-repeats while held - `letters`: the keymap's letters in row `row` (0–2), with `flicks` handed out left to right - `keys`: every typing key in keymap row `row` (0 = number row … 3 = bottom) -- `modifier`: `"modifier": "ctrl" | "alt" | "super"`; `capslock` +- `modifier`: `"modifier": "ctrl" | "alt" | "super" | "altgr"`; `capslock` - Layer `full`: rows used instead of `rows` in full-keyboard mode - `stretch`: the key grows so its row is as wide as the layer's widest (letter counts vary by layout) - Top-level `alternates`: long-press choices per letter, after the keymap's own diff --git a/layouts/template.json b/layouts/template.json index 2815adf..2952782 100644 --- a/layouts/template.json +++ b/layouts/template.json @@ -109,7 +109,7 @@ {"type": "modifier", "modifier": "alt", "label": "alt", "width": 1.25}, {"type": "globe"}, {"type": "space", "width": 6, "stretch": true}, - {"type": "modifier", "modifier": "alt", "label": "alt", "width": 1.25}, + {"type": "modifier", "modifier": "altgr", "label": "alt gr", "width": 1.25}, {"type": "layer", "label": ".?123", "target": "numbers", "width": 1.25}, {"type": "dismiss"} ] diff --git a/src/backend/KWinFakeInput.cpp b/src/backend/KWinFakeInput.cpp index f97ec81..4519dc9 100644 --- a/src/backend/KWinFakeInput.cpp +++ b/src/backend/KWinFakeInput.cpp @@ -9,6 +9,8 @@ constexpr uint32_t DestroyVersion = 5; constexpr uint32_t Released = 0; constexpr uint32_t Pressed = 1; +constexpr uint32_t RightAlt = 100; // KEY_RIGHTALT + // Modifiers are held with real keys (evdev codes) so they combine with any keysym. struct ModifierKey { Qt::KeyboardModifier flag; @@ -65,9 +67,16 @@ void KWinFakeInput::sendKeysym(quint32 keysym, Qt::KeyboardModifiers modifiers) } } +// KWin resets the modifier state after each keysym except Shift, Ctrl, Alt +// and Super, so a held AltGr keysym would never shift the next key: hold +// the physical key instead (AltGr is right Alt on the layouts that have it). void KWinFakeInput::setKeyPressed(quint32 keysym, bool pressed) { - if (canSendKeysyms()) + if (!canSendKeysyms()) + return; + if (keysym == XKB_KEY_ISO_Level3_Shift) + keyboard_key(RightAlt, pressed ? Pressed : Released); + else keyboard_keysym(keysym, pressed ? Pressed : Released); } diff --git a/src/input/HardwareKeys.cpp b/src/input/HardwareKeys.cpp index 782d18a..7aabce7 100644 --- a/src/input/HardwareKeys.cpp +++ b/src/input/HardwareKeys.cpp @@ -12,6 +12,8 @@ quint32 modifierKeysym(const QString &name) return XKB_KEY_Control_L; if (name == u"alt") return XKB_KEY_Alt_L; + if (name == u"altgr") + return XKB_KEY_ISO_Level3_Shift; return XKB_KEY_Super_L; } @@ -80,17 +82,18 @@ bool HardwareKeys::isShiftHeld() const return m_down.contains(XKB_KEY_Shift_L); } -ShiftLevel HardwareKeys::shiftLevel() const +KeyLevel HardwareKeys::keyLevel() const { + const bool altGr = m_down.contains(XKB_KEY_ISO_Level3_Shift); if (isShiftHeld()) - return ShiftLevel::Shifted; - return m_capsLock ? ShiftLevel::Capitals : ShiftLevel::None; + return {ShiftLevel::Shifted, altGr}; + return {m_capsLock ? ShiftLevel::Capitals : ShiftLevel::None, altGr}; } QVariantMap HardwareKeys::modifierModes() const { QVariantMap modes; - for (const char *name : {"ctrl", "alt", "super"}) { + for (const char *name : {"ctrl", "alt", "super", "altgr"}) { const QString key = QString::fromLatin1(name); modes.insert(key, m_down.contains(modifierKeysym(key)) ? 1 : 0); } diff --git a/src/input/HardwareKeys.h b/src/input/HardwareKeys.h index d571e19..a0eba9d 100644 --- a/src/input/HardwareKeys.h +++ b/src/input/HardwareKeys.h @@ -9,7 +9,7 @@ class InputEngine; // The "hold keys" mode: the keyboard acts like a hardware one. A key is down // while the finger is, so apps see held keys (games, Shift+click, the -// system's key repeat). Shift, Ctrl, Alt and Super are plain keys, caps lock +// system's key repeat). Shift, Ctrl, Alt, Super and AltGr are plain keys, caps lock // is the system's, and dead keys compose in the app; none of the touch // conveniences (sticky shift, auto-capitalization, accents) apply. class HardwareKeys @@ -25,11 +25,11 @@ public: // Lets go of everything still down, e.g. when the mode is turned off. void releaseAll(); - // For the view: shift shown while held, caps lock while on. - ShiftLevel shiftLevel() const; + // For the view: shift and AltGr shown while held, caps lock while on. + KeyLevel keyLevel() const; bool isShiftHeld() const; bool isCapsLockOn() const { return m_capsLock; } - // "ctrl"/"alt"/"super" → 1 while held (ShiftState::Mode::Once), else 0. + // "ctrl"/"alt"/"super"/"altgr" → 1 while held (ShiftState::Mode::Once), else 0. QVariantMap modifierModes() const; private: diff --git a/src/input/KeyboardController.cpp b/src/input/KeyboardController.cpp index 47b2c18..6dba600 100644 --- a/src/input/KeyboardController.cpp +++ b/src/input/KeyboardController.cpp @@ -52,7 +52,7 @@ bool KeyboardController::holdKey(const KeyDef &key, bool pressed) if (!m_options.holdKeys || !HardwareKeys::handles(key)) return false; pressed ? m_hardware.press(key) : m_hardware.release(key); - m_model.setShiftLevel(shiftLevel()); + m_model.setKeyLevel(keyLevel()); Q_EMIT shiftChanged(); Q_EMIT modifiersChanged(); return true; @@ -68,7 +68,7 @@ void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs) break; case KeyType::Modifier: m_modifiers.press(key.modifier, timeMs); - Q_EMIT modifiersChanged(); + modifiersUpdated(); break; case KeyType::Backspace: if (m_deadKeys.isPending()) { @@ -110,7 +110,7 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin break; case KeyType::Modifier: m_modifiers.release(key.modifier); - Q_EMIT modifiersChanged(); + modifiersUpdated(); break; case KeyType::Backspace: m_backspaceRepeater.stop(); @@ -147,7 +147,7 @@ void KeyboardController::keyCancelled(const KeyDef &key) changeShift([&] { m_shift.release(); }); else if (key.type == KeyType::Modifier) { m_modifiers.release(key.modifier); - Q_EMIT modifiersChanged(); + modifiersUpdated(); } else if (key.type == KeyType::Backspace) m_backspaceRepeater.stop(); else if (key.type == KeyType::Keysym) @@ -210,7 +210,7 @@ void KeyboardController::setOptions(const TypingOptions &options) m_shift = {}; } m_options = options; - m_model.setShiftLevel(shiftLevel()); + m_model.setKeyLevel(keyLevel()); Q_EMIT shiftChanged(); Q_EMIT modifiersChanged(); autoCapitalize(); @@ -227,7 +227,12 @@ void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, cons return; } - const ShiftLevel level = shiftLevel(); + const KeyLevel level = keyLevel(); + if (level.altGr) { + // Used up by this key, like a one-shot shift. + m_modifiers.keyTyped(); + modifiersUpdated(); + } if (const quint32 dead = key.deadAt(level); dead && output == KeyOutput::Primary) { if (const QString done = m_deadKeys.press(dead); !done.isEmpty()) typeText(done); @@ -260,7 +265,7 @@ Qt::KeyboardModifiers KeyboardController::shortcutModifiers() const void KeyboardController::useModifiers() { m_modifiers.keyTyped(); - Q_EMIT modifiersChanged(); + modifiersUpdated(); if (m_shift.appliesToShortcuts()) changeShift([&] { m_shift.characterTyped(); }); } @@ -309,23 +314,30 @@ void KeyboardController::autoCapitalize() changeShift([&] { m_shift.setAutomatic(capitalize); }); } -ShiftLevel KeyboardController::shiftLevel() const +KeyLevel KeyboardController::keyLevel() const { if (m_options.holdKeys) - return m_hardware.shiftLevel(); + return m_hardware.keyLevel(); + const bool altGr = m_modifiers.isAltGrActive(); if (m_shift.appliesToShortcuts()) - return ShiftLevel::Shifted; - return m_shift.isActive() ? ShiftLevel::Capitals : ShiftLevel::None; + return {ShiftLevel::Shifted, altGr}; + return {m_shift.isActive() ? ShiftLevel::Capitals : ShiftLevel::None, altGr}; +} + +void KeyboardController::modifiersUpdated() +{ + m_model.setKeyLevel(keyLevel()); // AltGr changes the labels + Q_EMIT modifiersChanged(); } template void KeyboardController::changeShift(Change change) { const ShiftState::Mode mode = m_shift.mode(); - const ShiftLevel level = shiftLevel(); + const KeyLevel level = keyLevel(); change(); - if (m_shift.mode() == mode && shiftLevel() == level) + if (m_shift.mode() == mode && keyLevel() == level) return; - m_model.setShiftLevel(shiftLevel()); + m_model.setKeyLevel(keyLevel()); Q_EMIT shiftChanged(); } diff --git a/src/input/KeyboardController.h b/src/input/KeyboardController.h index cf7a94f..d12f529 100644 --- a/src/input/KeyboardController.h +++ b/src/input/KeyboardController.h @@ -25,7 +25,7 @@ enum class KeyOutput { Primary, Flick, Alternate }; // receives key presses and text-field context, and talks to an InputEngine. // // With Ctrl, Alt or Super active, keys become shortcuts (keysyms with those -// modifiers) instead of text. Shift joins shortcuts and non-text keys +// modifiers) instead of text; AltGr instead types a key's AltGr symbol. Shift joins shortcuts and non-text keys // (Shift+F5, Shift+Tab, Shift+arrows) only when the user turned it on, not // when auto-capitalization did. class KeyboardController : public QObject @@ -34,7 +34,7 @@ class KeyboardController : public QObject QML_ELEMENT QML_UNCREATABLE("Created in C++") Q_PROPERTY(ShiftMode shiftMode READ shiftMode NOTIFY shiftChanged) - // "ctrl"/"alt"/"super" → ShiftMode + // "ctrl"/"alt"/"super"/"altgr" → ShiftMode Q_PROPERTY(QVariantMap modifierModes READ modifierModes NOTIFY modifiersChanged) public: @@ -78,10 +78,12 @@ private: // Sends a non-text key or shortcut with the active modifiers, then uses them up. void sendKey(quint32 keysym); Qt::KeyboardModifiers shortcutModifiers() const; - ShiftLevel shiftLevel() const; + KeyLevel keyLevel() const; // Hold-keys mode: forwards the key to m_hardware; false if it isn't one it presses. bool holdKey(const KeyDef &key, bool pressed); void useModifiers(); + // The modifiers changed: refreshes the labels and tells the view. + void modifiersUpdated(); void deleteBackward(bool wholeWord); void sendRepeatedKeysym(); void stopRepeating(const KeyDef &key); diff --git a/src/input/Modifiers.cpp b/src/input/Modifiers.cpp index ae1645e..7c0fe3b 100644 --- a/src/input/Modifiers.cpp +++ b/src/input/Modifiers.cpp @@ -40,7 +40,12 @@ Qt::KeyboardModifiers Modifiers::active() const if (entry.state.isActive()) flags |= entry.flag; } - return flags; + return flags & ~Qt::GroupSwitchModifier; +} + +bool Modifiers::isAltGrActive() const +{ + return m_entries.back().state.isActive(); } QVariantMap Modifiers::modes() const diff --git a/src/input/Modifiers.h b/src/input/Modifiers.h index 8f1cc3e..841d6bd 100644 --- a/src/input/Modifiers.h +++ b/src/input/Modifiers.h @@ -8,19 +8,22 @@ #include -// Ctrl, Alt and Super, each sticky like shift (see ShiftState): tap for the -// next key, double-tap to lock, hold while typing with another finger. +// Ctrl, Alt, Super and AltGr, each sticky like shift (see ShiftState): tap +// for the next key, double-tap to lock, hold while typing with another finger. +// The first three turn keys into shortcuts; AltGr picks their AltGr symbols. class Modifiers { public: - // Names as used in layouts: "ctrl", "alt", "super". + // Names as used in layouts: "ctrl", "alt", "super", "altgr". void press(const QString &name, qint64 timeMs); void release(const QString &name); // A key was typed: one-shot modifiers are used up. void keyTyped(); void clear(); + // Ctrl, Alt and Super: what makes a key a shortcut. Qt::KeyboardModifiers active() const; + bool isAltGrActive() const; // name → ShiftState::Mode as int, for the view. QVariantMap modes() const; @@ -32,9 +35,10 @@ private: }; ShiftState *find(const QString &name); - std::array m_entries{{ + std::array m_entries{{ {"ctrl", Qt::ControlModifier, {}}, {"alt", Qt::AltModifier, {}}, {"super", Qt::MetaModifier, {}}, + {"altgr", Qt::GroupSwitchModifier, {}}, }}; }; diff --git a/src/layout/KeymapKeys.cpp b/src/layout/KeymapKeys.cpp index 6a4cd7e..f79e417 100644 --- a/src/layout/KeymapKeys.cpp +++ b/src/layout/KeymapKeys.cpp @@ -66,6 +66,8 @@ std::optional keyAt(xkb_keymap *keymap, int code, xkb_layout_index_t return std::nullopt; if (!readLevel(symbolAt(keymap, code, layout, 1), key.upper, key.deadUpper)) key.upper = key.lower.toUpper(); + readLevel(symbolAt(keymap, code, layout, 2), key.altGr, key.deadAltGr); + readLevel(symbolAt(keymap, code, layout, 3), key.altGrUpper, key.deadAltGrUpper); return key; } diff --git a/src/layout/KeymapKeys.h b/src/layout/KeymapKeys.h index 49cce1b..45090f1 100644 --- a/src/layout/KeymapKeys.h +++ b/src/layout/KeymapKeys.h @@ -5,8 +5,8 @@ struct xkb_keymap; -// One typing key of a keymap layout, at its two shift levels. A level is -// either text or a dead key (then `text` is its spacing accent, for labels). +// One typing key of a keymap layout, at its shift and AltGr levels. A level +// is either text or a dead key (then the text is its spacing accent, for labels). struct KeymapKey { enum class Position { Regular, Backslash, LessGreater }; // BKSL, LSGT: not on every keyboard @@ -15,6 +15,10 @@ struct KeymapKey { QString upper; quint32 deadLower = 0; // dead keysym, 0 if the level types text quint32 deadUpper = 0; + QString altGr; // third level (AltGr), empty if it types nothing + QString altGrUpper; // fourth level (AltGr+Shift) + quint32 deadAltGr = 0; + quint32 deadAltGrUpper = 0; bool isLetter() const; }; diff --git a/src/layout/Layout.h b/src/layout/Layout.h index 7dc2900..311e088 100644 --- a/src/layout/Layout.h +++ b/src/layout/Layout.h @@ -27,6 +27,14 @@ enum class KeyType { // (Shift+1 = !), as on a PC. enum class ShiftLevel { None, Capitals, Shifted }; +// Shift plus AltGr, which picks a key's third (and, shifted, fourth) keymap level. +struct KeyLevel { + KeyLevel(ShiftLevel shift = ShiftLevel::None, bool altGr = false) : shift(shift), altGr(altGr) {} + ShiftLevel shift; + bool altGr; + friend bool operator==(const KeyLevel &, const KeyLevel &) = default; +}; + struct KeyDef { KeyType type = KeyType::Character; QString label; // shown and typed (for character keys) @@ -37,6 +45,10 @@ struct KeyDef { QString modifier; // Modifier only: "ctrl", "alt" or "super" quint32 dead = 0; // character keys: dead keysym typed instead of `label`, 0 if none quint32 deadShifted = 0; // same for the shifted level + QString altGr; // keymap keys: typed with AltGr, empty if nothing + QString altGrShifted; // with AltGr and shift, empty if nothing + quint32 deadAltGr = 0; // dead keysyms of those levels (then the texts are their accents) + quint32 deadAltGrShifted = 0; qreal width = 1.0; // in key units bool stretch = false; // grows so the row is as wide as its block's widest quint32 keysym = 0; // Keysym keys only @@ -47,17 +59,34 @@ struct KeyDef { QStringList flicks; // flick symbol per letter, left to right // Text this key shows/types; only character keys change. - QString text(ShiftLevel level) const + // Without an AltGr symbol, AltGr leaves the key as it is (as on a PC). + QString text(KeyLevel level) const { - if (level == ShiftLevel::None || type != KeyType::Character) + if (type != KeyType::Character) return label; - if (level == ShiftLevel::Capitals) + if (usesAltGr(level)) + return usesAltGrShifted(level) ? altGrShifted : altGr; + if (level.shift == ShiftLevel::None) + return label; + if (level.shift == ShiftLevel::Capitals) return isLetter() ? label.toUpper() : label; return shifted.isEmpty() ? label.toUpper() : shifted; } // The dead keysym typed at `level`, 0 if it types text. - quint32 deadAt(ShiftLevel level) const { return level == ShiftLevel::Shifted ? deadShifted : dead; } + quint32 deadAt(KeyLevel level) const + { + if (usesAltGr(level)) + return usesAltGrShifted(level) ? deadAltGrShifted : deadAltGr; + return level.shift == ShiftLevel::Shifted ? deadShifted : dead; + } bool isLetter() const { return !dead && label.size() == 1 && label.front().isLetter(); } + +private: + bool usesAltGr(KeyLevel level) const { return level.altGr && !altGr.isEmpty(); } + bool usesAltGrShifted(KeyLevel level) const + { + return level.shift == ShiftLevel::Shifted && !altGrShifted.isEmpty(); + } }; using Row = QList; diff --git a/src/layout/LayoutBuilder.cpp b/src/layout/LayoutBuilder.cpp index d0d25b0..531accb 100644 --- a/src/layout/LayoutBuilder.cpp +++ b/src/layout/LayoutBuilder.cpp @@ -34,6 +34,10 @@ KeyDef characterKey(const KeymapKey &source) key.shifted = source.upper; key.dead = source.deadLower; key.deadShifted = source.deadUpper; + key.altGr = source.altGr; + key.altGrShifted = source.altGrUpper; + key.deadAltGr = source.deadAltGr; + key.deadAltGrShifted = source.deadAltGrUpper; return key; } @@ -54,7 +58,8 @@ QList letterKeys(const KeymapKeys &keymap, int row, const QStringList &f return keys; } -// A full row: every typing key, the shifted symbol shown (and flickable) above. +// A full row: every typing key, the shifted symbol shown (and flickable) above; +// for letters, whose shift only capitalizes, the AltGr symbol (sk: ' on j). // `withLessGreater` tells whether the ISO key left of Z is among them. QList rowKeys(const KeymapKeys &keymap, int row, bool *withLessGreater) { @@ -76,6 +81,8 @@ QList rowKeys(const KeymapKeys &keymap, int row, bool *withLessGreater) // Show (and flick for) a shifted level that isn't just the capital letter: ! on 1, " on sk ô. if (!source.deadUpper && source.upper != source.lower && source.upper != source.lower.toUpper()) key.flick = source.upper; + else if (!source.deadAltGr && source.altGr != source.lower) + key.flick = source.altGr; keys.append(key); } return keys; diff --git a/src/layout/LayoutLoader.cpp b/src/layout/LayoutLoader.cpp index 8dbe304..04a3633 100644 --- a/src/layout/LayoutLoader.cpp +++ b/src/layout/LayoutLoader.cpp @@ -13,7 +13,8 @@ namespace { constexpr int LetterRows = 3; constexpr int KeyRows = 4; -const QStringList ModifierNames = {QStringLiteral("ctrl"), QStringLiteral("alt"), QStringLiteral("super")}; +const QStringList ModifierNames = {QStringLiteral("ctrl"), QStringLiteral("alt"), QStringLiteral("super"), + QStringLiteral("altgr")}; QStringList toStringList(const QJsonValue &value) { diff --git a/src/layout/LayoutModel.cpp b/src/layout/LayoutModel.cpp index 8828f81..58fe978 100644 --- a/src/layout/LayoutModel.cpp +++ b/src/layout/LayoutModel.cpp @@ -48,9 +48,9 @@ QVariant LayoutModel::data(const QModelIndex &index, int role) const const KeyDef &key = m_keys.at(index.row()); const QRectF rect = m_rects.value(index.row()); switch (role) { - case LabelRole: return key.text(m_shiftLevel); - // A shifted key already shows its shifted symbol (! on 1): don't repeat it. - case FlickLabelRole: return m_shiftLevel == ShiftLevel::Shifted && key.flick == key.shifted ? QString() : key.flick; + case LabelRole: return key.text(m_keyLevel); + // A shifted or AltGr key may already show its flick symbol (! on 1): don't repeat it. + case FlickLabelRole: return key.flick == key.text(m_keyLevel) ? QString() : key.flick; case ModifierRole: return key.modifier; case KeyTypeRole: return keyTypeName(key.type); case KeyXRole: return rect.x(); @@ -133,11 +133,11 @@ void LayoutModel::setSize(QSizeF size) Q_EMIT dataChanged(index(0), index(rowCount() - 1), {KeyXRole, KeyYRole, KeyWidthRole, KeyHeightRole}); } -void LayoutModel::setShiftLevel(ShiftLevel level) +void LayoutModel::setKeyLevel(KeyLevel level) { - if (level == m_shiftLevel) + if (level == m_keyLevel) return; - m_shiftLevel = level; + m_keyLevel = level; if (!m_keys.isEmpty()) Q_EMIT dataChanged(index(0), index(rowCount() - 1), {LabelRole, FlickLabelRole}); } diff --git a/src/layout/LayoutModel.h b/src/layout/LayoutModel.h index e8cda7c..d27a9cd 100644 --- a/src/layout/LayoutModel.h +++ b/src/layout/LayoutModel.h @@ -51,7 +51,7 @@ public: // Section ids to show; MainSectionId stands for the layers. void setVisibleSections(const QStringList &ids); void setSize(QSizeF size); - void setShiftLevel(ShiftLevel level); + void setKeyLevel(KeyLevel level); void setPressed(int index, bool pressed); void setFlickProgress(int index, qreal progress); @@ -59,7 +59,7 @@ public: int keyAt(QPointF pos) const; const KeyDef &key(int index) const { return m_keys.at(index); } QRectF keyRect(int index) const { return m_rects.value(index); } - bool isUppercase() const { return m_shiftLevel != ShiftLevel::None; } + bool isUppercase() const { return m_keyLevel.shift != ShiftLevel::None; } Q_SIGNALS: void layoutChanged(); @@ -75,7 +75,7 @@ private: QStringList m_visibleSections{MainSectionId}; QList m_blocks; QSizeF m_size; - ShiftLevel m_shiftLevel = ShiftLevel::None; + KeyLevel m_keyLevel; QList m_keys; QList m_rects; diff --git a/tests/tst_controller.cpp b/tests/tst_controller.cpp index 43de52c..f6ea324 100644 --- a/tests/tst_controller.cpp +++ b/tests/tst_controller.cpp @@ -328,6 +328,29 @@ private Q_SLOTS: QCOMPARE(controller->modifierModes().value(u"alt"_s).toInt(), int(KeyboardController::ShiftMode::Off)); } + void altGrTypesTheThirdLevel() + { + KeyDef j = makeKey(KeyType::Character, u"j"_s); + j.altGr = u"'"_s; + tap(modifierKey(u"altgr"_s)); + tap(j); + QCOMPARE(engine.output, u"'"); + QVERIFY(engine.keysyms.isEmpty()); // text, not an Alt shortcut + tap(j); // one-shot + QCOMPARE(engine.output, u"'j"); + } + + void altGrDeadKeyComposes() + { + KeyDef three = makeKey(KeyType::Character, u"3"_s); + three.altGr = u"^"_s; + three.deadAltGr = XKB_KEY_dead_circumflex; + tap(modifierKey(u"altgr"_s)); + tap(three); + tap(makeKey(KeyType::Character, u"a"_s)); + QCOMPARE(engine.output, u"â"); + } + void tappedShiftAppliesToFunctionKeys() { tap(makeKey(KeyType::Character, u"a"_s)); // mid-sentence: no auto-shift diff --git a/tests/tst_layoutbuilder.cpp b/tests/tst_layoutbuilder.cpp index d680d97..f2a3b23 100644 --- a/tests/tst_layoutbuilder.cpp +++ b/tests/tst_layoutbuilder.cpp @@ -137,6 +137,19 @@ private Q_SLOTS: QCOMPARE(letterKey(sk, u"l"_s).alternates.first(), u"ľ"); } + void fullModeLettersFlickToTheirAltGrSymbol() + { + const Layout sk = TestLayouts::build("sk", KeyboardMode::Full); + const KeyDef j = letterKey(sk, u"j"_s); + QCOMPARE(j.altGr, u"'"); + QCOMPARE(j.flick, u"'"); + QCOMPARE(j.text({ShiftLevel::None, true}), u"'"); + QCOMPARE(letterKey(sk, u"ô"_s).flick, u"\""); // keys with a shifted symbol keep it + const KeyDef plus = letterKey(sk, u"+"_s); + QCOMPARE(plus.deadAt({ShiftLevel::Shifted, true}), quint32(XKB_KEY_dead_tilde)); + QCOMPARE(plus.text({ShiftLevel::None, true}), u"!"); + } + void noPlaceholdersRemain() { for (const Layer &layer : TestLayouts::build("us").layers) {