diff --git a/src/input/DeadKeys.cpp b/src/input/DeadKeys.cpp index fb9ab5c..270bf4c 100644 --- a/src/input/DeadKeys.cpp +++ b/src/input/DeadKeys.cpp @@ -37,11 +37,36 @@ DeadKeys::DeadKeys(const QString &locale) DeadKeys::~DeadKeys() = default; -void DeadKeys::press(quint32 deadKeysym) +QString DeadKeys::press(quint32 deadKeysym) { - m_pending += deadKeyText(deadKeysym); - if (m_state) - xkb_compose_state_feed(m_state.get(), deadKeysym); + if (!m_state) { + m_pending += deadKeyText(deadKeysym); + return {}; + } + xkb_compose_state_feed(m_state.get(), deadKeysym); + switch (xkb_compose_state_get_status(m_state.get())) { + case XKB_COMPOSE_COMPOSED: { + const QString composed = composedText(); + cancel(); + return composed; + } + case XKB_COMPOSE_CANCELLED: { + // No sequence goes on like this: type the old accents, start anew. + const QString accents = m_pending; + cancel(); + return accents + press(deadKeysym); + } + default: + m_pending += deadKeyText(deadKeysym); + return {}; + } +} + +QString DeadKeys::composedText() const +{ + char utf8[64]; + xkb_compose_state_get_utf8(m_state.get(), utf8, sizeof utf8); + return QString::fromUtf8(utf8); } QString DeadKeys::compose(const QString &text) @@ -50,11 +75,8 @@ QString DeadKeys::compose(const QString &text) if (m_state && !text.isEmpty()) { const char32_t ucs = text.toUcs4().first(); xkb_compose_state_feed(m_state.get(), xkb_utf32_to_keysym(ucs)); - if (xkb_compose_state_get_status(m_state.get()) == XKB_COMPOSE_COMPOSED) { - char utf8[64]; - xkb_compose_state_get_utf8(m_state.get(), utf8, sizeof utf8); - result = QString::fromUtf8(utf8) + text.mid(QString::fromUcs4(&ucs, 1).size()); - } + if (xkb_compose_state_get_status(m_state.get()) == XKB_COMPOSE_COMPOSED) + result = composedText() + text.mid(QString::fromUcs4(&ucs, 1).size()); } cancel(); return result; diff --git a/src/input/DeadKeys.h b/src/input/DeadKeys.h index 05215ea..5709cf0 100644 --- a/src/input/DeadKeys.h +++ b/src/input/DeadKeys.h @@ -17,14 +17,18 @@ public: ~DeadKeys(); bool isPending() const { return !m_pending.isEmpty(); } - // A dead key was typed: starts or extends the sequence. - void press(quint32 deadKeysym); + // A dead key was typed: starts or extends the sequence. Returns text to + // type now: what two dead keys composed (´´ → ´), or the accents of a + // sequence the new one can't continue; empty while still pending. + QString press(quint32 deadKeysym); // `text` typed while pending: the composed character, or the accents // followed by `text` when nothing composes. Ends the sequence. QString compose(const QString &text); void cancel(); private: + QString composedText() const; + std::unique_ptr m_context; std::unique_ptr m_table; std::unique_ptr m_state; diff --git a/src/input/KeyboardController.cpp b/src/input/KeyboardController.cpp index 9c7eb96..47b2c18 100644 --- a/src/input/KeyboardController.cpp +++ b/src/input/KeyboardController.cpp @@ -220,16 +220,19 @@ void KeyboardController::typeCharacter(const KeyDef &key, KeyOutput output, cons { const QString base = key.type == KeyType::Space ? QStringLiteral(" ") : key.label; if (m_modifiers.active()) { - // A shortcut such as Ctrl+C: the key's unshifted symbol, with modifiers. + // A shortcut such as Ctrl+C: the key's unshifted symbol, with modifiers; + // for a dead key its dead keysym, as the physical key sends. m_deadKeys.cancel(); - sendKey(xkb_utf32_to_keysym(base.toLower().toUcs4().value(0))); + sendKey(key.dead ? key.dead : xkb_utf32_to_keysym(base.toLower().toUcs4().value(0))); return; } const ShiftLevel level = shiftLevel(); if (const quint32 dead = key.deadAt(level); dead && output == KeyOutput::Primary) { - m_deadKeys.press(dead); - changeShift([&] { m_shift.characterTyped(); }); + if (const QString done = m_deadKeys.press(dead); !done.isEmpty()) + typeText(done); + else + changeShift([&] { m_shift.characterTyped(); }); return; } diff --git a/tests/tst_controller.cpp b/tests/tst_controller.cpp index 64126b3..11a1d89 100644 --- a/tests/tst_controller.cpp +++ b/tests/tst_controller.cpp @@ -373,6 +373,16 @@ private Q_SLOTS: QCOMPARE(engine.output, u"+Ľ2"); } + void ctrlWithDeadKeySendsTheDeadKeysym() + { + KeyDef acute = makeKey(KeyType::Character, u"´"_s); + acute.dead = XKB_KEY_dead_acute; + tap(modifierKey(u"ctrl"_s)); + tap(acute); + QCOMPARE(engine.keysyms, QList{XKB_KEY_dead_acute}); + QCOMPARE(engine.lastModifiers, Qt::ControlModifier); + } + void deadKeyComposesWithNextCharacter() { KeyDef acute = makeKey(KeyType::Character, u"´"_s); diff --git a/tests/tst_deadkeys.cpp b/tests/tst_deadkeys.cpp index e535c67..6f830cb 100644 --- a/tests/tst_deadkeys.cpp +++ b/tests/tst_deadkeys.cpp @@ -30,6 +30,16 @@ private Q_SLOTS: QCOMPARE(dead.compose(u"q"_s), u"ˇq"); } + void twoDeadKeysComposeAtOnce() + { + DeadKeys dead(u"en_US.UTF-8"_s); + QVERIFY(dead.press(XKB_KEY_dead_acute).isEmpty()); + QCOMPARE(dead.press(XKB_KEY_dead_acute), u"´"); + QVERIFY(!dead.isPending()); + dead.press(XKB_KEY_dead_acute); // a fresh sequence + QCOMPARE(dead.compose(u"e"_s), u"é"); + } + void cancelForgetsTheAccent() { DeadKeys dead(u"en_US.UTF-8"_s);