windowrules: rewrite completely (#12269)

Reworks the window rule syntax completely

---------

Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
Vaxry 2025-11-17 18:34:02 +00:00 committed by GitHub
parent 95ee08b340
commit c2670e9ab9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 3574 additions and 2255 deletions

View file

@ -64,7 +64,7 @@ static bool startClient(SClient& client) {
// wait for window to appear
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}
@ -130,7 +130,7 @@ static bool test() {
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 30);
EXPECT(getFromSocket("r/dispatch setprop active scrollmouse 4"), "ok");
EXPECT(getFromSocket("r/dispatch setprop active scroll_mouse 4"), "ok");
EXPECT(sendScroll(10), true);
EXPECT(getLastDelta(client), 40);

View file

@ -64,7 +64,7 @@ static bool startClient(SClient& client) {
// wait for window to appear
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
if (getFromSocket(std::format("/dispatch setprop pid:{} noanim 1", client.proc->pid())) != "ok") {
if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") {
NLog::log("{}Failed to disable animations for client window", Colors::RED, ret);
return false;
}

View file

@ -56,82 +56,82 @@ static bool testGetprop() {
return false;
}
// animationstyle
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "(unset)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": ""})");
getFromSocket("/dispatch setprop class:kitty animationstyle teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animationstyle -j"), R"({"animationstyle": "teststyle"})");
// animation
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "(unset)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": ""})");
getFromSocket("/dispatch setprop class:kitty animation teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation"), "teststyle");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty animation -j"), R"({"animation": "teststyle"})");
// maxsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "inf inf");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [null,null]})");
getFromSocket("/dispatch setprop class:kitty maxsize 200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty maxsize -j"), R"({"maxsize": [200,150]})");
// max_size
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "inf inf");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [null,null]})");
getFromSocket("/dispatch setprop class:kitty max_size 200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "200 150");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [200,150]})");
// minsize
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "20 20");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [20,20]})");
getFromSocket("/dispatch setprop class:kitty minsize 100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty minsize -j"), R"({"minsize": [100,50]})");
// min_size
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "20 20");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [20,20]})");
getFromSocket("/dispatch setprop class:kitty min_size 100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})");
// alpha
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 1})");
getFromSocket("/dispatch setprop class:kitty alpha 0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alpha -j"), R"({"alpha": 0.3})");
// opacity
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})");
getFromSocket("/dispatch setprop class:kitty opacity 0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "0.3");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 0.3})");
// alphainactive
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 1})");
getFromSocket("/dispatch setprop class:kitty alphainactive 0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactive -j"), R"({"alphainactive": 0.5})");
// opacity_inactive
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 1})");
getFromSocket("/dispatch setprop class:kitty opacity_inactive 0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive"), "0.5");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive -j"), R"({"opacity_inactive": 0.5})");
// alphafullscreen
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 1})");
getFromSocket("/dispatch setprop class:kitty alphafullscreen 0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreen -j"), R"({"alphafullscreen": 0.75})");
// opacity_fullscreen
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "1");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 1})");
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen 0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen"), "0.75");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen -j"), R"({"opacity_fullscreen": 0.75})");
// alphaoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphaoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphaoverride -j"), R"({"alphaoverride": true})");
// opacity_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_override true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_override -j"), R"({"opacity_override": true})");
// alphainactiveoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphainactiveoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphainactiveoverride -j"), R"({"alphainactiveoverride": true})");
// opacity_inactive_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_inactive_override true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_inactive_override -j"), R"({"opacity_inactive_override": true})");
// alphafullscreenoverride
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": false})");
getFromSocket("/dispatch setprop class:kitty alphafullscreenoverride true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty alphafullscreenoverride -j"), R"({"alphafullscreenoverride": true})");
// opacity_fullscreen_override
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": false})");
getFromSocket("/dispatch setprop class:kitty opacity_fullscreen_override true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity_fullscreen_override -j"), R"({"opacity_fullscreen_override": true})");
// activebordercolor
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ee33ccff ee00ff99 45deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ee33ccff ee00ff99 45deg"})");
getFromSocket("/dispatch setprop class:kitty activebordercolor rgb(abcdef)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor"), "ffabcdef 0deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty activebordercolor -j"), R"({"activebordercolor": "ffabcdef 0deg"})");
// active_border_color
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ee33ccff ee00ff99 45deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ee33ccff ee00ff99 45deg"})");
getFromSocket("/dispatch setprop class:kitty active_border_color rgb(abcdef)");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color"), "ffabcdef 0deg");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty active_border_color -j"), R"({"active_border_color": "ffabcdef 0deg"})");
// bool window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": false})");
getFromSocket("/dispatch setprop class:kitty allowsinput true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allowsinput -j"), R"({"allowsinput": true})");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "false");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": false})");
getFromSocket("/dispatch setprop class:kitty allows_input true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input"), "true");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty allows_input -j"), R"({"allows_input": true})");
// int window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10");
@ -141,16 +141,16 @@ static bool testGetprop() {
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 4})");
// float window properties
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "2");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 2})");
getFromSocket("/dispatch setprop class:kitty roundingpower 1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty roundingpower -j"), R"({"roundingpower": 1.25})");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "2");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 2})");
getFromSocket("/dispatch setprop class:kitty rounding_power 1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power"), "1.25");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding_power -j"), R"({"rounding_power": 1.25})");
// errors
EXPECT(getCommandStdOut("hyprctl getprop"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty"), "not enough args");
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animationstyle"), "window not found");
EXPECT(getCommandStdOut("hyprctl getprop class:nonexistantclass animation"), "window not found");
EXPECT(getCommandStdOut("hyprctl getprop class:kitty nonexistantprop"), "prop not found");
// kill all

View file

@ -21,21 +21,24 @@ static bool testTags() {
NLog::log("{}Testing testTag tags", Colors::YELLOW);
OK(getFromSocket("/keyword windowrule tag +testTag, class:tagged"));
OK(getFromSocket("/keyword windowrule noshadow, tag:negative:testTag"));
OK(getFromSocket("/keyword windowrule noborder, tag:testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-1]:tag +testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-1]:match:class tagged"));
OK(getFromSocket("/keyword windowrule[tag-test-2]:match:tag negative:testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-2]:no_shadow true"));
OK(getFromSocket("/keyword windowrule[tag-test-3]:match:tag testTag"));
OK(getFromSocket("/keyword windowrule[tag-test-3]:no_dim true"));
EXPECT(Tests::windowCount(), 2);
OK(getFromSocket("/dispatch focuswindow class:tagged"));
NLog::log("{}Testing tagged window for noborder & noshadow", Colors::YELLOW);
NLog::log("{}Testing tagged window for no_dim 0 & no_shadow", Colors::YELLOW);
EXPECT_CONTAINS(getFromSocket("/activewindow"), "testTag");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "false");
NLog::log("{}Testing untagged window for noborder & noshadow", Colors::YELLOW);
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "false");
NLog::log("{}Testing untagged window for no_dim & no_shadow", Colors::YELLOW);
OK(getFromSocket("/dispatch focuswindow class:untagged"));
EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "testTag");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow noborder"), "false");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow noshadow"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_shadow"), "true");
EXPECT_CONTAINS(getFromSocket("/getprop activewindow no_dim"), "false");
Tests::killAllWindows();
EXPECT(Tests::windowCount(), 0);

