Text rules: auto-capitalization, double-space period, backspace repeat
- TextContext: field purpose/hints and text before the cursor, reported by the backend (KWin content_type + surrounding_text) - TextRules: sentence-start and double-space heuristics, deletion helpers - KeyRepeater: held backspace repeats, then deletes whole words (Ctrl+BS) - Controller keeps a best-guess of the text before the cursor so rules work instantly and in fields that don't report surrounding text - No auto-capitalization or smart punctuation in password/URL/email fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#include "KeyboardController.h"
|
||||
#include "TextRules.h"
|
||||
|
||||
#include "engine/InputEngine.h"
|
||||
#include "layout/LayoutModel.h"
|
||||
@@ -11,6 +12,11 @@ static_assert(int(KeyboardController::ShiftMode::Off) == int(ShiftState::Mode::O
|
||||
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,
|
||||
const LayoutRepository &layouts, QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -20,6 +26,7 @@ KeyboardController::KeyboardController(InputEngine &engine, LayoutModel &model,
|
||||
{
|
||||
if (!m_layouts.layouts().isEmpty())
|
||||
m_model.setLayout(m_layouts.layouts().first());
|
||||
connect(&m_backspaceRepeater, &KeyRepeater::repeat, this, &KeyboardController::deleteBackward);
|
||||
}
|
||||
|
||||
KeyboardController::ShiftMode KeyboardController::shiftMode() const
|
||||
@@ -34,7 +41,8 @@ void KeyboardController::keyDown(const KeyDef &key, qint64 timeMs)
|
||||
changeShift([&] { m_shift.press(timeMs); });
|
||||
break;
|
||||
case KeyType::Backspace:
|
||||
m_engine.sendKeysym(XKB_KEY_BackSpace);
|
||||
deleteBackward(false);
|
||||
m_backspaceRepeater.start();
|
||||
break;
|
||||
case KeyType::LayerSwitch:
|
||||
m_layerBeforeSwitch = m_model.layerName();
|
||||
@@ -51,6 +59,9 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin
|
||||
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);
|
||||
@@ -64,6 +75,8 @@ void KeyboardController::keyUp(const KeyDef &key, KeyOutput output, const QStrin
|
||||
break;
|
||||
case KeyType::Enter:
|
||||
m_engine.sendKeysym(XKB_KEY_Return);
|
||||
m_textBefore += u'\n';
|
||||
autoCapitalize();
|
||||
break;
|
||||
case KeyType::Globe:
|
||||
nextLayout();
|
||||
@@ -80,6 +93,8 @@ 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)
|
||||
@@ -100,6 +115,21 @@ void KeyboardController::moveCursor(QPoint steps)
|
||||
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::nextLayout()
|
||||
{
|
||||
const auto &layouts = m_layouts.layouts();
|
||||
@@ -111,8 +141,31 @@ void KeyboardController::nextLayout()
|
||||
|
||||
void KeyboardController::typeText(const QString &text)
|
||||
{
|
||||
if (text == u" " && m_context.isProse() && TextRules::wantsDoubleSpacePeriod(m_textBefore)) {
|
||||
m_engine.sendKeysym(XKB_KEY_BackSpace);
|
||||
m_textBefore.chop(1);
|
||||
typeText(QStringLiteral(". "));
|
||||
return;
|
||||
}
|
||||
|
||||
m_engine.commitText(text);
|
||||
m_textBefore = (m_textBefore + text).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_context.allowsAutoCapitalization() && TextRules::startsSentence(m_textBefore);
|
||||
changeShift([&] { m_shift.setAutomatic(capitalize); });
|
||||
}
|
||||
|
||||
template<typename Change>
|
||||
|
||||
Reference in New Issue
Block a user