hyprctl: use new hyprpaper ipc format (#12537)
--------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
parent
9b1891e476
commit
d9657a95cb
13 changed files with 392 additions and 14 deletions
183
hyprctl/src/Strings.hpp
Normal file
183
hyprctl/src/Strings.hpp
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
const std::string_view USAGE = R"#(usage: hyprctl [flags] <command> [args...|--help]
|
||||
|
||||
commands:
|
||||
activewindow → Gets the active window name and its properties
|
||||
activeworkspace → Gets the active workspace and its properties
|
||||
animations → Gets the current config'd info about animations
|
||||
and beziers
|
||||
binds → Lists all registered binds
|
||||
clients → Lists all windows with their properties
|
||||
configerrors → Lists all current config parsing errors
|
||||
cursorpos → Gets the current cursor position in global layout
|
||||
coordinates
|
||||
decorations <window_regex> → Lists all decorations and their info
|
||||
devices → Lists all connected keyboards and mice
|
||||
dismissnotify [amount] → Dismisses all or up to AMOUNT notifications
|
||||
dispatch <dispatcher> [args] → Issue a dispatch to call a keybind
|
||||
dispatcher with arguments
|
||||
getoption <option> → Gets the config option status (values)
|
||||
globalshortcuts → Lists all global shortcuts
|
||||
hyprpaper ... → Issue a hyprpaper request
|
||||
hyprsunset ... → Issue a hyprsunset request
|
||||
instances → Lists all running instances of Hyprland with
|
||||
their info
|
||||
keyword <name> <value> → Issue a keyword to call a config keyword
|
||||
dynamically
|
||||
kill → Issue a kill to get into a kill mode, where you can
|
||||
kill an app by clicking on it. You can exit it
|
||||
with ESCAPE
|
||||
layers → Lists all the surface layers
|
||||
layouts → Lists all layouts available (including plugin'd ones)
|
||||
monitors → Lists active outputs with their properties,
|
||||
'monitors all' lists active and inactive outputs
|
||||
notify ... → Sends a notification using the built-in Hyprland
|
||||
notification system
|
||||
output ... → Allows you to add and remove fake outputs to your
|
||||
preferred backend
|
||||
plugin ... → Issue a plugin request
|
||||
reload [config-only] → Issue a reload to force reload the config. Pass
|
||||
'config-only' to disable monitor reload
|
||||
rollinglog → Prints tail of the log. Also supports -f/--follow
|
||||
option
|
||||
setcursor <theme> <size> → Sets the cursor theme and reloads the cursor
|
||||
manager
|
||||
seterror <color> <message...> → Sets the hyprctl error string. Color has
|
||||
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
|
||||
version → Prints the hyprland version, meaning flags, commit
|
||||
and branch of build.
|
||||
workspacerules → Lists all workspace rules
|
||||
workspaces → Lists all workspaces with their properties
|
||||
|
||||
flags:
|
||||
-j → Output in JSON
|
||||
-r → Refresh state after issuing command (e.g. for
|
||||
updating variables)
|
||||
--batch → Execute a batch of commands, separated by ';'
|
||||
--instance (-i) → use a specific instance. Can be either signature or
|
||||
index in hyprctl instances (0, 1, etc)
|
||||
--quiet (-q) → Disable the output of hyprctl
|
||||
|
||||
--help:
|
||||
Can be used to print command's arguments that did not fit into this page
|
||||
(three dots))#";
|
||||
|
||||
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
|
||||
|
||||
requests:
|
||||
wallpaper → Issue a wallpaper to call a config wallpaper dynamically.
|
||||
Arguments are [mon],[path],[fit_mode]. Fit mode is optional.
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
|
||||
const std::string_view HYPRSUNSET_HELP = R"#(usage: hyprctl [flags] hyprsunset <request>
|
||||
|
||||
requests:
|
||||
temperature <temp> → Enable blue-light filter
|
||||
identity → Disable blue-light filter
|
||||
gamma <gamma> → Enable gamma filter
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
|
||||
const std::string_view NOTIFY_HELP = R"#(usage: hyprctl [flags] notify <icon> <time_ms> <color> <message...>
|
||||
|
||||
icon:
|
||||
Integer of value:
|
||||
0 → Warning
|
||||
1 → Info
|
||||
2 → Hint
|
||||
3 → Error
|
||||
4 → Confused
|
||||
5 → Ok
|
||||
6 or -1 → No icon
|
||||
|
||||
time_ms:
|
||||
Time to display notification in milliseconds
|
||||
|
||||
color:
|
||||
Notification color. Format is the same as for colors in hyprland.conf. Use
|
||||
0 for default color for icon
|
||||
|
||||
message:
|
||||
Notification message
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
|
||||
const std::string_view OUTPUT_HELP = R"#(usage: hyprctl [flags] output <create <backend> | remove <name>>
|
||||
|
||||
create <backend>:
|
||||
Creates new virtual output. Possible values for backend: wayland, x11,
|
||||
headless or auto.
|
||||
|
||||
remove <name>:
|
||||
Removes virtual output. Pass the output's name, as found in
|
||||
'hyprctl monitors'
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
|
||||
const std::string_view PLUGIN_HELP = R"#(usage: hyprctl [flags] plugin <request>
|
||||
|
||||
requests:
|
||||
load <path> → Loads a plugin. Path must be absolute
|
||||
unload <path> → Unloads a plugin. Path must be absolute
|
||||
list → Lists all loaded plugins
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
|
||||
const std::string_view SETPROP_HELP = R"#(usage: hyprctl [flags] setprop <regex> <property> <value> [lock]
|
||||
|
||||
regex:
|
||||
Regular expression by which a window will be searched
|
||||
|
||||
property:
|
||||
See https://wiki.hypr.land/Configuring/Using-hyprctl/#setprop for list
|
||||
of properties
|
||||
|
||||
value:
|
||||
Property value
|
||||
|
||||
lock:
|
||||
Optional argument. If lock is not added, will be unlocked. Locking means a
|
||||
dynamic windowrule cannot override this setting.
|
||||
|
||||
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:
|
||||
You can find the device using 'hyprctl devices' command
|
||||
|
||||
cmd:
|
||||
'next' for next, 'prev' for previous, or ID for a specific one. IDs are
|
||||
assigned based on their order in config file (keyboard_layout),
|
||||
starting from 0
|
||||
|
||||
flags:
|
||||
See 'hyprctl --help')#";
|
||||
11
hyprctl/src/helpers/Memory.hpp
Normal file
11
hyprctl/src/helpers/Memory.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <hyprutils/memory/Atomic.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
148
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
148
hyprctl/src/hyprpaper/Hyprpaper.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "Hyprpaper.hpp"
|
||||
#include "../helpers/Memory.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <format>
|
||||
#include <filesystem>
|
||||
|
||||
#include <hyprpaper_core-client.hpp>
|
||||
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
|
||||
static SP<CCHyprpaperCoreImpl> g_coreImpl;
|
||||
|
||||
constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1;
|
||||
|
||||
//
|
||||
static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) {
|
||||
if (sv == "contain")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN;
|
||||
if (sv == "fit" || sv == "stretch")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH;
|
||||
if (sv == "tile")
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE;
|
||||
return HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
|
||||
std::error_code ec;
|
||||
auto can = std::filesystem::canonical(sv, ec);
|
||||
|
||||
if (ec)
|
||||
return std::unexpected(std::format("invalid path: {}", ec.message()));
|
||||
|
||||
return can;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
|
||||
if (sv.empty())
|
||||
return std::unexpected("empty path");
|
||||
|
||||
if (sv[0] == '~') {
|
||||
static auto HOME = getenv("HOME");
|
||||
if (!HOME || HOME[0] == '\0')
|
||||
return std::unexpected("home path but no $HOME");
|
||||
|
||||
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
|
||||
}
|
||||
|
||||
return resolvePath(sv);
|
||||
}
|
||||
|
||||
std::expected<void, std::string> Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) {
|
||||
if (!rq.contains(' '))
|
||||
return std::unexpected("Invalid request");
|
||||
|
||||
if (!rq.starts_with("/hyprpaper "))
|
||||
return std::unexpected("Invalid request");
|
||||
|
||||
std::string_view LHS, RHS;
|
||||
auto spacePos = rq.find(' ', 12);
|
||||
LHS = rq.substr(11, spacePos - 11);
|
||||
RHS = rq.substr(spacePos + 1);
|
||||
|
||||
if (LHS != "wallpaper")
|
||||
return std::unexpected("Unknown hyprpaper request");
|
||||
|
||||
CVarList2 args(std::string{RHS}, 0, ',');
|
||||
|
||||
const std::string MONITOR = std::string{args[0]};
|
||||
const auto& PATH_RAW = args[1];
|
||||
const auto& FIT = args[2];
|
||||
|
||||
if (PATH_RAW.empty())
|
||||
return std::unexpected("not enough args");
|
||||
|
||||
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!RTDIR || RTDIR[0] == '\0')
|
||||
return std::unexpected("can't send: no XDG_RUNTIME_DIR");
|
||||
|
||||
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!HIS || HIS[0] == '\0')
|
||||
return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)");
|
||||
|
||||
const auto PATH = getFullPath(PATH_RAW);
|
||||
|
||||
if (!PATH)
|
||||
return std::unexpected(std::format("bad path: {}", PATH_RAW));
|
||||
|
||||
auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
|
||||
|
||||
auto socket = Hyprwire::IClientSocket::open(socketPath);
|
||||
|
||||
if (!socket)
|
||||
return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)");
|
||||
|
||||
g_coreImpl = makeShared<CCHyprpaperCoreImpl>(1);
|
||||
|
||||
socket->addImplementation(g_coreImpl);
|
||||
|
||||
if (!socket->waitForHandshake())
|
||||
return std::unexpected("can't send: wire handshake failed");
|
||||
|
||||
auto spec = socket->getSpec(g_coreImpl->protocol()->specName());
|
||||
|
||||
if (!spec)
|
||||
return std::unexpected("can't send: hyprpaper doesn't have the spec?!");
|
||||
|
||||
auto manager = makeShared<CCHyprpaperCoreManagerObject>(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED));
|
||||
|
||||
if (!manager)
|
||||
return std::unexpected("wire error: couldn't create manager");
|
||||
|
||||
auto wallpaper = makeShared<CCHyprpaperWallpaperObject>(manager->sendGetWallpaperObject());
|
||||
|
||||
if (!wallpaper)
|
||||
return std::unexpected("wire error: couldn't create wallpaper object");
|
||||
|
||||
bool canExit = false;
|
||||
std::optional<std::string> err;
|
||||
|
||||
wallpaper->setFailed([&canExit, &err](uint32_t code) {
|
||||
canExit = true;
|
||||
err = std::format("failed to set wallpaper, code {}", code);
|
||||
});
|
||||
wallpaper->setSuccess([&canExit]() { canExit = true; });
|
||||
|
||||
wallpaper->sendPath(PATH->c_str());
|
||||
wallpaper->sendMonitorName(MONITOR.c_str());
|
||||
if (!FIT.empty())
|
||||
wallpaper->sendFitMode(fitFromString(FIT));
|
||||
|
||||
wallpaper->sendApply();
|
||||
|
||||
while (!canExit) {
|
||||
socket->dispatchEvents(true);
|
||||
}
|
||||
|
||||
if (err)
|
||||
return std::unexpected(*err);
|
||||
|
||||
return {};
|
||||
}
|
||||
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
8
hyprctl/src/hyprpaper/Hyprpaper.hpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
|
||||
namespace Hyprpaper {
|
||||
std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);
|
||||
};
|
||||
539
hyprctl/src/main.cpp
Normal file
539
hyprctl/src/main.cpp
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
#include <re2/re2.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <ranges>
|
||||
#include <optional>
|
||||
#include <charconv>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <print>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <cstdarg>
|
||||
#include <hyprutils/string/String.hpp>
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
using namespace Hyprutils::String;
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#include "Strings.hpp"
|
||||
#include "hyprpaper/Hyprpaper.hpp"
|
||||
|
||||
std::string instanceSignature;
|
||||
bool quiet = false;
|
||||
|
||||
struct SInstanceData {
|
||||
std::string id;
|
||||
uint64_t time;
|
||||
uint64_t pid;
|
||||
std::string wlSocket;
|
||||
};
|
||||
|
||||
void log(const std::string_view str) {
|
||||
if (quiet)
|
||||
return;
|
||||
|
||||
std::println("{}", str);
|
||||
}
|
||||
|
||||
static int getUID() {
|
||||
const auto UID = getuid();
|
||||
const auto PWUID = getpwuid(UID);
|
||||
return PWUID ? PWUID->pw_uid : UID;
|
||||
}
|
||||
|
||||
std::string getRuntimeDir() {
|
||||
const auto XDG = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!XDG) {
|
||||
const std::string USERID = std::to_string(getUID());
|
||||
return "/run/user/" + USERID + "/hypr";
|
||||
}
|
||||
|
||||
return std::string{XDG} + "/hypr";
|
||||
}
|
||||
|
||||
static std::optional<uint64_t> toUInt64(const std::string_view str) {
|
||||
uint64_t value = 0;
|
||||
const auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), value);
|
||||
if (ec != std::errc() || ptr != str.data() + str.size())
|
||||
return std::nullopt;
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::optional<SInstanceData> parseInstance(const std::filesystem::directory_entry& entry) {
|
||||
if (!entry.is_directory())
|
||||
return std::nullopt;
|
||||
|
||||
const auto lockPath = entry.path() / "hyprland.lock";
|
||||
std::ifstream ifs(lockPath);
|
||||
if (!ifs.is_open())
|
||||
return std::nullopt;
|
||||
|
||||
SInstanceData data;
|
||||
data.id = entry.path().filename().string();
|
||||
|
||||
const auto first = std::string_view{data.id}.find_first_of('_');
|
||||
const auto last = std::string_view{data.id}.find_last_of('_');
|
||||
if (first == std::string_view::npos || last == std::string_view::npos || last <= first)
|
||||
return std::nullopt;
|
||||
|
||||
auto time = toUInt64(std::string_view{data.id}.substr(first + 1, last - first - 1));
|
||||
if (!time)
|
||||
return std::nullopt;
|
||||
data.time = *time;
|
||||
|
||||
std::string line;
|
||||
if (!std::getline(ifs, line))
|
||||
return std::nullopt;
|
||||
|
||||
auto pid = toUInt64(std::string_view{line});
|
||||
if (!pid)
|
||||
return std::nullopt;
|
||||
data.pid = *pid;
|
||||
|
||||
if (!std::getline(ifs, data.wlSocket))
|
||||
return std::nullopt;
|
||||
|
||||
if (std::getline(ifs, line) && !line.empty())
|
||||
return std::nullopt; // more lines than expected
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<SInstanceData> instances() {
|
||||
std::vector<SInstanceData> result;
|
||||
|
||||
std::error_code ec;
|
||||
const auto runtimeDir = getRuntimeDir();
|
||||
if (!std::filesystem::exists(runtimeDir, ec) || ec)
|
||||
return result;
|
||||
|
||||
std::filesystem::directory_iterator it(runtimeDir, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||
if (ec)
|
||||
return result;
|
||||
|
||||
for (const auto& el : it) {
|
||||
if (auto instance = parseInstance(el))
|
||||
result.emplace_back(std::move(*instance));
|
||||
}
|
||||
|
||||
std::erase_if(result, [](const auto& el) { return kill(el.pid, 0) != 0 && errno == ESRCH; });
|
||||
|
||||
std::ranges::sort(result, {}, &SInstanceData::time);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static volatile bool sigintReceived = false;
|
||||
void intHandler(int sig) {
|
||||
sigintReceived = true;
|
||||
std::println("[hyprctl] SIGINT received, closing connection");
|
||||
}
|
||||
|
||||
int rollingRead(const int socket) {
|
||||
sigintReceived = false;
|
||||
signal(SIGINT, intHandler);
|
||||
|
||||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
std::array<char, BUFFER_SIZE> buffer = {0};
|
||||
long sizeWritten = 0;
|
||||
std::println("[hyprctl] reading from socket following up log:");
|
||||
while (!sigintReceived) {
|
||||
sizeWritten = read(socket, buffer.data(), BUFFER_SIZE);
|
||||
if (sizeWritten < 0 && errno != EAGAIN) {
|
||||
if (errno != EINTR)
|
||||
std::println("Couldn't read (5): {}: {}", strerror(errno), errno);
|
||||
close(socket);
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (sizeWritten == 0)
|
||||
break;
|
||||
|
||||
if (sizeWritten > 0) {
|
||||
std::println("{}", std::string(buffer.data(), sizeWritten));
|
||||
buffer.fill('\0');
|
||||
}
|
||||
|
||||
usleep(100000);
|
||||
}
|
||||
close(socket);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int request(std::string_view arg, int minArgs = 0, bool needRoll = false) {
|
||||
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (SERVERSOCKET < 0) {
|
||||
log("Couldn't open a socket (1)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
|
||||
if (setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval)) < 0) {
|
||||
log("Couldn't set socket timeout (2)");
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto ARGS = std::count(arg.begin(), arg.end(), ' ');
|
||||
|
||||
if (ARGS < minArgs) {
|
||||
log(std::format("Not enough arguments in '{}', expected at least {}", arg, minArgs));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (instanceSignature.empty()) {
|
||||
log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?) (3)");
|
||||
return 3;
|
||||
}
|
||||
|
||||
sockaddr_un serverAddress = {0};
|
||||
serverAddress.sun_family = AF_UNIX;
|
||||
|
||||
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/.socket.sock";
|
||||
|
||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||
|
||||
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||
log("Couldn't connect to " + socketPath + ". (4)");
|
||||
return 4;
|
||||
}
|
||||
|
||||
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
log("Couldn't write (5)");
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (needRoll)
|
||||
return rollingRead(SERVERSOCKET);
|
||||
|
||||
std::string reply = "";
|
||||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
log("Hyprland IPC didn't respond in time\n");
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
|
||||
while (sizeWritten == BUFFER_SIZE) {
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
if (sizeWritten < 0) {
|
||||
log("Couldn't read (6)");
|
||||
return 6;
|
||||
}
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
close(SERVERSOCKET);
|
||||
|
||||
log(reply);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int requestIPC(std::string_view filename, std::string_view arg) {
|
||||
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (SERVERSOCKET < 0) {
|
||||
log("Couldn't open a socket (1)");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (instanceSignature.empty()) {
|
||||
log("HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
|
||||
return 2;
|
||||
}
|
||||
|
||||
sockaddr_un serverAddress = {0};
|
||||
serverAddress.sun_family = AF_UNIX;
|
||||
|
||||
std::string socketPath = getRuntimeDir() + "/" + instanceSignature + "/" + filename;
|
||||
|
||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||
|
||||
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0) {
|
||||
log("Couldn't connect to " + socketPath + ". (3)");
|
||||
return 3;
|
||||
}
|
||||
|
||||
arg = arg.substr(arg.find_first_of('/') + 1); // strip flags
|
||||
arg = arg.substr(arg.find_first_of(' ') + 1); // strip "hyprpaper"
|
||||
|
||||
auto sizeWritten = write(SERVERSOCKET, arg.data(), arg.size());
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
log("Couldn't write (4)");
|
||||
return 4;
|
||||
}
|
||||
constexpr size_t BUFFER_SIZE = 8192;
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
log("Couldn't read (5)");
|
||||
return 5;
|
||||
}
|
||||
|
||||
close(SERVERSOCKET);
|
||||
|
||||
log(std::string(buffer));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int requestHyprsunset(std::string_view arg) {
|
||||
return requestIPC(".hyprsunset.sock", arg);
|
||||
}
|
||||
|
||||
void batchRequest(std::string_view arg, bool json) {
|
||||
std::string commands(arg.substr(arg.find_first_of(' ') + 1));
|
||||
|
||||
if (json) {
|
||||
RE2::GlobalReplace(&commands, ";\\s*", ";j/");
|
||||
commands.insert(0, "j/");
|
||||
}
|
||||
|
||||
std::string rq = "[[BATCH]]" + commands;
|
||||
request(rq);
|
||||
}
|
||||
|
||||
void instancesRequest(bool json) {
|
||||
std::string result = "";
|
||||
|
||||
// gather instance data
|
||||
std::vector<SInstanceData> inst = instances();
|
||||
|
||||
if (!json) {
|
||||
for (auto const& el : inst) {
|
||||
result += std::format("instance {}:\n\ttime: {}\n\tpid: {}\n\twl socket: {}\n\n", el.id, el.time, el.pid, el.wlSocket);
|
||||
}
|
||||
} else {
|
||||
result += '[';
|
||||
for (auto const& el : inst) {
|
||||
result += std::format(R"#(
|
||||
{{
|
||||
"instance": "{}",
|
||||
"time": {},
|
||||
"pid": {},
|
||||
"wl_socket": "{}"
|
||||
}},)#",
|
||||
el.id, el.time, el.pid, el.wlSocket);
|
||||
}
|
||||
|
||||
result.pop_back();
|
||||
result += "\n]";
|
||||
}
|
||||
|
||||
log(result + "\n");
|
||||
}
|
||||
|
||||
std::vector<std::string> splitArgs(int argc, char** argv) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (auto i = 1 /* skip the executable */; i < argc; ++i)
|
||||
result.emplace_back(argv[i]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool parseArgs = true;
|
||||
|
||||
if (argc < 2) {
|
||||
std::println("{}", USAGE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string fullRequest = "";
|
||||
std::string fullArgs = "";
|
||||
const auto ARGS = splitArgs(argc, argv);
|
||||
bool json = false;
|
||||
bool needRoll = false;
|
||||
std::string overrideInstance = "";
|
||||
|
||||
for (std::size_t i = 0; i < ARGS.size(); ++i) {
|
||||
if (ARGS[i] == "--") {
|
||||
// Stop parsing arguments after --
|
||||
parseArgs = false;
|
||||
continue;
|
||||
}
|
||||
if (parseArgs && (ARGS[i][0] == '-') && !(isNumber(ARGS[i], true) || isNumber(ARGS[i].substr(0, ARGS[i].length() - 1), true)) /* For stuff like -2 or -2, */) {
|
||||
// parse
|
||||
if (ARGS[i] == "-j" && !fullArgs.contains("j")) {
|
||||
fullArgs += "j";
|
||||
json = true;
|
||||
} else if (ARGS[i] == "-r" && !fullArgs.contains("r")) {
|
||||
fullArgs += "r";
|
||||
} else if (ARGS[i] == "-a" && !fullArgs.contains("a")) {
|
||||
fullArgs += "a";
|
||||
} else if ((ARGS[i] == "-c" || ARGS[i] == "--config") && !fullArgs.contains("c")) {
|
||||
fullArgs += "c";
|
||||
} else if ((ARGS[i] == "-f" || ARGS[i] == "--follow") && !fullArgs.contains("f")) {
|
||||
fullArgs += "f";
|
||||
needRoll = true;
|
||||
} else if (ARGS[i] == "--batch") {
|
||||
fullRequest = "--batch ";
|
||||
} else if (ARGS[i] == "--instance" || ARGS[i] == "-i") {
|
||||
++i;
|
||||
|
||||
if (i >= ARGS.size()) {
|
||||
std::println("{}", USAGE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
overrideInstance = ARGS[i];
|
||||
} else if (ARGS[i] == "-q" || ARGS[i] == "--quiet") {
|
||||
quiet = true;
|
||||
} else if (ARGS[i] == "--help") {
|
||||
const std::string& cmd = ARGS[0];
|
||||
|
||||
if (cmd == "hyprpaper") {
|
||||
std::println("{}", HYPRPAPER_HELP);
|
||||
} else if (cmd == "hyprsunset") {
|
||||
std::println("{}", HYPRSUNSET_HELP);
|
||||
} else if (cmd == "notify") {
|
||||
std::println("{}", NOTIFY_HELP);
|
||||
} else if (cmd == "output") {
|
||||
std::println("{}", OUTPUT_HELP);
|
||||
} else if (cmd == "plugin") {
|
||||
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 {
|
||||
std::println("{}", USAGE);
|
||||
}
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
std::println("{}", USAGE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
fullRequest += ARGS[i] + " ";
|
||||
}
|
||||
|
||||
if (fullRequest.empty()) {
|
||||
std::println("{}", USAGE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fullRequest.pop_back(); // remove trailing space
|
||||
|
||||
fullRequest = fullArgs + "/" + fullRequest;
|
||||
|
||||
// instances is HIS-independent
|
||||
if (fullRequest.contains("/instances")) {
|
||||
instancesRequest(json);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (needRoll && !fullRequest.contains("/rollinglog")) {
|
||||
log("only 'rollinglog' command supports '--follow' option");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (overrideInstance.contains("_"))
|
||||
instanceSignature = overrideInstance;
|
||||
else if (!overrideInstance.empty()) {
|
||||
if (!isNumber(overrideInstance, false)) {
|
||||
log("instance invalid\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const auto INSTANCENO = std::stoi(overrideInstance);
|
||||
|
||||
const auto INSTANCES = instances();
|
||||
|
||||
if (INSTANCENO < 0 || sc<std::size_t>(INSTANCENO) >= INSTANCES.size()) {
|
||||
log("no such instance\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
instanceSignature = INSTANCES[INSTANCENO].id;
|
||||
} else {
|
||||
const auto ISIG = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!ISIG) {
|
||||
log("HYPRLAND_INSTANCE_SIGNATURE not set! (is hyprland running?)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
instanceSignature = ISIG;
|
||||
}
|
||||
|
||||
int exitStatus = 0;
|
||||
|
||||
if (fullRequest.contains("/--batch"))
|
||||
batchRequest(fullRequest, json);
|
||||
else if (fullRequest.contains("/hyprpaper")) {
|
||||
auto result = Hyprpaper::makeHyprpaperRequest(fullRequest);
|
||||
if (!result)
|
||||
log(std::format("error: {}", result.error()));
|
||||
exitStatus = !result;
|
||||
} else if (fullRequest.contains("/hyprsunset"))
|
||||
exitStatus = requestHyprsunset(fullRequest);
|
||||
else if (fullRequest.contains("/switchxkblayout"))
|
||||
exitStatus = request(fullRequest, 2);
|
||||
else if (fullRequest.contains("/seterror"))
|
||||
exitStatus = request(fullRequest, 1);
|
||||
else if (fullRequest.contains("/setprop"))
|
||||
exitStatus = request(fullRequest, 3);
|
||||
else if (fullRequest.contains("/plugin"))
|
||||
exitStatus = request(fullRequest, 1);
|
||||
else if (fullRequest.contains("/dismissnotify"))
|
||||
exitStatus = request(fullRequest, 0);
|
||||
else if (fullRequest.contains("/notify"))
|
||||
exitStatus = request(fullRequest, 2);
|
||||
else if (fullRequest.contains("/output"))
|
||||
exitStatus = request(fullRequest, 2);
|
||||
else if (fullRequest.contains("/setcursor"))
|
||||
exitStatus = request(fullRequest, 1);
|
||||
else if (fullRequest.contains("/dispatch"))
|
||||
exitStatus = request(fullRequest, 1);
|
||||
else if (fullRequest.contains("/keyword"))
|
||||
exitStatus = request(fullRequest, 2);
|
||||
else if (fullRequest.contains("/decorations"))
|
||||
exitStatus = request(fullRequest, 1);
|
||||
else if (fullRequest.contains("/--help"))
|
||||
std::println("{}", USAGE);
|
||||
else if (fullRequest.contains("/rollinglog") && needRoll)
|
||||
exitStatus = request(fullRequest, 0, true);
|
||||
else {
|
||||
exitStatus = request(fullRequest);
|
||||
}
|
||||
|
||||
std::cout << std::flush;
|
||||
return exitStatus;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue