/* * Deskflow -- mouse and keyboard sharing utility * SPDX-FileCopyrightText: (C) 2026 Deskflow Developers * SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception */ #include "EiKeyStateTests.h" #include "base/EventQueue.h" #include "deskflow/AppUtil.h" #include "deskflow/KeyTypes.h" #include "platform/EiKeyState.h" #include #include #include namespace { class TestAppUtil : public AppUtil { public: int run() override { return 0; } std::vector getKeyboardLayoutList() override { return {"en"}; } std::string getCurrentLanguageCode() override { return "en"; } }; const char TestKeymap[] = R"XKB(xkb_keymap { xkb_keycodes "test" { minimum = 8; maximum = 255; = 50; = 77; }; xkb_types "test" { type "ONE_LEVEL" { modifiers = none; level_name[Level1] = "Any"; }; }; xkb_compat "test" { interpret Shift_L+AnyOfOrNone(all) { action = SetMods(modifiers=Shift); }; interpret Num_Lock+AnyOfOrNone(all) { action = LockMods(modifiers=Mod2); }; }; xkb_symbols "test" { key { [ Shift_L ] }; key { [ Num_Lock ] }; modifier_map Shift { }; modifier_map Mod2 { }; }; };)XKB"; // XKB keycodes for TestKeymap. constexpr std::uint32_t LeftShiftKeycode = 50; constexpr std::uint32_t NumLockKeycode = 77; } // namespace void EiKeyStateTests::initTestCase() { m_arch.init(); m_log.setFilter(LogLevel::Level::Verbose); } void EiKeyStateTests::clearStaleModifiers_shiftDownAndNumLockOn_shiftClearedAndNumLockPreserved() { TestAppUtil appUtil; EventQueue eventQueue; deskflow::EiKeyState keyState(nullptr, &eventQueue); QTemporaryFile keymapFile; QVERIFY(keymapFile.open()); const QByteArray keymapData = QByteArray::fromRawData(TestKeymap, sizeof(TestKeymap) - 1); QCOMPARE(keymapFile.write(keymapData), keymapData.size()); QVERIFY(keymapFile.flush()); keyState.init(keymapFile.handle(), keymapFile.size()); keyState.updateXkbState(LeftShiftKeycode, true); keyState.updateXkbState(NumLockKeycode, true); keyState.updateXkbState(NumLockKeycode, false); QVERIFY((keyState.pollActiveModifiers() & KeyModifierShift) != 0); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) != 0); keyState.clearStaleModifiers(); QVERIFY((keyState.pollActiveModifiers() & KeyModifierShift) == 0); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) != 0); } void EiKeyStateTests::updateLockedModifiers_compositorLockState_numLockFollowsCompositor() { // Real modifier Mod2 is index 4 (Shift, Lock, Control, Mod1, Mod2, ...). constexpr std::uint32_t xkbMod2Mask = 1 << 4; TestAppUtil appUtil; EventQueue eventQueue; deskflow::EiKeyState keyState(nullptr, &eventQueue); QTemporaryFile keymapFile; QVERIFY(keymapFile.open()); const QByteArray keymapData = QByteArray::fromRawData(TestKeymap, sizeof(TestKeymap) - 1); QCOMPARE(keymapFile.write(keymapData), keymapData.size()); QVERIFY(keymapFile.flush()); keyState.init(keymapFile.handle(), keymapFile.size()); keyState.updateXkbState(LeftShiftKeycode, true); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) == 0); // NumLock toggled on locally, reported by the compositor keyState.updateLockedModifiers(xkbMod2Mask); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) != 0); QVERIFY((keyState.pollActiveModifiers() & KeyModifierShift) != 0); keyState.clearStaleModifiers(); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) != 0); // NumLock toggled off locally keyState.updateLockedModifiers(0); QVERIFY((keyState.pollActiveModifiers() & KeyModifierNumLock) == 0); } QTEST_MAIN(EiKeyStateTests)