hyprctl: add getprop (#11394)

This commit is contained in:
Luke Barkess
2025-08-18 16:41:17 +01:00
committed by GitHub
parent d890178610
commit 21953ddf3d
4 changed files with 272 additions and 0 deletions

View File

@@ -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> <property>
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> <cmd>
device:

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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<SAlphaValue>& 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<Hyprlang::CUSTOMTYPE>("general:col.active_border");
static auto PINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.inactive_border");
static auto PNOGROUPACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.nogroup_border_active");
static auto PNOGROUPINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("general:col.nogroup_border");
static auto PGROUPACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_active");
static auto PGROUPINACTIVECOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_inactive");
static auto PGROUPACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("group:col.border_locked_active");
static auto PGROUPINACTIVELOCKEDCOL = CConfigValue<Hyprlang::CUSTOMTYPE>("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});