View file

@ -246,12 +246,15 @@ static bool test() {
testSwapWindow();
getFromSocket("/dispatch workspace 1");
NLog::log("{}Testing minsize/maxsize rules for tiled windows", Colors::YELLOW);
{
// Enable the config for testing, test max/minsize for tiled windows and centering
OK(getFromSocket("/keyword misc:size_limits_tiled 1"));
OK(getFromSocket("/keyword windowrule maxsize 1500 500, class:kitty_maxsize"));
OK(getFromSocket("/keyword windowrule minsize 1200 500, class:kitty_maxsize"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:match:class kitty_maxsize"));
OK(getFromSocket("/keyword windowrule[kitty-max-rule]:max_size 1500 500"));
OK(getFromSocket("r/keyword windowrule[kitty-max-rule]:min_size 1200 500"));
if (!spawnKitty("kitty_maxsize"))
return false;
@ -297,29 +300,127 @@ static bool test() {
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, std::format("size: {},{}", SIZE, SIZE));
EXPECT_NOT_CONTAINS(str, "pinned: 1");
OK(getFromSocket("/keyword windowrule plugin:someplugin:variable, class:wr_kitty"));
OK(getFromSocket("/keyword windowrule plugin:someplugin:variable 10, class:wr_kitty"));
OK(getFromSocket("/keyword windowrule workspace 1, class:wr_kitty"));
OK(getFromSocket("/keyword windowrule workspace special:magic, class:magic_kitty"));
}
if (!spawnKitty("magic_kitty"))
return false;
EXPECT_CONTAINS(getFromSocket("/activewindow"), "special:magic");
OK(getFromSocket("/keyword windowrule[wr-kitty-stuff]:opacity 0.5 0.5 override"));
{
auto str = getFromSocket("/getprop active opacity");
EXPECT_CONTAINS(str, "0.5");
}
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:match:class magic_kitty"));
OK(getFromSocket("/keyword windowrule[special-magic-kitty]:workspace special:magic"));
if (!spawnKitty("magic_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "special:magic");
EXPECT_NOT_CONTAINS(str, "workspace: 9");
}
NLog::log("{}Testing faulty rules", Colors::YELLOW);
{
const auto PARAM = "Invalid parameter";
const auto RULE = "Invalid value";
const auto NORULE = "no rules provided";
EXPECT_CONTAINS(getFromSocket("/keyword windowrule notarule, class:wr_kitty"), RULE)
EXPECT_CONTAINS(getFromSocket("/keyword windowrule class:wr_kitty"), NORULE)
EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, class:wr_kitty, size"), PARAM)
EXPECT_CONTAINS(getFromSocket("/keyword windowrule float, classI:wr_kitty"), PARAM)
EXPECT_CONTAINS(getFromSocket("/keyword windowrule workspace:, class:wr_kitty"), NORULE)
if (auto str = getFromSocket("/monitors"); str.contains("magic)")) {
OK(getFromSocket("/dispatch togglespecialworkspace magic"));
}
Tests::killAllWindows();
if (!spawnKitty("tag_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "floating: 1");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
// test rules that overlap effects but don't overlap props
OK(getFromSocket("/keyword windowrule match:class overlap_kitty, border_size 0"));
OK(getFromSocket("/keyword windowrule match:fullscreen false, border_size 10"));
if (!spawnKitty("overlap_kitty"))
return false;
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "10");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/keyword general:border_size 0"));
OK(getFromSocket("/keyword windowrule match:float true, border_size 10"));
if (!spawnKitty("border_kitty"))
return false;
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/dispatch togglefloating"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "10");
}
OK(getFromSocket("/dispatch togglefloating"));
{
auto str = getFromSocket("/getprop active border_size");
EXPECT_CONTAINS(str, "0");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
// test expression rules
OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5"));
if (!spawnKitty("expr_kitty"))
return false;
{
auto str = getFromSocket("/activewindow");
EXPECT_CONTAINS(str, "floating: 1");
EXPECT_CONTAINS(str, "at: 212,540");
EXPECT_CONTAINS(str, "size: 960,540");
}
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_rule"));
OK(getFromSocket("/reload"));
Tests::killAllWindows();
OK(getFromSocket("/dispatch plugin:test:add_rule"));
OK(getFromSocket("/reload"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty"));
OK(getFromSocket("/keyword windowrule[test-plugin-rule]:plugin_rule effect"));
if (!spawnKitty("plugin_kitty"))
return false;
OK(getFromSocket("/dispatch plugin:test:check_rule"));
NLog::log("{}Reloading config", Colors::YELLOW);
OK(getFromSocket("/reload"));

View file

@ -27,7 +27,7 @@ static bool test() {
// test on workspace "window"
NLog::log("{}Switching to workspace 1", Colors::YELLOW);
OK(getFromSocket("/dispatch workspace 1"));
getFromSocket("/dispatch workspace 1");
NLog::log("{}Checking persistent no-mon", Colors::YELLOW);
OK(getFromSocket("r/keyword workspace 966,persistent:1"));