3
0
mirror of https://github.com/hyprwm/Hyprland.git synced 2026-08-18 02:51:23 +00:00

hyprctl: random Lua-adjacent fixes (#15767)

* hyprctl(repl): go interactive if no Lua was given via args

The previous check would mess up if `-i` was provided, since it'd see
*an argument* and refuse to go interactive, even when it should've (i.e.
`hyprctl -i 0 repl`).

* ipc(socket1): new escape parsing for batch commands

Semicolons are still the command separator, so those have to be escaped
if they appear anywhere inside of commands (e.g. multi-statement Lua
code). Also, literal backslashes need to be escaped as well.

* tests(ipc): add test for hyprctl batch mode

This test case includes slightly weird stuff (semicolons in Lua code;
square brackets) that would break the old parsing logic.

* format: dang braces
This commit is contained in:
Lucas Ritzdorf
2026-08-09 12:00:17 -06:00
committed by GitHub
parent b20621d48b
commit f7daeb7f79
3 changed files with 29 additions and 16 deletions

View File

@ -566,8 +566,8 @@ int main(int argc, char** argv) {
std::println("{}", USAGE); std::println("{}", USAGE);
else if (fullRequest.contains("/rollinglog") && needRoll) else if (fullRequest.contains("/rollinglog") && needRoll)
exitStatus = request(fullRequest, 0, true); exitStatus = request(fullRequest, 0, true);
else if (fullRequest.contains("/repl")) { else if (auto pos = fullRequest.find("/repl"); pos != std::string::npos) {
if (ARGS.size() > 1) { if (fullRequest.length() > pos + 5) {
// single command with output // single command with output
exitStatus = request(fullRequest, 1); exitStatus = request(fullRequest, 1);
} else { } else {

View File

@ -206,3 +206,16 @@ TEST_CASE(hyprctlREPL) {
EXPECT(getCommandStdOut("hyprctl repl 'print(type(hl))'"), "table"); EXPECT(getCommandStdOut("hyprctl repl 'print(type(hl))'"), "table");
EXPECT(getCommandStdOut("hyprctl eval 'print(type(hl))'"), "ok"); EXPECT(getCommandStdOut("hyprctl eval 'print(type(hl))'"), "ok");
} }
TEST_CASE(hyprctlBatch) {
const auto command = R"([[BATCH]] activewindow; repl local i = 42\; print(i, "hello\\nworld ]"); clients)";
const auto expected = R"(Invalid
42 hello
world ]
no open windows)";
EXPECT(getFromSocket(command), expected);
}

View File

@ -7,6 +7,7 @@
#include <algorithm> #include <algorithm>
#include <optional> #include <optional>
#include <ranges> #include <ranges>
#include <sstream>
#include <hyprutils/string/String.hpp> #include <hyprutils/string/String.hpp>
using namespace IPC::Socket1; using namespace IPC::Socket1;
@ -129,25 +130,24 @@ SResponse CSocket1::dispatchBatch(std::string request, pid_t pid) {
request = request.substr(BATCH_TOKEN.size()); request = request.substr(BATCH_TOKEN.size());
std::vector<std::string> commands; std::vector<std::string> commands;
size_t commandStart = 0; std::stringstream parsedCommand("");
int bracketDepth = 0;
for (size_t i = 0; i <= request.size(); ++i) { for (size_t i = 0; i <= request.size(); ++i) {
const bool atEnd = i == request.size(); const bool atEnd = i == request.size();
if (atEnd || request[i] == ';') {
if (!atEnd) { commands.emplace_back(Hyprutils::String::trim(parsedCommand.str()));
if (request[i] == '[') parsedCommand.str("");
++bracketDepth; parsedCommand.clear();
else if (request[i] == ']') continue;
--bracketDepth;
} }
if (!atEnd && (request[i] != ';' || bracketDepth != 0)) if (request[i] == '\\') {
continue; if (i < request.size() && (request[i + 1] == '\\' || request[i + 1] == ';'))
++i;
if (commandStart < i) else
commands.emplace_back(Hyprutils::String::trim(request.substr(commandStart, i - commandStart))); Log::logger->log(Log::ERR, "Malformed socket1 request: invalid escape sequence {} at position {}, using it verbatim", request.subview(i, 2), i);
commandStart = i + 1; }
parsedCommand << request[i];
} }
std::vector<SResponse> responses; std::vector<SResponse> responses;