summaryrefslogtreecommitdiff
path: root/hyprctl
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2025-12-04 17:59:47 +0000
committerGitHub <noreply@github.com>2025-12-04 17:59:47 +0000
commitd9657a95cb6706860332ff47dad444a96bcc874a (patch)
tree948f071656f942f9681189c7febab371b094e5f5 /hyprctl
parent9b1891e4765e2c5b84c8c61725e3973ca9940e05 (diff)
hyprctl: use new hyprpaper ipc format (#12537)
--------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
Diffstat (limited to 'hyprctl')
-rw-r--r--hyprctl/CMakeLists.txt25
-rw-r--r--hyprctl/hw-protocols/hyprpaper_core.xml144
-rw-r--r--hyprctl/src/Strings.hpp (renamed from hyprctl/Strings.hpp)7
-rw-r--r--hyprctl/src/helpers/Memory.hpp11
-rw-r--r--hyprctl/src/hyprpaper/Hyprpaper.cpp148
-rw-r--r--hyprctl/src/hyprpaper/Hyprpaper.hpp8
-rw-r--r--hyprctl/src/main.cpp (renamed from hyprctl/main.cpp)14
7 files changed, 343 insertions, 14 deletions
diff --git a/hyprctl/CMakeLists.txt b/hyprctl/CMakeLists.txt
index db5ef615..7071ede9 100644
--- a/hyprctl/CMakeLists.txt
+++ b/hyprctl/CMakeLists.txt
@@ -5,11 +5,32 @@ project(
DESCRIPTION "Control utility for Hyprland"
)
-pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 re2)
+pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.4 hyprwire re2)
-add_executable(hyprctl "main.cpp")
+file(GLOB_RECURSE HYPRCTL_SRCFILES CONFIGURE_DEPENDS "src/*.cpp" "hw-protocols/*.cpp" "include/*.hpp")
+
+add_executable(hyprctl ${HYPRCTL_SRCFILES})
target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps)
+target_include_directories(hyprctl PRIVATE "hw-protocols")
+
+# Hyprwire
+
+function(hyprprotocol protoPath protoName)
+ set(path ${CMAKE_CURRENT_SOURCE_DIR}/${protoPath})
+ add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-client.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/${protoName}-spec.hpp
+ COMMAND hyprwire-scanner --client ${path}/${protoName}.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/hw-protocols/
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
+ target_sources(hyprctl PRIVATE hw-protocols/${protoName}-client.cpp
+ hw-protocols/${protoName}-client.hpp
+ hw-protocols/${protoName}-spec.hpp)
+endfunction()
+
+hyprprotocol(hw-protocols hyprpaper_core)
# binary
install(TARGETS hyprctl)
diff --git a/hyprctl/hw-protocols/hyprpaper_core.xml b/hyprctl/hw-protocols/hyprpaper_core.xml
new file mode 100644
index 00000000..fa2edc0a
--- /dev/null
+++ b/hyprctl/hw-protocols/hyprpaper_core.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="hyprpaper_core" version="1">
+ <copyright>
+ BSD 3-Clause License
+
+ Copyright (c) 2025, Hypr Development
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ 3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ </copyright>
+
+ <object name="hyprpaper_core_manager" version="1">
+ <description summary="manager object">
+ This is the core manager object for hyprpaper operations
+ </description>
+
+ <c2s name="get_wallpaper_object">
+ <description summary="Get a wallpaper object">
+ Creates a wallpaper object
+ </description>
+ <returns iface="hyprpaper_wallpaper"/>
+ </c2s>
+
+ <s2c name="add_monitor">
+ <description summary="New monitor added">
+ Emitted when a new monitor is added.
+ </description>
+ <arg name="monitor_name" type="varchar" summary="the monitor's name"/>
+ </s2c>
+
+ <s2c name="remove_monitor">
+ <description summary="A monitor was removed">
+ Emitted when a monitor is removed.
+ </description>
+ <arg name="monitor_name" type="varchar" summary="the monitor's name"/>
+ </s2c>
+
+ <c2s name="destroy" destructor="true">
+ <description summary="Destroy this object">
+ Destroys this object. Children remain alive until destroyed.
+ </description>
+ </c2s>
+ </object>
+
+ <enum name="wallpaper_fit_mode">
+ <value idx="0" name="stretch"/>
+ <value idx="1" name="cover"/>
+ <value idx="2" name="contain"/>
+ <value idx="3" name="tile"/>
+ </enum>
+
+ <enum name="wallpaper_errors">
+ <value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
+ </enum>
+
+ <enum name="applying_error">
+ <value idx="0" name="invalid_path" description="path provided was invalid"/>
+ <value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
+ <value idx="2" name="unknown_error" description="unknown error"/>
+ </enum>
+
+ <object name="hyprpaper_wallpaper" version="1">
+ <description summary="wallpaper object">
+ This is an object describing a wallpaper
+ </description>
+
+ <c2s name="path">
+ <description summary="Set a path">
+ Set a file path for the wallpaper. This has to be an absolute path from the fs root.
+ This is required.
+ </description>
+ <arg name="wallpaper" type="varchar" summary="path"/>
+ </c2s>
+
+ <c2s name="fit_mode">
+ <description summary="Set a fit mode">
+ Set a fit mode for the wallpaper. This is set to cover by default.
+ </description>
+ <arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
+ </c2s>
+
+ <c2s name="monitor_name">
+ <description summary="Set the monitor name">
+ Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
+ treat this as a wildcard fallback.
+
+ See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
+ for tracking monitor names.
+ </description>
+ <arg name="monitor_name" type="varchar" summary="monitor name"/>
+ </c2s>
+
+ <c2s name="apply">
+ <description summary="Apply this wallpaper">
+ Applies this object's state to the wallpaper state. Will emit .success on success,
+ and .failed on failure.
+
+ This object becomes inert after .succeess or .failed, the only valid operation
+ is to destroy it afterwards.
+ </description>
+ </c2s>
+
+ <s2c name="success">
+ <description summary="Operation succeeded">
+ Wallpaper was applied successfully.
+ </description>
+ </s2c>
+
+ <s2c name="failed">
+ <description summary="Operation failed">
+ Wallpaper was not applied. See the error field for more information.
+ </description>
+ <arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
+ </s2c>
+
+ <c2s name="destroy" destructor="true">
+ <description summary="Destroy this object">
+ Destroys this object.
+ </description>
+ </c2s>
+ </object>
+</protocol>
diff --git a/hyprctl/Strings.hpp b/hyprctl/src/Strings.hpp
index 67e4f992..549d84bb 100644
--- a/hyprctl/Strings.hpp
+++ b/hyprctl/src/Strings.hpp
@@ -74,11 +74,8 @@ flags:
const std::string_view HYPRPAPER_HELP = R"#(usage: hyprctl [flags] hyprpaper <request>
requests:
- listactive → Lists all active images
- listloaded → Lists all loaded images
- preload <path> → Preloads image
- unload <path> → Unloads image. Pass 'all' as path to unload all images
- wallpaper → Issue a wallpaper to call a config wallpaper dynamically
+ wallpaper → Issue a wallpaper to call a config wallpaper dynamically.
+ Arguments are [mon],[path],[fit_mode]. Fit mode is optional.
flags:
See 'hyprctl --help')#";
diff --git a/hyprctl/src/helpers/Memory.hpp b/hyprctl/src/helpers/Memory.hpp
new file mode 100644
index 00000000..1d3a9e07
--- /dev/null
+++ b/hyprctl/src/helpers/Memory.hpp
@@ -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
diff --git a/hyprctl/src/hyprpaper/Hyprpaper.cpp b/hyprctl/src/hyprpaper/Hyprpaper.cpp
new file mode 100644
index 00000000..afa7f653
--- /dev/null
+++ b/hyprctl/src/hyprpaper/Hyprpaper.cpp
@@ -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 {};
+} \ No newline at end of file
diff --git a/hyprctl/src/hyprpaper/Hyprpaper.hpp b/hyprctl/src/hyprpaper/Hyprpaper.hpp
new file mode 100644
index 00000000..167b0a8d
--- /dev/null
+++ b/hyprctl/src/hyprpaper/Hyprpaper.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+#include <expected>
+#include <string>
+
+namespace Hyprpaper {
+ std::expected<void, std::string> makeHyprpaperRequest(const std::string_view& rq);
+}; \ No newline at end of file
diff --git a/hyprctl/main.cpp b/hyprctl/src/main.cpp
index e15a17f5..7146c635 100644
--- a/hyprctl/main.cpp
+++ b/hyprctl/src/main.cpp
@@ -31,6 +31,7 @@ using namespace Hyprutils::String;
using namespace Hyprutils::Memory;
#include "Strings.hpp"
+#include "hyprpaper/Hyprpaper.hpp"
std::string instanceSignature;
bool quiet = false;
@@ -305,10 +306,6 @@ int requestIPC(std::string_view filename, std::string_view arg) {
return 0;
}
-int requestHyprpaper(std::string_view arg) {
- return requestIPC(".hyprpaper.sock", arg);
-}
-
int requestHyprsunset(std::string_view arg) {
return requestIPC(".hyprsunset.sock", arg);
}
@@ -500,9 +497,12 @@ int main(int argc, char** argv) {
if (fullRequest.contains("/--batch"))
batchRequest(fullRequest, json);
- else if (fullRequest.contains("/hyprpaper"))
- exitStatus = requestHyprpaper(fullRequest);
- else if (fullRequest.contains("/hyprsunset"))
+ 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);