#include "Hyprpaper.hpp" #include "../helpers/Memory.hpp" #include #include #include #include #include #include using namespace Hyprutils::String; using namespace std::string_literals; constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; static SP g_coreImpl; constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2; // 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 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 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); } static std::expected doWallpaper(const std::string_view& RHS) { 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(PROTOCOL_VERSION_SUPPORTED); 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(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); if (!manager) return std::unexpected("wire error: couldn't create manager"); auto wallpaper = makeShared(manager->sendGetWallpaperObject()); if (!wallpaper) return std::unexpected("wire error: couldn't create wallpaper object"); bool canExit = false; std::optional err; wallpaper->setFailed([&canExit, &err](uint32_t code) { canExit = true; switch (code) { case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break; case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break; default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break; } }); 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 {}; } static std::expected doListActive() { 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)"); 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(PROTOCOL_VERSION_SUPPORTED); 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?!"); if (spec->specVer() < 2) return std::unexpected("can't send: hyprpaper protocol version too low (hyprpaper too old)"); auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); if (!manager) return std::unexpected("wire error: couldn't create manager"); auto status = makeShared(manager->sendGetStatusObject()); status->setActiveWallpaper([](const char* mon, const char* wp) { std::println("{}: {}", mon, wp); }); socket->roundtrip(); return {}; } std::expected 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 doWallpaper(RHS); else if (LHS == "listactive") return doListActive(); else return std::unexpected("invalid hyprpaper request"); return {}; }