diff --git a/src/lib/deskflow/KeyMap.cpp b/src/lib/deskflow/KeyMap.cpp index 967e2bccc..a25f627dc 100644 --- a/src/lib/deskflow/KeyMap.cpp +++ b/src/lib/deskflow/KeyMap.cpp @@ -1152,6 +1152,12 @@ bool KeyMap::parseModifiers(std::string &x, KeyModifierMask &mask) } std::string c = x.substr(tb, te - tb); if (c.empty()) { + // allow '+' to be parsed as the key after modifiers (e.g. Control+Shift++) + if (x[tb] == '+') { + x.erase(0, tb); + return true; + } + // missing component return false; } diff --git a/src/lib/gui/KeySequence.cpp b/src/lib/gui/KeySequence.cpp index f6c267976..192280b02 100644 --- a/src/lib/gui/KeySequence.cpp +++ b/src/lib/gui/KeySequence.cpp @@ -62,6 +62,7 @@ static const struct {Qt::Key_Launch0, "AppUser1"}, {Qt::Key_Launch1, "AppUser2"}, {Qt::Key_Select, "Select"}, + {Qt::Key_Plus, "Plus"}, {Qt::Key_Comma, "Comma"}, {Qt::Key_Semicolon, "Semicolon"}, diff --git a/src/unittests/deskflow/KeyMapTests.cpp b/src/unittests/deskflow/KeyMapTests.cpp index 3378d1704..f9d358ae9 100644 --- a/src/unittests/deskflow/KeyMapTests.cpp +++ b/src/unittests/deskflow/KeyMapTests.cpp @@ -183,4 +183,22 @@ void KeyMapTests::mapkey() QVERIFY(result == nullptr); } +void KeyMapTests::parseModifiers_plusKey_keepsPlusAsKey() +{ + std::string keystroke = "Control+Shift++"; + KeyModifierMask mask = 0; + + QVERIFY(KeyMap::parseModifiers(keystroke, mask)); + QCOMPARE(mask, static_cast(KeyModifierControl | KeyModifierShift)); + QCOMPARE(keystroke, std::string("+")); +} + +void KeyMapTests::parseKey_plusSymbol_parsesAsAsciiKey() +{ + KeyID key = kKeyNone; + + QVERIFY(KeyMap::parseKey("+", key)); + QCOMPARE(key, static_cast('+')); +} + QTEST_MAIN(KeyMapTests) diff --git a/src/unittests/deskflow/KeyMapTests.h b/src/unittests/deskflow/KeyMapTests.h index 3e9b4c900..045799cce 100644 --- a/src/unittests/deskflow/KeyMapTests.h +++ b/src/unittests/deskflow/KeyMapTests.h @@ -21,6 +21,8 @@ private Q_SLOTS: void findBestKey_noRequiredDown_cannotMatch(); void isCommand(); void mapkey(); + void parseModifiers_plusKey_keepsPlusAsKey(); + void parseKey_plusSymbol_parsesAsAsciiKey(); private: Log m_log; diff --git a/src/unittests/gui/CMakeLists.txt b/src/unittests/gui/CMakeLists.txt index 871ed4b66..05241f2e5 100644 --- a/src/unittests/gui/CMakeLists.txt +++ b/src/unittests/gui/CMakeLists.txt @@ -10,3 +10,10 @@ create_test( SOURCE LoggerTests.cpp WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui" ) + +create_test( + NAME KeySequenceTests + DEPENDS gui + SOURCE KeySequenceTests.cpp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/lib/gui" +) diff --git a/src/unittests/gui/KeySequenceTests.cpp b/src/unittests/gui/KeySequenceTests.cpp new file mode 100644 index 000000000..bed84ed7f --- /dev/null +++ b/src/unittests/gui/KeySequenceTests.cpp @@ -0,0 +1,22 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include "KeySequenceTests.h" + +#include "gui/KeySequence.h" + +void KeySequenceTests::toString_controlShiftPlus_usesNamedPlus() +{ + KeySequence sequence; + + sequence.appendKey(Qt::Key_Control, Qt::ControlModifier); + sequence.appendKey(Qt::Key_Shift, Qt::ControlModifier | Qt::ShiftModifier); + QVERIFY(sequence.appendKey(Qt::Key_Plus, Qt::ControlModifier | Qt::ShiftModifier)); + + QCOMPARE(sequence.toString(), QStringLiteral("Control+Shift+Plus")); +} + +QTEST_MAIN(KeySequenceTests) diff --git a/src/unittests/gui/KeySequenceTests.h b/src/unittests/gui/KeySequenceTests.h new file mode 100644 index 000000000..737c03eb0 --- /dev/null +++ b/src/unittests/gui/KeySequenceTests.h @@ -0,0 +1,14 @@ +/* + * Deskflow -- mouse and keyboard sharing utility + * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers + * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception + */ + +#include + +class KeySequenceTests : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void toString_controlShiftPlus_usesNamedPlus(); +};