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
+43 -6
View File
@@ -3,6 +3,7 @@
#include "layout/LayoutLoader.h"
#include <QTest>
#include <xkbcommon/xkbcommon-keysyms.h>
using namespace Qt::StringLiterals;
@@ -67,24 +68,60 @@ private Q_SLOTS:
&error));
}
// Stretching must even out rows whatever the keymap's letter counts.
void builtLayersHaveEqualRows_data()
// Stretch keys must even out rows whatever the keymap's letter counts.
void builtLayersHaveEvenRows_data()
{
QTest::addColumn<QString>("xkbLayout");
for (const char *layout : {"us", "sk", "se", "de", "fr", "ru", "gr"})
QTest::newRow(layout) << QString::fromLatin1(layout);
}
void builtLayersHaveEqualRows()
void builtLayersHaveEvenRows()
{
QFETCH(QString, xkbLayout);
const Layout layout = TestLayouts::build(qPrintable(xkbLayout));
for (const Layer &layer : layout.layers) {
const qreal expected = rowUnits(layer.rows.first());
for (const Row &row : layer.rows)
QVERIFY2(qFuzzyCompare(rowUnits(row), expected), qPrintable(layer.name));
const QList<QRectF> rects = computeKeyRects(layer, {1000, 300});
qsizetype i = 0;
for (const Row &row : layer.rows) {
const QRectF first = rects[i];
const QRectF last = rects[i + row.size() - 1];
QVERIFY2(qAbs(first.left() - rects.first().left()) < 0.01, qPrintable(layer.name));
QVERIFY2(qAbs(last.right() - rects[layer.rows.first().size() - 1].right()) < 0.01,
qPrintable(layer.name));
i += row.size();
}
}
}
void templateHasKeySections()
{
const Layout layout = TestLayouts::builtinTemplate();
QStringList ids;
for (const Section &section : layout.sections)
ids.append(section.id);
QCOMPARE(ids, (QStringList{u"function"_s, u"navigation"_s, u"numpad"_s}));
}
void parsesKeysymKeys()
{
QString error;
const auto layout = LayoutLoader::parse(
R"({"id": "t", "layers": [{"name": "l", "rows": [[{"type": "keysym", "keysym": "Home", "repeat": true}]]}]})",
&error);
QVERIFY2(layout, qPrintable(error));
const KeyDef &key = layout->layers[0].rows[0][0];
QCOMPARE(key.keysym, quint32(XKB_KEY_Home));
QCOMPARE(key.label, u"Home"); // the keysym name unless labelled
QVERIFY(key.repeat);
}
void rejectsUnknownKeysym()
{
QString error;
QVERIFY(!LayoutLoader::parse(
R"({"id": "t", "layers": [{"name": "l", "rows": [[{"type": "keysym", "keysym": "Nope"}]]}]})", &error));
}
};
QTEST_GUILESS_MAIN(TestLayoutLoader)