Hold Keys mode: keys stay down while touched, like a hardware keyboard
- HardwareKeys presses a key's unshifted keysym on touch and releases it on lift, through fake input; held keys repeat and combine as on a physical keyboard (games, Shift+click) - Shift/Ctrl/Alt/Super are plain held keys, caps lock is the system's, dead keys compose in the app - No long-press, flick, trackpad or rollover in this mode; nothing is left held when the mode is turned off or touches are cancelled - Settings switch; default stays the touch (virtual keyboard) mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "engine/InputEngine.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
// Records what the keyboard sends and applies it to a simulated field, so
|
||||
@@ -27,8 +28,25 @@ public:
|
||||
lastModifiers = modifiers;
|
||||
}
|
||||
|
||||
void setKeyPressed(quint32 keysym, bool pressed) override
|
||||
{
|
||||
keyEvents.append({keysym, pressed});
|
||||
if (pressed)
|
||||
held.insert(keysym);
|
||||
else
|
||||
held.remove(keysym);
|
||||
}
|
||||
|
||||
struct KeyEvent {
|
||||
quint32 keysym;
|
||||
bool pressed;
|
||||
bool operator==(const KeyEvent &) const = default;
|
||||
};
|
||||
|
||||
QString output; // the field's text
|
||||
int mismatchedReplacements = 0; // replaced text that wasn't before the cursor
|
||||
QList<quint32> keysyms;
|
||||
Qt::KeyboardModifiers lastModifiers;
|
||||
QList<KeyEvent> keyEvents; // setKeyPressed calls
|
||||
QSet<quint32> held;
|
||||
};
|
||||
|
||||
@@ -330,6 +330,48 @@ private Q_SLOTS:
|
||||
QVERIFY(engine.output.endsWith(u"é"_s));
|
||||
}
|
||||
|
||||
void holdKeysPressesWhileDown()
|
||||
{
|
||||
controller->setOptions({.holdKeys = true});
|
||||
const KeyDef shift = makeKey(KeyType::Shift);
|
||||
const KeyDef a = makeKey(KeyType::Character, u"a"_s);
|
||||
controller->keyDown(shift, 0);
|
||||
QCOMPARE(model.isUppercase(), true); // labels follow the held shift
|
||||
controller->keyDown(a, 10);
|
||||
controller->keyUp(a, KeyOutput::Primary, {}, 20);
|
||||
controller->keyUp(shift, KeyOutput::Primary, {}, 30);
|
||||
using E = FakeEngine::KeyEvent;
|
||||
QCOMPARE(engine.keyEvents, (QList<E>{{XKB_KEY_Shift_L, true}, {XKB_KEY_a, true},
|
||||
{XKB_KEY_a, false}, {XKB_KEY_Shift_L, false}}));
|
||||
QVERIFY(engine.output.isEmpty());
|
||||
QVERIFY(engine.keysyms.isEmpty());
|
||||
QCOMPARE(model.isUppercase(), false);
|
||||
}
|
||||
|
||||
void holdKeysLeavesNoKeyStuck()
|
||||
{
|
||||
controller->setOptions({.holdKeys = true});
|
||||
const KeyDef ctrl = modifierKey(u"ctrl"_s);
|
||||
controller->keyDown(ctrl, 0);
|
||||
QCOMPARE(controller->modifierModes().value(u"ctrl"_s).toInt(), 1);
|
||||
controller->setOptions({}); // turned off while held
|
||||
QVERIFY(engine.held.isEmpty());
|
||||
controller->keyUp(ctrl, KeyOutput::Primary, {}, 10); // back to sticky rules: harmless
|
||||
QVERIFY(engine.held.isEmpty());
|
||||
}
|
||||
|
||||
void holdKeysSendsDeadKeysAndCapsLockToTheSystem()
|
||||
{
|
||||
controller->setOptions({.holdKeys = true});
|
||||
KeyDef acute = makeKey(KeyType::Character, u"´"_s);
|
||||
acute.dead = XKB_KEY_dead_acute;
|
||||
tap(acute);
|
||||
tap(makeKey(KeyType::CapsLock));
|
||||
QCOMPARE(controller->shiftMode(), KeyboardController::ShiftMode::Locked);
|
||||
QCOMPARE(engine.keyEvents.first().keysym, quint32(XKB_KEY_dead_acute));
|
||||
QCOMPARE(engine.keyEvents[2].keysym, quint32(XKB_KEY_Caps_Lock));
|
||||
}
|
||||
|
||||
void noTextRulesWithoutAField()
|
||||
{
|
||||
controller->resetContext({}); // keys go to a window, not a text field
|
||||
|
||||
@@ -135,6 +135,22 @@ private Q_SLOTS:
|
||||
QCOMPARE(engine.output, u"as");
|
||||
}
|
||||
|
||||
void holdKeysKeepsEveryFingerDown()
|
||||
{
|
||||
controller->setOptions({.holdKeys = true});
|
||||
const QPoint w = keyCenter(u"w"_s), e = keyCenter(u"e"_s);
|
||||
QTest::touchEvent(&window, touchscreen).press(0, w);
|
||||
QTest::touchEvent(&window, touchscreen).stationary(0).press(1, e);
|
||||
QTest::qWait(600); // past the long-press delay: e has accents, but no popup
|
||||
QCOMPARE(engine.held, (QSet<quint32>{XKB_KEY_w, XKB_KEY_e}));
|
||||
QTest::touchEvent(&window, touchscreen).release(0, w).stationary(1);
|
||||
QCOMPARE(engine.held, QSet<quint32>{XKB_KEY_e});
|
||||
QTest::touchEvent(&window, touchscreen).release(1, e);
|
||||
QVERIFY(engine.held.isEmpty());
|
||||
QVERIFY(engine.output.isEmpty()); // key events only, no text
|
||||
controller->setOptions({});
|
||||
}
|
||||
|
||||
void slideFromNumbersKeyTypesAndReturns()
|
||||
{
|
||||
const QPoint layerKey = keyCenter(u".?123"_s, u"layer"_s);
|
||||
|
||||
Reference in New Issue
Block a user