349 lines
12 KiB
C++
349 lines
12 KiB
C++
#include "axis.h"
|
|
#include "bindingdialog.h"
|
|
#include "button.h"
|
|
#include "inputbinding.h"
|
|
|
|
#include <QElapsedTimer>
|
|
#include <QApplication>
|
|
#include <QKeyEvent>
|
|
#include <QSignalSpy>
|
|
#include <QTest>
|
|
#include <QTextStream>
|
|
|
|
class RecorderProbe : public BindingDialog {
|
|
public:
|
|
RecorderProbe() : BindingDialog("Test control", true) {}
|
|
|
|
void press(int qtKey, int scanCode, bool repeat = false) {
|
|
QKeyEvent event(QEvent::KeyPress, qtKey, Qt::NoModifier,
|
|
static_cast<quint32>(scanCode), 0, 0, QString(), repeat);
|
|
keyPressEvent(&event);
|
|
}
|
|
void releaseKey(int qtKey, int scanCode, bool repeat = false) {
|
|
QKeyEvent event(QEvent::KeyRelease, qtKey, Qt::NoModifier,
|
|
static_cast<quint32>(scanCode), 0, 0, QString(), repeat);
|
|
keyReleaseEvent(&event);
|
|
}
|
|
void tap(int qtKey, int scanCode) {
|
|
press(qtKey, scanCode);
|
|
releaseKey(qtKey, scanCode);
|
|
}
|
|
void finish() {
|
|
press(Qt::Key_Control, 37);
|
|
press(Qt::Key_Return, 36);
|
|
}
|
|
void dispatchTap(int qtKey, int scanCode) {
|
|
QWidget *target = findChild<QPushButton *>();
|
|
QVERIFY(target);
|
|
QKeyEvent pressEvent(QEvent::KeyPress, qtKey, Qt::NoModifier,
|
|
static_cast<quint32>(scanCode), 0, 0);
|
|
QApplication::sendEvent(target, &pressEvent);
|
|
QKeyEvent releaseEvent(QEvent::KeyRelease, qtKey, Qt::NoModifier,
|
|
static_cast<quint32>(scanCode), 0, 0);
|
|
QApplication::sendEvent(target, &releaseEvent);
|
|
}
|
|
bool cancelAvailable() const {
|
|
for (QPushButton *button : findChildren<QPushButton *>()) {
|
|
if (button->text().contains("Cancel")) return button->isVisibleTo(this);
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
class ButtonProbe : public Button {
|
|
public:
|
|
ButtonProbe() : Button(0) {}
|
|
QVector<bool> plainEvents;
|
|
QVector<QPair<int, bool>> compositeEvents;
|
|
protected:
|
|
void click(bool press) override { plainEvents.append(press); }
|
|
void compositeKeyEvent(int keycode, bool press) override {
|
|
compositeEvents.append(qMakePair(keycode, press));
|
|
}
|
|
};
|
|
|
|
class AxisProbe : public Axis {
|
|
public:
|
|
AxisProbe() : Axis(0) {}
|
|
QVector<QPair<int, bool>> compositeEvents;
|
|
protected:
|
|
void compositeKeyEvent(int keycode, bool press) override {
|
|
compositeEvents.append(qMakePair(keycode, press));
|
|
}
|
|
};
|
|
|
|
class BindingTests : public QObject {
|
|
Q_OBJECT
|
|
private slots:
|
|
void parseAndSerializeSequences();
|
|
void rejectInvalidSequences_data();
|
|
void rejectInvalidSequences();
|
|
void preserveLegacySerialization();
|
|
void recorderPlainChordAndSteps();
|
|
void recorderModifiersRepeatLimitAndFinish();
|
|
void runnerOrderTimingRetriggerAndCancel();
|
|
void preservePlainButtonModes();
|
|
void axisCompositeRearmsWithoutRepeating();
|
|
};
|
|
|
|
void BindingTests::parseAndSerializeSequences() {
|
|
InputBinding binding;
|
|
QString error;
|
|
QVERIFY2(InputBinding::parseSequence("125", "64+28/49/10+14", &binding, &error), qPrintable(error));
|
|
QCOMPARE(binding.stepDelay, 125);
|
|
QCOMPARE(binding.chords.size(), 3);
|
|
QCOMPARE(binding.sequenceToken(), QString("64+28/49/10+14"));
|
|
|
|
Button button(0);
|
|
QString buttonInput("sequence 125 64+28/49");
|
|
QTextStream buttonRead(&buttonInput);
|
|
QVERIFY(button.read(buttonRead));
|
|
QString buttonOutput;
|
|
QTextStream buttonWrite(&buttonOutput);
|
|
button.write(buttonWrite);
|
|
QCOMPARE(buttonOutput, QString("\tButton 1: sequence 125 64+28/49\n"));
|
|
|
|
Axis axis(0);
|
|
QString axisInput("ZeroOne, +sequence 80 64+28/49, -sequence 250 10/11+12");
|
|
QTextStream axisRead(&axisInput);
|
|
QVERIFY2(axis.read(axisRead), qPrintable(axis.readError()));
|
|
QString axisOutput;
|
|
QTextStream axisWrite(&axisOutput);
|
|
axis.write(axisWrite);
|
|
QCOMPARE(axisOutput, QString("\tAxis 1: ZeroOne, +sequence 80 64+28/49, -sequence 250 10/11+12\n"));
|
|
|
|
Button malformedButton(0);
|
|
QString malformedButtonInput("sequence 100 10 / 11");
|
|
QTextStream malformedButtonRead(&malformedButtonInput);
|
|
QVERIFY(!malformedButton.read(malformedButtonRead));
|
|
QVERIFY(!malformedButton.readError().isEmpty());
|
|
|
|
Axis malformedAxis(0);
|
|
QString malformedAxisInput("ZeroOne, +sequence 100 10 + 11, -key 12");
|
|
QTextStream malformedAxisRead(&malformedAxisInput);
|
|
QVERIFY(!malformedAxis.read(malformedAxisRead));
|
|
QVERIFY(!malformedAxis.readError().isEmpty());
|
|
|
|
Axis mouseModeSequence(0);
|
|
QString mouseModeInput("Gradient, +sequence 100 10+11, -key 12, mouse+h");
|
|
QTextStream mouseModeRead(&mouseModeInput);
|
|
QVERIFY(!mouseModeSequence.read(mouseModeRead));
|
|
QVERIFY(mouseModeSequence.readError().contains("mouse movement"));
|
|
}
|
|
|
|
void BindingTests::rejectInvalidSequences_data() {
|
|
QTest::addColumn<QString>("delay");
|
|
QTest::addColumn<QString>("sequence");
|
|
QTest::newRow("delay-low") << "49" << "10/11";
|
|
QTest::newRow("delay-high") << "1001" << "10/11";
|
|
QTest::newRow("delay-text") << "fast" << "10/11";
|
|
QTest::newRow("empty-chord") << "100" << "10//11";
|
|
QTest::newRow("empty-key") << "100" << "10++11";
|
|
QTest::newRow("duplicate") << "100" << "10+10";
|
|
QTest::newRow("keycode-zero") << "100" << "0/11";
|
|
QTest::newRow("keycode-high") << "100" << "256/11";
|
|
QStringList tooMany;
|
|
for (int i = 0; i < 33; ++i) tooMany.append(QString::number(i + 1));
|
|
QTest::newRow("too-many") << "100" << tooMany.join('/');
|
|
}
|
|
|
|
void BindingTests::rejectInvalidSequences() {
|
|
QFETCH(QString, delay);
|
|
QFETCH(QString, sequence);
|
|
InputBinding binding;
|
|
QString error;
|
|
QVERIFY(!InputBinding::parseSequence(delay, sequence, &binding, &error));
|
|
QVERIFY(!error.isEmpty());
|
|
|
|
}
|
|
|
|
void BindingTests::preserveLegacySerialization() {
|
|
Button button(1);
|
|
QString buttonInput("rapidfire, sticky, key 42");
|
|
QTextStream buttonRead(&buttonInput);
|
|
QVERIFY(button.read(buttonRead));
|
|
QString buttonOutput;
|
|
QTextStream buttonWrite(&buttonOutput);
|
|
button.write(buttonWrite);
|
|
QCOMPARE(buttonOutput, QString("\tButton 2: rapidfire, sticky, key 42\n"));
|
|
|
|
Axis axis(2);
|
|
QString axisInput("Gradient, dZone 4000, xZone 29000, +mouse 1, -key 113");
|
|
QTextStream axisRead(&axisInput);
|
|
QVERIFY(axis.read(axisRead));
|
|
QString axisOutput;
|
|
QTextStream axisWrite(&axisOutput);
|
|
axis.write(axisWrite);
|
|
QCOMPARE(axisOutput, QString("\tAxis 3: Gradient, dZone 4000, xZone 29000, +mouse 1, -key 113\n"));
|
|
}
|
|
|
|
void BindingTests::recorderPlainChordAndSteps() {
|
|
RecorderProbe plain;
|
|
plain.tap(Qt::Key_A, 38);
|
|
plain.finish();
|
|
QVERIFY(plain.binding().isPlainKey());
|
|
QCOMPARE(plain.binding().chords.first().keycodes, QVector<int>({38}));
|
|
|
|
RecorderProbe chord;
|
|
chord.press(Qt::Key_Alt, 64);
|
|
chord.press(Qt::Key_T, 28);
|
|
chord.releaseKey(Qt::Key_T, 28);
|
|
chord.releaseKey(Qt::Key_Alt, 64);
|
|
chord.finish();
|
|
QCOMPARE(chord.binding().chords, QVector<KeyChord>({KeyChord{{64, 28}}}));
|
|
|
|
RecorderProbe steps;
|
|
steps.tap(Qt::Key_A, 38);
|
|
steps.tap(Qt::Key_B, 56);
|
|
steps.finish();
|
|
QCOMPARE(steps.binding().chords, QVector<KeyChord>({KeyChord{{38}}, KeyChord{{56}}}));
|
|
|
|
RecorderProbe tab;
|
|
tab.dispatchTap(Qt::Key_Tab, 23);
|
|
tab.finish();
|
|
QCOMPARE(tab.binding().chords, QVector<KeyChord>({KeyChord{{23}}}));
|
|
QVERIFY(tab.cancelAvailable());
|
|
}
|
|
|
|
void BindingTests::recorderModifiersRepeatLimitAndFinish() {
|
|
RecorderProbe modifier;
|
|
modifier.tap(Qt::Key_Shift, 50);
|
|
modifier.finish();
|
|
QCOMPARE(modifier.binding().chords, QVector<KeyChord>({KeyChord{{50}}}));
|
|
|
|
RecorderProbe repeat;
|
|
repeat.press(Qt::Key_A, 38);
|
|
repeat.press(Qt::Key_A, 38, true);
|
|
repeat.releaseKey(Qt::Key_A, 38, true);
|
|
repeat.releaseKey(Qt::Key_A, 38);
|
|
repeat.finish();
|
|
QCOMPARE(repeat.binding().chords, QVector<KeyChord>({KeyChord{{38}}}));
|
|
|
|
RecorderProbe separateCtrlEnter;
|
|
separateCtrlEnter.tap(Qt::Key_Control, 37);
|
|
separateCtrlEnter.tap(Qt::Key_Return, 36);
|
|
separateCtrlEnter.finish();
|
|
QCOMPARE(separateCtrlEnter.binding().chords,
|
|
QVector<KeyChord>({KeyChord{{37}}, KeyChord{{36}}}));
|
|
|
|
RecorderProbe limit;
|
|
for (int i = 0; i < InputBinding::MaximumChords; ++i) limit.tap(Qt::Key_A, 8 + i);
|
|
QCOMPARE(limit.binding().chords.size(), InputBinding::MaximumChords);
|
|
}
|
|
|
|
void BindingTests::runnerOrderTimingRetriggerAndCancel() {
|
|
InputBinding binding;
|
|
QVERIFY(InputBinding::parseSequence("80", "10+11/12+13", &binding));
|
|
SequenceRunner runner;
|
|
QVector<QPair<int, bool>> events;
|
|
QVector<qint64> times;
|
|
QElapsedTimer elapsed;
|
|
elapsed.start();
|
|
connect(&runner, &SequenceRunner::keyEvent, &runner, [&](int keycode, bool press) {
|
|
events.append(qMakePair(keycode, press));
|
|
times.append(elapsed.elapsed());
|
|
});
|
|
QSignalSpy finished(&runner, &SequenceRunner::finished);
|
|
QVERIFY(runner.start(binding));
|
|
QVERIFY(!runner.start(binding));
|
|
QVERIFY(finished.wait(300));
|
|
const QVector<QPair<int, bool>> expected = {
|
|
{10, true}, {11, true}, {11, false}, {10, false},
|
|
{12, true}, {13, true}, {13, false}, {12, false}
|
|
};
|
|
QCOMPARE(events, expected);
|
|
QVERIFY(times.at(2) >= 20 && times.at(2) <= 60);
|
|
QVERIFY(times.at(4) >= 65 && times.at(4) <= 120);
|
|
|
|
events.clear();
|
|
QVERIFY(runner.start(binding));
|
|
runner.cancel();
|
|
const QVector<QPair<int, bool>> cancelled = {
|
|
{10, true}, {11, true}, {11, false}, {10, false}
|
|
};
|
|
QCOMPARE(events, cancelled);
|
|
QVERIFY(!runner.isRunning());
|
|
}
|
|
|
|
void BindingTests::preservePlainButtonModes() {
|
|
ButtonProbe held;
|
|
held.setKey(false, 38);
|
|
held.jsevent(1);
|
|
held.jsevent(0);
|
|
QCOMPARE(held.plainEvents, QVector<bool>({true, false}));
|
|
|
|
ButtonProbe sticky;
|
|
QString stickyInput("sticky, key 38");
|
|
QTextStream stickyRead(&stickyInput);
|
|
QVERIFY(sticky.read(stickyRead));
|
|
sticky.jsevent(1);
|
|
sticky.jsevent(0);
|
|
sticky.jsevent(1);
|
|
QCOMPARE(sticky.plainEvents, QVector<bool>({true, false}));
|
|
|
|
ButtonProbe rapid;
|
|
QString rapidInput("rapidfire, key 38");
|
|
QTextStream rapidRead(&rapidInput);
|
|
QVERIFY(rapid.read(rapidRead));
|
|
rapid.jsevent(1);
|
|
rapid.timerTick(FREQ);
|
|
rapid.timerTick(FREQ + FREQ / 2);
|
|
rapid.jsevent(0);
|
|
QCOMPARE(rapid.plainEvents, QVector<bool>({true, false}));
|
|
|
|
ButtonProbe mouse;
|
|
mouse.setKey(true, 1);
|
|
QVERIFY(mouse.getBinding().isMouse());
|
|
mouse.jsevent(1);
|
|
mouse.jsevent(0);
|
|
QCOMPARE(mouse.plainEvents, QVector<bool>({true, false}));
|
|
|
|
Button composite(0);
|
|
QString compositeInput("sticky, rapidfire, sequence 100 10+11");
|
|
QTextStream compositeRead(&compositeInput);
|
|
QVERIFY(composite.read(compositeRead));
|
|
QString output;
|
|
QTextStream writer(&output);
|
|
composite.write(writer);
|
|
QVERIFY(!output.contains("sticky"));
|
|
QVERIFY(!output.contains("rapidfire"));
|
|
|
|
InputBinding sequence;
|
|
QVERIFY(InputBinding::parseSequence("100", "10+11/12", &sequence));
|
|
ButtonProbe cancelling;
|
|
cancelling.setBinding(sequence);
|
|
cancelling.jsevent(1);
|
|
cancelling.release();
|
|
const QVector<QPair<int, bool>> cancelled = {
|
|
{10, true}, {11, true}, {11, false}, {10, false}
|
|
};
|
|
QCOMPARE(cancelling.compositeEvents, cancelled);
|
|
}
|
|
|
|
void BindingTests::axisCompositeRearmsWithoutRepeating() {
|
|
AxisProbe axis;
|
|
QString input("Gradient, +sequence 50 10+11, -sequence 50 12+13");
|
|
QTextStream reader(&input);
|
|
QVERIFY(axis.read(reader));
|
|
axis.jsevent(20000);
|
|
QTRY_COMPARE_WITH_TIMEOUT(axis.compositeEvents.size(), 4, 150);
|
|
axis.jsevent(25000);
|
|
QTest::qWait(80);
|
|
QCOMPARE(axis.compositeEvents.size(), 4);
|
|
axis.jsevent(-25000);
|
|
QTest::qWait(80);
|
|
QCOMPARE(axis.compositeEvents.size(), 4);
|
|
axis.jsevent(0);
|
|
axis.jsevent(20000);
|
|
QTRY_COMPARE_WITH_TIMEOUT(axis.compositeEvents.size(), 8, 150);
|
|
|
|
axis.jsevent(0);
|
|
axis.jsevent(-20000);
|
|
QTRY_COMPARE_WITH_TIMEOUT(axis.compositeEvents.size(), 12, 150);
|
|
QCOMPARE(axis.compositeEvents.at(8), qMakePair(12, true));
|
|
QCOMPARE(axis.compositeEvents.at(9), qMakePair(13, true));
|
|
}
|
|
|
|
QTEST_MAIN(BindingTests)
|
|
#include "test_bindings.moc"
|