Fixed quickbinding bug.
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
set(binding_test_SOURCES
|
||||
test_bindings.cpp
|
||||
../src/axis.cpp
|
||||
../src/bindingdialog.cpp
|
||||
../src/button.cpp
|
||||
../src/event.cpp
|
||||
../src/inputbinding.cpp
|
||||
../src/keycode.cpp
|
||||
../src/keydialog.cpp)
|
||||
|
||||
set(binding_test_QOBJECT_HEADERS
|
||||
../src/axis.h
|
||||
../src/bindingdialog.h
|
||||
../src/button.h
|
||||
../src/inputbinding.h
|
||||
../src/keycode.h
|
||||
../src/keydialog.hpp)
|
||||
|
||||
qt6_wrap_cpp(binding_test_MOC ${binding_test_QOBJECT_HEADERS})
|
||||
add_executable(binding_tests ${binding_test_SOURCES} ${binding_test_MOC})
|
||||
set_target_properties(binding_tests PROPERTIES AUTOMOC ON)
|
||||
target_include_directories(binding_tests PRIVATE ../src "${PROJECT_BINARY_DIR}/src")
|
||||
target_link_libraries(binding_tests Qt6::Test Qt6::Widgets Qt6::Gui Xtst X11)
|
||||
add_test(NAME binding_tests COMMAND binding_tests)
|
||||
set_tests_properties(binding_tests PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
|
||||
@@ -0,0 +1,348 @@
|
||||
#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"
|
||||
Reference in New Issue
Block a user