Files
Hyprland/hyprtester/src/tests/main/keybinds.cpp
T
VaxryandGitHub 5ba33f8461 config/lua: init lua config manager, use lua if available (#13817)
Adds a new lua config manager, which will automatically be used if there is a lua config present.
2026-04-26 15:16:36 +01:00

595 lines
24 KiB
C++

#include <filesystem>
#include <linux/input-event-codes.h>
#include <thread>
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include "../shared.hpp"
#include "tests.hpp"
using namespace Hyprutils::OS;
using namespace Hyprutils::Memory;
static int ret = 0;
static std::string flagFile = "/tmp/hyprtester-keybinds.txt";
static std::string pluginKeybindCmd(bool pressed, uint32_t modifier, uint32_t key) {
return "/eval hl.plugin.test.keybind(" + std::to_string(pressed ? 1 : 0) + ", " + std::to_string(modifier) + ", " + std::to_string(key) + ")";
}
static std::string pluginScrollCmd(int delta) {
return "/eval hl.plugin.test.scroll(" + std::to_string(delta) + ")";
}
// Because i don't feel like changing someone elses code.
enum eKeyboardModifierIndex : uint8_t {
MOD_SHIFT = 1,
MOD_CAPS,
MOD_CTRL,
MOD_ALT,
MOD_MOD2,
MOD_MOD3,
MOD_META,
MOD_MOD5
};
static void clearFlag() {
std::filesystem::remove(flagFile);
}
static bool checkFlag() {
bool exists = std::filesystem::exists(flagFile);
clearFlag();
return exists;
}
static bool attemptCheckFlag(int attempts, int intervalMs) {
for (int i = 0; i < attempts; i++) {
if (checkFlag())
return true;
std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs));
}
return false;
}
static std::string readKittyOutput() {
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
// chop off shell prompt
std::size_t pos = output.rfind("$");
if (pos != std::string::npos) {
pos += 1;
if (pos < output.size())
output.erase(0, pos);
}
// NLog::log("Kitty output: '{}'", output);
return output;
}
static void awaitKittyPrompt() {
// wait until we see the shell prompt, meaning it's ready for test inputs
for (int i = 0; i < 10; i++) {
std::string output = Tests::execAndGet("kitten @ --to unix:/tmp/hyprtester-kitty.sock get-text --extent all");
if (output.rfind("$") == std::string::npos) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
continue;
}
return;
}
NLog::log("{}Error: timed out waiting for kitty prompt", Colors::RED);
}
static CUniquePointer<CProcess> spawnRemoteControlKitty() {
auto kittyProc = Tests::spawnKitty("keybinds_test", {"-o", "allow_remote_control=yes", "--listen-on", "unix:/tmp/hyprtester-kitty.sock", "--config", "NONE", "/bin/sh"});
// wait a bit to ensure shell prompt is sent, we are going to read the text after it
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (kittyProc)
awaitKittyPrompt();
return kittyProc;
}
static void testBind() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
EXPECT(attemptCheckFlag(20, 50), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testBindKey() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'))"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// await flag
EXPECT(attemptCheckFlag(20, 50), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testKeyLongPress() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testLongPressRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testLongPressOnlyKeyRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// check no flag on short press
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), false);
// release key, keep modifier
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// check that it continues repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testKeyRepeat() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), true);
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// check that it continues repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
}
static void testRepeatRelease() {
// wait until flag becomes false (CI timing can vary)
bool ok = false;
for (int i = 0; i < 20; ++i) {
if (!checkFlag()) {
ok = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
EXPECT(ok, true);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(50));
EXPECT(checkFlag(), true);
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
// check that it is not repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testRepeatOnlyKeyRelease() {
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await flag
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), true);
// release key, keep modifier
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
clearFlag();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
// check that it is not repeating
std::this_thread::sleep_for(std::chrono::milliseconds(200));
EXPECT(checkFlag(), false);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testShortcutBind() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testShortcutBindKey() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }))"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 0, 29)));
// release keybind
std::this_thread::sleep_for(std::chrono::milliseconds(50));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
// disabled: doesn't work in CI
// EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/eval hl.unbind('', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testShortcutLongPress() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 10 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
const std::string output = readKittyOutput();
int yCount = Tests::countOccurrences(output, "y");
// sometimes 1, sometimes 2, not sure why
// keybind press sends 1 y immediately
// then repeat triggers, sending 1 y
// final release stop repeats, and shouldn't send any more
EXPECT(true, yCount == 1 || yCount == 2);
EXPECT_COUNT_STRING(output, "q", 1);
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testShortcutLongPressKeyRelease() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { long_press = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 100 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 10 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// release key, keep modifier
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// await repeat delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
const std::string output = readKittyOutput();
// disabled: doesn't work on CI
// EXPECT_COUNT_STRING(output, "y", 1);
EXPECT_COUNT_STRING(output, "q", 0);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testShortcutRepeat() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 5 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 200 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
// await repeat
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release keybind
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(450));
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
int qCount = Tests::countOccurrences(output, "q");
// sometimes 2, sometimes 3, not sure why
// keybind press sends 1 q immediately
// then repeat triggers, sending 1 q
// final release stop repeats, and shouldn't send any more
EXPECT(true, qCount == 2 || qCount == 3);
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testShortcutRepeatKeyRelease() {
auto kittyProc = spawnRemoteControlKitty();
if (!kittyProc) {
NLog::log("{}Error: kitty did not spawn", Colors::RED);
ret = 1;
return;
}
EXPECT(getFromSocket("/dispatch hl.dsp.focus({ window = 'class:keybinds_test' })"), "ok");
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.send_shortcut({ mods = '', key = 'q', window = 'activewindow' }), { repeating = true })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_rate = 5 } })"), "ok");
EXPECT(getFromSocket("r/eval hl.config({ input = { repeat_delay = 200 } })"), "ok");
// press keybind
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
std::this_thread::sleep_for(std::chrono::milliseconds(210));
// release key, keep modifier
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
// if repeat was still active, we'd get 2 more q's here
std::this_thread::sleep_for(std::chrono::milliseconds(450));
// release modifier
const std::string output = readKittyOutput();
EXPECT_COUNT_STRING(output, "y", 0);
int qCount = Tests::countOccurrences(output, "q");
// sometimes 2, sometimes 3, not sure why
// keybind press sends 1 q immediately
// then repeat triggers, sending 1 q
// final release stop repeats, and shouldn't send any more
EXPECT(true, qCount == 2 || qCount == 3);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
Tests::killAllWindows();
}
static void testSubmap() {
const auto press = [](const uint32_t key, const uint32_t mod = 0) {
// +8 because udev -> XKB keycode.
getFromSocket(pluginKeybindCmd(true, mod, key + 8));
getFromSocket(pluginKeybindCmd(false, mod, key + 8));
};
NLog::log("{}Testing submaps", Colors::GREEN);
// submap 1 no resets
press(KEY_U, MOD_META);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
press(KEY_O);
Tests::waitUntilWindowsN(1);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
// submap 2 resets to submap 1
press(KEY_U);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap2");
press(KEY_O);
Tests::waitUntilWindowsN(2);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
// submap 3 resets to default
press(KEY_I);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap3");
press(KEY_O);
Tests::waitUntilWindowsN(3);
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
// submap 1 reset via keybind
press(KEY_U, MOD_META);
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
press(KEY_P);
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
Tests::killAllWindows();
}
static void testBindsAfterScroll() {
NLog::log("{}Testing binds after scroll", Colors::GREEN);
clearFlag();
OK(getFromSocket("/eval hl.bind('ALT + w', hl.dsp.exec_cmd('touch " + flagFile + "'))"));
// press keybind before scroll
OK(getFromSocket(pluginKeybindCmd(true, 0, 108))); // Alt_R press
OK(getFromSocket(pluginKeybindCmd(true, 4, 25))); // w press
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 4, 25))); // w release
OK(getFromSocket(pluginKeybindCmd(false, 0, 108))); // Alt_R release
// scroll
OK(getFromSocket(pluginScrollCmd(120)));
OK(getFromSocket(pluginScrollCmd(-120)));
OK(getFromSocket(pluginScrollCmd(120)));
// press keybind after scroll
OK(getFromSocket(pluginKeybindCmd(true, 0, 108))); // Alt_R press
OK(getFromSocket(pluginKeybindCmd(true, 4, 25))); // w press
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 4, 25))); // w release
OK(getFromSocket(pluginKeybindCmd(false, 0, 108))); // Alt_R release
clearFlag();
OK(getFromSocket("/eval hl.unbind('ALT', 'w')"));
}
static void testSubmapUniversal() {
NLog::log("{}Testing submap universal", Colors::GREEN);
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { submap_universal = true })"), "ok");
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
// keybind works on default submap
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
EXPECT(attemptCheckFlag(30, 5), true);
// keybind works on submap1
getFromSocket(pluginKeybindCmd(true, 7, 30));
getFromSocket(pluginKeybindCmd(false, 7, 30));
EXPECT_CONTAINS(getFromSocket("/submap"), "submap1");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
OK(getFromSocket(pluginKeybindCmd(false, 7, 29)));
EXPECT(attemptCheckFlag(30, 5), true);
// reset to default submap
getFromSocket(pluginKeybindCmd(true, 0, 33));
getFromSocket(pluginKeybindCmd(false, 0, 33));
EXPECT_CONTAINS(getFromSocket("/submap"), "default");
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static void testPerDeviceKeybind() {
NLog::log("{}Testing per-device binds", Colors::GREEN);
// Inclusive
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { device = { inclusive = true, list = { 'test-keyboard-1' } } })"), "ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
// Exclusive
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile + "'), { device = { inclusive = false, list = { 'test-keyboard-1' } } })"), "ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), false);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
// With description
EXPECT(checkFlag(), false);
EXPECT(getFromSocket("/eval hl.bind('SUPER + Y', hl.dsp.exec_cmd('touch " + flagFile +
"'), { description = 'test description', device = { inclusive = true, list = { 'test-keyboard-1' } } })"),
"ok");
OK(getFromSocket(pluginKeybindCmd(true, 7, 29)));
EXPECT(attemptCheckFlag(20, 50), true);
OK(getFromSocket(pluginKeybindCmd(false, 0, 29)));
EXPECT(getFromSocket("/eval hl.unbind('SUPER', 'Y')"), "ok");
}
static bool test() {
NLog::log("{}Testing keybinds", Colors::GREEN);
clearFlag();
testBind();
testBindKey();
testLongPress();
testKeyLongPress();
testLongPressRelease();
testLongPressOnlyKeyRelease();
testRepeat();
testKeyRepeat();
testRepeatRelease();
testRepeatOnlyKeyRelease();
testShortcutBind();
testShortcutBindKey();
testShortcutLongPress();
testShortcutLongPressKeyRelease();
testShortcutRepeat();
testShortcutRepeatKeyRelease();
testSubmap();
testSubmapUniversal();
testBindsAfterScroll();
testPerDeviceKeybind();
clearFlag();
return !ret;
}
REGISTER_TEST_FN(test)