#include "KeyboardController.h" #include "TextRules.h" #include "engine/InputEngine.h" #include "layout/LayoutModel.h" #include // ShiftMode mirrors ShiftState::Mode for QML. static_assert(int(KeyboardController::ShiftMode::Off) == int(ShiftState::Mode::Off)); static_assert(int(KeyboardController::ShiftMode::Once) == int(ShiftState::Mode::Once)); static_assert(int(KeyboardController::ShiftMode::Locked) == int(ShiftState::Mode::Locked)); namespace { // Enough history for the text rules; fields may report megabytes. constexpr qsizetype MaxTextBefore = 256; } KeyboardController::KeyboardController(InputEngine &engine, LayoutModel &model, QObject *parent) : QObject(parent) , m_engine(engine) , m_model(model) { connect(&m_backspaceRepeater, &KeyRepeater::repeat, this, &KeyboardController::deleteBackward); } KeyboardController::ShiftMode KeyboardController::shiftMode() const { return ShiftMode(m_shift.mode()); } void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs) { switch (key.type) { case KeyType::Shift: changeShift([&] { m_shift.press(timeMs); }); break; case KeyType::Backspace: deleteBackward(false); m_backspaceRepeater.start(); break; case KeyType::LayerSwitch: m_layerBeforeSwitch = m_model.layerName(); m_model.setLayer(key.target); break; default: break; // everything else acts on release } } void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QString &alternate, qint64) { switch (key.type) { case KeyType::Shift: changeShift([&] { m_shift.release(); }); break; case KeyType::Backspace: m_backspaceRepeater.stop(); break; case KeyType::Character: if (output == KeyOutput::Alternate && !alternate.isEmpty()) typeText(alternate); else if (output == KeyOutput::Flick && !key.flick.isEmpty()) typeText(key.flick); else typeText(key.text(m_shift.isUppercase())); break; case KeyType::Space: typeText(QStringLiteral(" ")); break; case KeyType::Enter: m_engine.sendKeysym(XKB_KEY_Return); m_textBefore += u'\n'; autoCapitalize(); break; case KeyType::Globe: Q_EMIT nextLayoutRequested(); break; case KeyType::Dismiss: Q_EMIT dismissRequested(); break; default: break; } } void KeyboardController::keyCancelled(const KeyDef &key) { if (key.type == KeyType::Shift) changeShift([&] { m_shift.release(); }); else if (key.type == KeyType::Backspace) m_backspaceRepeater.stop(); } void KeyboardController::slideReleased(const KeyDef &target, qint64 timeMs) { keyUp(target, KeyOutput::Primary, {}, timeMs); m_model.setLayer(m_layerBeforeSwitch); } void KeyboardController::moveCursor(QPoint steps) { const auto repeat = [this](int count, quint32 keysym) { for (int i = 0; i < count; ++i) m_engine.sendKeysym(keysym); }; repeat(qMax(0, steps.x()), XKB_KEY_Right); repeat(qMax(0, -steps.x()), XKB_KEY_Left); repeat(qMax(0, steps.y()), XKB_KEY_Down); repeat(qMax(0, -steps.y()), XKB_KEY_Up); } void KeyboardController::resetContext() { m_context = {}; m_textBefore.clear(); autoCapitalize(); } void KeyboardController::setContext(const TextContext &context) { m_context = context; if (context.hasSurroundingText) m_textBefore = context.textBeforeCursor.right(MaxTextBefore); autoCapitalize(); } void KeyboardController::setOptions(const TypingOptions &options) { m_options = options; autoCapitalize(); } void KeyboardController::typeText(const QString &text) { if (text == u" " && m_options.doubleSpacePeriod && m_context.isProse() && TextRules::wantsDoubleSpacePeriod(m_textBefore)) { // "word " + space → "word. ", replacing the first space in place. m_engine.replaceBeforeCursor(QStringLiteral(" "), QStringLiteral(". ")); m_textBefore.chop(1); m_textBefore += QStringLiteral(". "); } else { m_engine.commitText(text); m_textBefore += text; } m_textBefore = m_textBefore.right(MaxTextBefore); changeShift([&] { m_shift.characterTyped(); }); autoCapitalize(); } void KeyboardController::deleteBackward(bool wholeWord) { m_engine.sendKeysym(XKB_KEY_BackSpace, wholeWord ? Qt::ControlModifier : Qt::NoModifier); m_textBefore = wholeWord ? TextRules::withoutLastWord(m_textBefore) : TextRules::withoutLastCharacter(m_textBefore); autoCapitalize(); } void KeyboardController::autoCapitalize() { const bool capitalize = m_options.autoCapitalize && m_context.allowsAutoCapitalization() && TextRules::startsSentence(m_textBefore); changeShift([&] { m_shift.setAutomatic(capitalize); }); } template void KeyboardController::changeShift(Change change) { const ShiftState::Mode before = m_shift.mode(); change(); if (m_shift.mode() == before) return; m_model.setUppercase(m_shift.isUppercase()); Q_EMIT shiftChanged(); }