diff --git a/.gitignore b/.gitignore index 8d38ed4..e4c8953 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ result protocols/*.hpp protocols/*.cpp +hw-protocols/*.hpp +hw-protocols/*.cpp + .cache/ hyprctl/hyprctl diff --git a/CMakeLists.txt b/CMakeLists.txt index 955d7f9..45a5b70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,7 +41,7 @@ execute_process( OUTPUT_VARIABLE GIT_DIRTY OUTPUT_STRIP_TRAILING_WHITESPACE) -include_directories(.) +include_directories(. hw-protocols protocols) set(CMAKE_CXX_STANDARD 23) add_compile_options(-DWLR_USE_UNSTABLE) add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value @@ -59,6 +59,7 @@ pkg_check_modules( hyprutils>=0.2.4 wayland-client hyprtoolkit>=0.4.0 + hyprwire pixman-1 libdrm) @@ -66,6 +67,8 @@ file(GLOB_RECURSE SRCFILES "src/*.cpp") add_executable(hyprpaper ${SRCFILES}) +# Wayland + pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) @@ -107,6 +110,25 @@ protocolnew("stable/xdg-shell" "xdg-shell" false) protocolnew("staging/cursor-shape" "cursor-shape-v1" false) protocolnew("stable/tablet" "tablet-v2" false) +# Hyprwire + +function(hyprprotocolServer protoPath protoName) + set(path ${CMAKE_SOURCE_DIR}/${protoPath}) + add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.cpp + ${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.hpp + COMMAND hyprwire-scanner ${path}/${protoName}.xml + ${CMAKE_SOURCE_DIR}/hw-protocols/ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + target_sources(hyprpaper PRIVATE hw-protocols/${protoName}-server.cpp + hw-protocols/${protoName}-server.hpp + ) +endfunction() + +hyprprotocolServer(hw-protocols hyprpaper_core) + +# + string(REPLACE "\"" " " GIT_COMMIT_MESSAGE_ESCAPED "${GIT_COMMIT_MESSAGE}") target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") diff --git a/hw-protocols/hyprpaper_core.xml b/hw-protocols/hyprpaper_core.xml new file mode 100644 index 0000000..fa2edc0 --- /dev/null +++ b/hw-protocols/hyprpaper_core.xml @@ -0,0 +1,144 @@ + + + + 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. + + + + + This is the core manager object for hyprpaper operations + + + + + Creates a wallpaper object + + + + + + + Emitted when a new monitor is added. + + + + + + + Emitted when a monitor is removed. + + + + + + + Destroys this object. Children remain alive until destroyed. + + + + + + + + + + + + + + + + + + + + + + + + This is an object describing a wallpaper + + + + + Set a file path for the wallpaper. This has to be an absolute path from the fs root. + This is required. + + + + + + + Set a fit mode for the wallpaper. This is set to cover by default. + + + + + + + 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. + + + + + + + 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. + + + + + + Wallpaper was applied successfully. + + + + + + Wallpaper was not applied. See the error field for more information. + + + + + + + Destroys this object. + + + + diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 36c2e07..ef37ef9 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -94,7 +94,7 @@ std::vector CConfigManager::getSettings() { continue; } - result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value(), .id = ++m_maxId}); + result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value()}); } return result; diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 237a9a2..65fca52 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -29,7 +29,6 @@ class CConfigManager { Hyprlang::CConfig m_config; std::string m_currentConfigPath; - uint32_t m_maxId = 0; }; inline UP g_config; diff --git a/src/config/WallpaperMatcher.cpp b/src/config/WallpaperMatcher.cpp index e93c4ec..3e60003 100644 --- a/src/config/WallpaperMatcher.cpp +++ b/src/config/WallpaperMatcher.cpp @@ -3,12 +3,18 @@ #include void CWallpaperMatcher::addState(CConfigManager::SSetting&& s) { + s.id = ++m_maxId; + std::erase_if(m_settings, [&s](const auto& e) { return e.monitor == s.monitor; }); m_settings.emplace_back(std::move(s)); recalcStates(); } void CWallpaperMatcher::addStates(std::vector&& s) { + for (auto& ss : s) { + ss.id = ++m_maxId; + } + std::erase_if(m_settings, [&s](const auto& e) { return std::ranges::any_of(s, [&e](const auto& el) { return el.monitor == e.monitor; }); }); m_settings.append_range(std::move(s)); recalcStates(); @@ -25,6 +31,10 @@ void CWallpaperMatcher::unregisterOutput(const std::string_view& s) { recalcStates(); } +bool CWallpaperMatcher::outputExists(const std::string_view& s) { + return std::ranges::contains(m_monitorNames, s); +} + std::optional> CWallpaperMatcher::getSetting(const std::string_view& monName) { for (const auto& m : m_monitorStates) { if (m.name != monName) diff --git a/src/config/WallpaperMatcher.hpp b/src/config/WallpaperMatcher.hpp index 51c6bf5..b889c24 100644 --- a/src/config/WallpaperMatcher.hpp +++ b/src/config/WallpaperMatcher.hpp @@ -23,6 +23,7 @@ class CWallpaperMatcher { void registerOutput(const std::string_view&); void unregisterOutput(const std::string_view&); + bool outputExists(const std::string_view&); std::optional> getSetting(const std::string_view& monName); @@ -44,6 +45,8 @@ class CWallpaperMatcher { std::vector m_monitorNames; std::vector m_monitorStates; + uint32_t m_maxId = 0; + SMonitorState& getState(const std::string_view& monName); }; diff --git a/src/ipc/IPC.cpp b/src/ipc/IPC.cpp new file mode 100644 index 0000000..43d020f --- /dev/null +++ b/src/ipc/IPC.cpp @@ -0,0 +1,135 @@ +#include "IPC.hpp" +#include "../helpers/Logger.hpp" +#include "../config/WallpaperMatcher.hpp" +#include "../ui/UI.hpp" + +#include + +using namespace IPC; +using namespace std::string_literals; + +constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; + +static SP g_coreImpl; + +CWallpaperObject::CWallpaperObject(SP&& obj) : m_object(std::move(obj)) { + m_object->setDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); }); + m_object->setOnDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); }); + + m_object->setPath([this](const char* s) { + if (m_inert) + m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert"); + + m_path = s; + }); + + m_object->setFitMode([this](hyprpaperCoreWallpaperFitMode f) { + if (m_inert) + m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert"); + + if (f > HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE) + m_object->error(HYPRPAPER_CORE_APPLYING_ERROR_UNKNOWN_ERROR, "Invalid fit mode"); + m_fitMode = f; + }); + + m_object->setMonitorName([this](const char* s) { + if (m_inert) + m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert"); + + m_monitor = s; + }); + + m_object->setApply([this]() { + if (m_inert) + m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert"); + + apply(); + }); +} + +static std::string fitModeToStr(hyprpaperCoreWallpaperFitMode m) { + switch (m) { + case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN: return "contain"; + case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER: return "cover"; + case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE: return "tile"; + case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH: return "fit"; + default: return "cover"; + } +} + +void CWallpaperObject::apply() { + + m_inert = true; + + if (!m_monitor.empty() && !g_matcher->outputExists(m_monitor)) { + m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR); + return; + } + + if (m_path.empty()) { + m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH); + return; + } + + if (m_path[0] != '/') { + m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH); + return; + } + + std::error_code ec; + if (!std::filesystem::exists(m_path, ec) || ec) { + m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH); + return; + } + + g_matcher->addState(CConfigManager::SSetting{ + .monitor = std::move(m_monitor), + .fitMode = fitModeToStr(m_fitMode), + .path = std::move(m_path), + }); + + m_object->sendSuccess(); +} + +CSocket::CSocket() { + const auto RTDIR = getenv("XDG_RUNTIME_DIR"); + + if (!RTDIR) + return; + + const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + if (!HIS) { + g_logger->log(LOG_WARN, "not running under hyprland, IPC will be disabled."); + return; + } + + m_socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME; + + std::error_code ec; + std::filesystem::remove(m_socketPath, ec); + + m_socket = Hyprwire::IServerSocket::open(m_socketPath); + + if (!m_socket) + return; + + g_coreImpl = makeShared(1, [this](SP obj) { + auto manager = m_managers.emplace_back(makeShared(std::move(obj))); + + manager->setDestroy([this, weak = WP{manager}] { std::erase(m_managers, weak); }); + manager->setOnDestroy([this, weak = WP{manager}] { std::erase(m_managers, weak); }); + + manager->setGetWallpaperObject([this, weak = WP{manager}](uint32_t id) { + if (!weak) + return; + + m_wallpaperObjects.emplace_back(makeShared( + makeShared(m_socket->createObject(weak->getObject()->client(), weak->getObject(), "hyprpaper_wallpaper", id)))); + }); + }); + + m_socket->addImplementation(g_coreImpl); + + g_ui->backend()->addFd(m_socket->extractLoopFD(), [this]() { m_socket->dispatchEvents(); }); +} \ No newline at end of file diff --git a/src/ipc/IPC.hpp b/src/ipc/IPC.hpp new file mode 100644 index 0000000..d11e287 --- /dev/null +++ b/src/ipc/IPC.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "../helpers/Memory.hpp" + +#include +#include + +namespace IPC { + class CWallpaperObject { + public: + CWallpaperObject(SP&& obj); + ~CWallpaperObject() = default; + + private: + void apply(); + + SP m_object; + + std::string m_path; + hyprpaperCoreWallpaperFitMode m_fitMode = HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER; + std::string m_monitor; + + bool m_inert = false; + }; + + class CSocket { + public: + CSocket(); + ~CSocket() = default; + + private: + SP m_socket; + + std::string m_socketPath = ""; + + std::vector> m_managers; + std::vector> m_wallpaperObjects; + + friend class CWallpaperObject; + }; + + inline UP g_IPCSocket; +}; \ No newline at end of file diff --git a/src/ipc/Socket.cpp b/src/ipc/Socket.cpp deleted file mode 100644 index c6650c5..0000000 --- a/src/ipc/Socket.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "Socket.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -void CIPCSocket::initialize() { - // std::thread([&]() { - // const auto SOCKET = socket(AF_UNIX, SOCK_STREAM, 0); - - // if (SOCKET < 0) { - // Debug::log(ERR, "Couldn't start the hyprpaper Socket. (1) IPC will not work."); - // return; - // } - - // sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX}; - - // const auto HISenv = getenv("HYPRLAND_INSTANCE_SIGNATURE"); - // const auto RUNTIMEdir = getenv("XDG_RUNTIME_DIR"); - // const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid); - - // const auto USERDIR = RUNTIMEdir ? RUNTIMEdir + std::string{"/hypr/"} : "/run/user/" + USERID + "/hypr/"; - - // std::string socketPath = HISenv ? USERDIR + std::string(HISenv) + "/.hyprpaper.sock" : USERDIR + ".hyprpaper.sock"; - - // if (!HISenv) - // mkdir(USERDIR.c_str(), S_IRWXU); - - // unlink(socketPath.c_str()); - - // strcpy(SERVERADDRESS.sun_path, socketPath.c_str()); - - // bind(SOCKET, (sockaddr*)&SERVERADDRESS, SUN_LEN(&SERVERADDRESS)); - - // // 10 max queued. - // listen(SOCKET, 10); - - // sockaddr_in clientAddress = {}; - // socklen_t clientSize = sizeof(clientAddress); - - // char readBuffer[1024] = {0}; - - // Debug::log(LOG, "hyprpaper socket started at {} (fd: {})", socketPath, SOCKET); - // while (1) { - // const auto ACCEPTEDCONNECTION = accept(SOCKET, (sockaddr*)&clientAddress, &clientSize); - // if (ACCEPTEDCONNECTION < 0) { - // Debug::log(ERR, "Couldn't listen on the hyprpaper Socket. (3) IPC will not work."); - // break; - // } else { - // do { - // Debug::log(LOG, "Accepted incoming socket connection request on fd {}", ACCEPTEDCONNECTION); - // std::lock_guard lg(g_pHyprpaper->m_mtTickMutex); - - // auto messageSize = read(ACCEPTEDCONNECTION, readBuffer, 1024); - // readBuffer[messageSize == 1024 ? 1023 : messageSize] = '\0'; - // if (messageSize == 0) - // break; - // std::string request(readBuffer); - - // m_szRequest = request; - // m_bRequestReady = true; - - // g_pHyprpaper->tick(true); - // while (!m_bReplyReady) { // wait for Hyprpaper to finish processing the request - // std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // } - // write(ACCEPTEDCONNECTION, m_szReply.c_str(), m_szReply.length()); - // m_bReplyReady = false; - // m_szReply = ""; - - // } while (1); - // Debug::log(LOG, "Closing Accepted Connection"); - // close(ACCEPTEDCONNECTION); - // } - // } - - // close(SOCKET); - // }).detach(); -} - -bool CIPCSocket::mainThreadParseRequest() { - - // if (!m_bRequestReady) - // return false; - - // std::string copy = m_szRequest; - - // if (copy == "") - // return false; - - // // now we can work on the copy - - // Debug::log(LOG, "Received a request: {}", copy); - - // // set default reply - // m_szReply = "ok"; - // m_bReplyReady = true; - // m_bRequestReady = false; - - // // config commands - // if (copy.find("wallpaper") == 0) { - - // const auto RESULT = g_pConfigManager->config->parseDynamic(copy.substr(0, copy.find_first_of(' ')).c_str(), copy.substr(copy.find_first_of(' ') + 1).c_str()); - - // if (RESULT.error) { - // m_szReply = RESULT.getError(); - // return false; - // } - - // return true; - // } - - // m_szReply = "invalid command"; - // return false; -} diff --git a/src/ipc/Socket.hpp b/src/ipc/Socket.hpp deleted file mode 100644 index a071efc..0000000 --- a/src/ipc/Socket.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "../helpers/Memory.hpp" -#include -#include - -class CIPCSocket { - public: - void initialize(); - - bool mainThreadParseRequest(); - - private: - std::mutex m_mtRequestMutex; - std::string m_szRequest = ""; - std::string m_szReply = ""; - - bool m_bRequestReady = false; - bool m_bReplyReady = false; -}; - -inline UP g_pIPCSocket; \ No newline at end of file diff --git a/src/ui/UI.cpp b/src/ui/UI.cpp index 7aea9b6..b9f09a6 100644 --- a/src/ui/UI.cpp +++ b/src/ui/UI.cpp @@ -1,6 +1,7 @@ #include "UI.hpp" #include "../helpers/Logger.hpp" #include "../ipc/HyprlandSocket.hpp" +#include "../ipc/IPC.hpp" #include "../config/WallpaperMatcher.hpp" #include @@ -80,6 +81,8 @@ bool CUI::run() { if (!m_backend) return false; + IPC::g_IPCSocket = makeUnique(); + const auto MONITORS = m_backend->getOutputs(); for (const auto& m : MONITORS) {