Optional PC key blocks: function row, navigation, numeric keypad

- Template sections placed above or to the right of the typing keys;
  the user picks any mix in settings, typing keys included
- keysym keys send XKB keysyms on press, optionally repeating
- Geometry lays blocks side by side with one key unit; stretch keys are
  resolved at layout time so composed blocks line up
- Toggling a block resizes the window so keys keep their size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 21:18:10 +02:00
co-authored by Claude Opus 5.5
parent e532f5c072
commit 88338fde22
22 changed files with 521 additions and 101 deletions
+31
View File
@@ -42,6 +42,7 @@ private Q_SLOTS:
{
engine = {};
model.setLayout(TestLayouts::build("us"));
model.setVisibleSections({MainSectionId});
controller = std::make_unique<KeyboardController>(engine, model);
}
@@ -199,6 +200,36 @@ private Q_SLOTS:
QCOMPARE(requested, 1);
}
void keysymKeyActsOnPressAndRepeats()
{
KeyDef left = makeKey(KeyType::Keysym, u"←"_s);
left.keysym = XKB_KEY_Left;
left.repeat = true;
controller->keyDown(left, 0);
QCOMPARE(engine.keysyms, QList<quint32>{XKB_KEY_Left});
QTRY_VERIFY_WITH_TIMEOUT(engine.keysyms.size() >= 3, 2000); // held: repeats
controller->keyUp(left, KeyOutput::Primary, {}, 0);
const auto sent = engine.keysyms.size();
QTest::qWait(200);
QCOMPARE(engine.keysyms.size(), sent);
QVERIFY(engine.output.isEmpty()); // no text
}
void sectionsAreComposedAroundTheMainKeys()
{
const qsizetype mainKeys = model.rowCount();
const qreal mainUnits = model.totalUnits();
model.setVisibleSections({MainSectionId, u"function"_s});
QCOMPARE(model.rowCount(), mainKeys + 13);
QCOMPARE(model.totalUnits(), mainUnits); // above, not beside
model.setVisibleSections({MainSectionId, u"navigation"_s});
QCOMPARE(model.totalUnits(), mainUnits + 3);
model.setVisibleSections({u"numpad"_s});
QCOMPARE(model.rowCount(), 19);
model.setVisibleSections({});
QCOMPARE(model.rowCount(), mainKeys); // never nothing
}
void dismissEmitsSignal()
{
int dismissed = 0;