diff --git a/hyprctl/Strings.hpp b/hyprctl/Strings.hpp index f738561e3..67e4f992c 100644 --- a/hyprctl/Strings.hpp +++ b/hyprctl/Strings.hpp @@ -49,6 +49,7 @@ commands: the same format as in colors in config. Will reset when Hyprland's config is reloaded setprop ... → Sets a window property + getprop ... → Gets a window property splash → Get the current splash switchxkblayout ... → Sets the xkb layout index for a keyboard systeminfo → Get system info @@ -159,6 +160,18 @@ lock: flags: See 'hyprctl --help')#"; +const std::string_view GETPROP_HELP = R"#(usage: hyprctl [flags] getprop + +regex: + Regular expression by which a window will be searched + +property: + See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list + of properties + +flags: + See 'hyprctl --help')#"; + const std::string_view SWITCHXKBLAYOUT_HELP = R"#(usage: [flags] switchxkblayout device: diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index b15d3e218..e15a17f5f 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/main.cpp @@ -427,6 +427,8 @@ int main(int argc, char** argv) { std::println("{}", PLUGIN_HELP); } else if (cmd == "setprop") { std::println("{}", SETPROP_HELP); + } else if (cmd == "getprop") { + std::println("{}", GETPROP_HELP); } else if (cmd == "switchxkblayout") { std::println("{}", SWITCHXKBLAYOUT_HELP); } else { diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index c16b0d979..2946c6305 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -18,6 +18,131 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer +static std::string getCommandStdOut(std::string command) { + CProcess process("bash", {"-c", command}); + process.addEnv("HYPRLAND_INSTANCE_SIGNATURE", HIS); + process.runSync(); + + const std::string& stdOut = process.stdOut(); + + // Remove trailing new line + return stdOut.substr(0, stdOut.length() - 1); +} + +static bool testGetprop() { + NLog::log("{}Testing hyprctl getprop", Colors::GREEN); + if (!Tests::spawnKitty()) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + 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"})"); + + // 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]})"); + + // 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]})"); + + // 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})"); + + // 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})"); + + // 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})"); + + // 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})"); + + // 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})"); + + // 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})"); + + // 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"})"); + + // 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})"); + + // int window properties + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "10"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding -j"), R"({"rounding": 10})"); + getFromSocket("/dispatch setprop class:kitty rounding 4"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty rounding"), "4"); + 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})"); + + // 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:kitty nonexistantprop"), "prop not found"); + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return true; +} + static bool test() { NLog::log("{}Testing hyprctl", Colors::GREEN); @@ -29,6 +154,9 @@ static bool test() { EXPECT(jqProc.exitCode(), 0); } + if (!testGetprop()) + return false; + return !ret; } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 758138fda..45aeb4f1b 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1399,6 +1399,134 @@ static std::string dispatchSetProp(eHyprCtlOutputFormat format, std::string requ return "DEPRECATED: use hyprctl dispatch setprop instead" + (result.success ? "" : "\n" + result.error); } +static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string request) { + CVarList vars(request, 0, ' '); + + if (vars.size() < 3) + return "not enough args"; + + const auto WINREGEX = vars[1]; + const auto PROP = vars[2]; + + const auto PWINDOW = g_pCompositor->getWindowByRegex(WINREGEX); + + if (!PWINDOW) + return "window not found"; + + const bool FORMNORM = format == FORMAT_NORMAL; + + auto sizeToString = [&](bool max) -> std::string { + auto sizeValue = PWINDOW->m_windowData.minSize.valueOr(Vector2D(MIN_WINDOW_SIZE, MIN_WINDOW_SIZE)); + if (max) + sizeValue = PWINDOW->m_windowData.maxSize.valueOr(Vector2D(INFINITY, INFINITY)); + + if (FORMNORM) + return std::format("{} {}", sizeValue.x, sizeValue.y); + else { + std::string xSizeString = (sizeValue.x != INFINITY) ? std::to_string(sizeValue.x) : "null"; + std::string ySizeString = (sizeValue.y != INFINITY) ? std::to_string(sizeValue.y) : "null"; + return std::format(R"({{"{}": [{},{}]}})", PROP, xSizeString, ySizeString); + } + }; + + auto alphaToString = [&](CWindowOverridableVar& alpha, bool getAlpha) -> std::string { + if (FORMNORM) { + if (getAlpha) + return std::format("{}", alpha.valueOrDefault().alpha); + else + return std::format("{}", alpha.valueOrDefault().overridden); + } else { + if (getAlpha) + return std::format(R"({{"{}": {}}})", PROP, alpha.valueOrDefault().alpha); + else + return std::format(R"({{"{}": {}}})", PROP, alpha.valueOrDefault().overridden); + } + }; + + auto borderColorToString = [&](bool active) -> std::string { + static auto PACTIVECOL = CConfigValue("general:col.active_border"); + static auto PINACTIVECOL = CConfigValue("general:col.inactive_border"); + static auto PNOGROUPACTIVECOL = CConfigValue("general:col.nogroup_border_active"); + static auto PNOGROUPINACTIVECOL = CConfigValue("general:col.nogroup_border"); + static auto PGROUPACTIVECOL = CConfigValue("group:col.border_active"); + static auto PGROUPINACTIVECOL = CConfigValue("group:col.border_inactive"); + static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); + static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); + + const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false; + + if (active) { + auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); + auto* const NOGROUPACTIVECOL = (CGradientValueData*)(PNOGROUPACTIVECOL.ptr())->getData(); + auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); + auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); + const auto* const ACTIVECOLOR = + !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + + std::string borderColorString = PWINDOW->m_windowData.activeBorderColor.valueOr(*ACTIVECOLOR).toString(); + if (FORMNORM) + return borderColorString; + else + return std::format(R"({{"{}": "{}"}})", PROP, borderColorString); + } else { + auto* const INACTIVECOL = (CGradientValueData*)(PINACTIVECOL.ptr())->getData(); + auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); + auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); + auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); + const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : + (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + + std::string borderColorString = PWINDOW->m_windowData.inactiveBorderColor.valueOr(*INACTIVECOLOR).toString(); + if (FORMNORM) + return borderColorString; + else + return std::format(R"({{"{}": "{}"}})", PROP, borderColorString); + } + }; + + auto windowPropToString = [&](auto& prop) -> std::string { + if (FORMNORM) + return std::format("{}", prop.valueOrDefault()); + else + return std::format(R"({{"{}": {}}})", PROP, prop.valueOrDefault()); + }; + + if (PROP == "animationstyle") { + auto& animationStyle = PWINDOW->m_windowData.animationStyle; + if (FORMNORM) + return animationStyle.valueOr("(unset)"); + else + return std::format(R"({{"{}": "{}"}})", PROP, animationStyle.valueOr("")); + } else if (PROP == "maxsize") + return sizeToString(true); + else if (PROP == "minsize") + return sizeToString(false); + else if (PROP == "alpha") + return alphaToString(PWINDOW->m_windowData.alpha, true); + else if (PROP == "alphainactive") + return alphaToString(PWINDOW->m_windowData.alphaInactive, true); + else if (PROP == "alphafullscreen") + return alphaToString(PWINDOW->m_windowData.alphaFullscreen, true); + else if (PROP == "alphaoverride") + return alphaToString(PWINDOW->m_windowData.alpha, false); + else if (PROP == "alphainactiveoverride") + return alphaToString(PWINDOW->m_windowData.alphaInactive, false); + else if (PROP == "alphafullscreenoverride") + return alphaToString(PWINDOW->m_windowData.alphaFullscreen, false); + else if (PROP == "activebordercolor") + return borderColorToString(true); + else if (PROP == "inactivebordercolor") + return borderColorToString(false); + else if (auto search = NWindowProperties::boolWindowProperties.find(PROP); search != NWindowProperties::boolWindowProperties.end()) + return windowPropToString(*search->second(PWINDOW)); + else if (auto search = NWindowProperties::intWindowProperties.find(PROP); search != NWindowProperties::intWindowProperties.end()) + return windowPropToString(*search->second(PWINDOW)); + else if (auto search = NWindowProperties::floatWindowProperties.find(PROP); search != NWindowProperties::floatWindowProperties.end()) + return windowPropToString(*search->second(PWINDOW)); + + return "prop not found"; +} + static std::string dispatchGetOption(eHyprCtlOutputFormat format, std::string request) { std::string curitem = ""; @@ -1760,6 +1888,7 @@ CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"notify", false, dispatchNotify}); registerCommand(SHyprCtlCommand{"dismissnotify", false, dispatchDismissNotify}); registerCommand(SHyprCtlCommand{"setprop", false, dispatchSetProp}); + registerCommand(SHyprCtlCommand{"getprop", false, dispatchGetProp}); registerCommand(SHyprCtlCommand{"seterror", false, dispatchSeterror}); registerCommand(SHyprCtlCommand{"switchxkblayout", false, switchXKBLayoutRequest}); registerCommand(SHyprCtlCommand{"output", false, dispatchOutput});