From ff2d0afd9becd24f6d18cb4a576b0be0e043048f Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Sat, 7 Feb 2026 15:31:23 +0100 Subject: [PATCH] use hyprland command socket instead of spawning a hyprctl process --- include/helpers/hypr.hpp | 150 +++++++++++++----- .../widgets/notification/baseNotification.hpp | 36 ++++- src/services/hyprland.cpp | 2 +- src/services/notificationController.cpp | 12 +- src/widgets/notification/baseNotification.cpp | 10 +- src/widgets/notification/copyNotification.cpp | 7 +- .../notification/notificationWindow.cpp | 2 +- .../notification/spotifyNotification.cpp | 4 +- 8 files changed, 153 insertions(+), 70 deletions(-) diff --git a/include/helpers/hypr.hpp b/include/helpers/hypr.hpp index 4069ffc..3a9ab3b 100644 --- a/include/helpers/hypr.hpp +++ b/include/helpers/hypr.hpp @@ -1,52 +1,15 @@ #pragma once +#include +#include #include +#include +#include #include #include #include - -#include "helpers/command.hpp" - -class HyprctlHelper { - public: - static nlohmann::json getMonitorData() { - std::string result = CommandHelper::exec("hyprctl -j monitors"); - assert(!result.empty() && "Failed to get monitor data from hyprctl"); - - auto json = nlohmann::json::parse(result); - - assert(json.is_array()); - - return json; - } - - static nlohmann::json getWorkspaceData() { - std::string result = CommandHelper::exec("hyprctl -j workspaces"); - assert(!result.empty() && "Failed to get workspace data from hyprctl"); - - auto json = nlohmann::json::parse(result); - - assert(json.is_array()); - - return json; - } - - static nlohmann::json getClientData() { - std::string result = CommandHelper::exec("hyprctl -j clients"); - assert(!result.empty() && "Failed to get client data from hyprctl"); - - auto json = nlohmann::json::parse(result); - - assert(json.is_array()); - - return json; - } - - static void dispatchWorkspace(int workspaceNumber) { - std::string out = "hyprctl dispatch workspace " + std::to_string(workspaceNumber); - CommandHelper::execNoOutput(out.c_str()); - } -}; +#include +#include class HyprSocketHelper { public: @@ -66,4 +29,105 @@ class HyprSocketHelper { return std::string(); } + + static std::string getHyprlandCommandSocketPath() { + const char *hyprlandInstanceSignature = std::getenv("HYPRLAND_INSTANCE_SIGNATURE"); + const char *xdgRuntimeDir = std::getenv("XDG_RUNTIME_DIR"); + + if (!xdgRuntimeDir || !hyprlandInstanceSignature) { + return std::string(); + } + + std::string basePath = std::string(xdgRuntimeDir) + "/hypr/" + std::string(hyprlandInstanceSignature) + "/"; + std::string sock1 = basePath + ".socket.sock"; + if (access(sock1.c_str(), F_OK) == 0) { + return sock1; + } + + return std::string(); + } +}; + +class HyprctlHelper { + public: + static std::string sendCommand(const std::string &command) { + std::string socketPath = HyprSocketHelper::getHyprlandCommandSocketPath(); + assert(!socketPath.empty() && "Failed to get Hyprland command socket path"); + + int socketFd = socket(AF_UNIX, SOCK_STREAM, 0); + assert(socketFd != -1 && "Failed to create Hyprland command socket"); + + struct sockaddr_un addr {}; + std::memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + auto sunPathSpan = std::span(addr.sun_path); + std::ranges::fill(sunPathSpan, '\0'); + constexpr size_t kMaxPathLength = sizeof(addr.sun_path) - 1; + const size_t copyLength = std::min(socketPath.size(), kMaxPathLength); + std::copy_n(socketPath.begin(), copyLength, sunPathSpan.begin()); + + auto *sockaddrPtr = static_cast(static_cast(&addr)); + int result = connect(socketFd, sockaddrPtr, sizeof(addr)); + assert(result != -1 && "Failed to connect to Hyprland command socket"); + + ssize_t bytesSent = send(socketFd, command.c_str(), command.size(), 0); + assert(bytesSent != -1 && "Failed to send command to Hyprland socket"); + + shutdown(socketFd, SHUT_WR); + + std::string response; + constexpr size_t kBufferSize = 4096; + std::array buffer {}; + while (true) { + ssize_t bytesRead = recv(socketFd, buffer.data(), buffer.size(), 0); + if (bytesRead <= 0) { + break; + } + + response.append(buffer.data(), static_cast(bytesRead)); + } + + close(socketFd); + + return response; + } + + static nlohmann::json getMonitorData() { + std::string result = sendCommand("j/monitors"); + assert(!result.empty() && "Failed to get monitor data from Hyprland socket"); + + auto json = nlohmann::json::parse(result); + + assert(json.is_array()); + + return json; + } + + static nlohmann::json getWorkspaceData() { + std::string result = sendCommand("j/workspaces"); + assert(!result.empty() && "Failed to get workspace data from Hyprland socket"); + + auto json = nlohmann::json::parse(result); + + assert(json.is_array()); + + return json; + } + + static nlohmann::json getClientData() { + std::string result = sendCommand("j/clients"); + assert(!result.empty() && "Failed to get client data from Hyprland socket"); + + auto json = nlohmann::json::parse(result); + + assert(json.is_array()); + + return json; + } + + static void dispatchWorkspace(int workspaceNumber) { + std::string command = "dispatch workspace " + std::to_string(workspaceNumber); + sendCommand(command); + } }; \ No newline at end of file diff --git a/include/widgets/notification/baseNotification.hpp b/include/widgets/notification/baseNotification.hpp index 4734806..286fb39 100644 --- a/include/widgets/notification/baseNotification.hpp +++ b/include/widgets/notification/baseNotification.hpp @@ -17,14 +17,29 @@ class BaseNotification : public Gtk::Window { void resumeAutoClose(); void startAutoClose(int timeoutMs); - sigc::signal signal_close; - sigc::signal signal_hover_changed; - uint64_t getNotificationId() const { return this->notificationId; } + sigc::signal getSignalClose() { + return this->signalClose; + } + + sigc::signal getSignalHoverChanged() { + return this->signalHoverChanged; + } + private: + sigc::signal signalClose; + sigc::signal signalHoverChanged; + + uint64_t notificationId; + + bool autoClosePaused = false; + int autoCloseRemainingMs = 0; + std::chrono::steady_clock::time_point autoCloseDeadline; + sigc::connection autoCloseConnection; + void ensure_notification_css_loaded(); void start_auto_close_timeout(int timeoutMs); @@ -32,10 +47,15 @@ class BaseNotification : public Gtk::Window { void resume_auto_close(); protected: - uint64_t notificationId; + bool getAutoClosePaused() const { + return this->autoClosePaused; + } - bool autoClosePaused = false; - int autoCloseRemainingMs = 0; - std::chrono::steady_clock::time_point autoCloseDeadline; - sigc::connection autoCloseConnection; + int getAutoCloseRemainingMs() const { + return this->autoCloseRemainingMs; + } + + std::chrono::steady_clock::time_point getAutoCloseDeadline() const { + return this->autoCloseDeadline; + } }; \ No newline at end of file diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index 549c1b4..17adbf2 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -92,7 +92,7 @@ void HyprlandService::bindHyprlandSocket() { return; } - struct sockaddr_un addr; + struct sockaddr_un addr{}; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; std::strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1); diff --git a/src/services/notificationController.cpp b/src/services/notificationController.cpp index 28afad4..d8aca61 100644 --- a/src/services/notificationController.cpp +++ b/src/services/notificationController.cpp @@ -52,11 +52,11 @@ void NotificationController::showNotificationOnAllMonitors(NotifyMessage notify) timeout = DEFAULT_NOTIFICATION_TIMEOUT; } - notification->signal_close.connect([this, id = notification->getNotificationId()](int) { + notification->getSignalClose().connect([this, id = notification->getNotificationId()](int) { closeNotification(id); }); - notification->signal_hover_changed.connect([this, id = notification->getNotificationId()](bool hovered) { + notification->getSignalHoverChanged().connect([this, id = notification->getNotificationId()](bool hovered) { updateHoverState(id, hovered); }); @@ -80,11 +80,11 @@ void NotificationController::showCopyNotification(NotifyMessage notify) { notification->show(); notifications.push_back(notification); - notification->signal_close.connect([this, id = notification->getNotificationId()](int) { + notification->getSignalClose().connect([this, id = notification->getNotificationId()](int) { closeNotification(id); }); - notification->signal_hover_changed.connect([this, id = notification->getNotificationId()](bool hovered) { + notification->getSignalHoverChanged().connect([this, id = notification->getNotificationId()](bool hovered) { updateHoverState(id, hovered); }); notification->startAutoClose(DEFAULT_NOTIFICATION_TIMEOUT); @@ -103,11 +103,11 @@ void NotificationController::showSpotifyNotification(MprisPlayer2Message mpris) notification->show(); notifications.push_back(notification); - notification->signal_close.connect([this, id = notification->getNotificationId()](int) { + notification->getSignalClose().connect([this, id = notification->getNotificationId()](int) { closeNotification(id); }); - notification->signal_hover_changed.connect([this, id = notification->getNotificationId()](bool hovered) { + notification->getSignalHoverChanged().connect([this, id = notification->getNotificationId()](bool hovered) { updateHoverState(id, hovered); }); diff --git a/src/widgets/notification/baseNotification.cpp b/src/widgets/notification/baseNotification.cpp index 3cd129c..48d8f2b 100644 --- a/src/widgets/notification/baseNotification.cpp +++ b/src/widgets/notification/baseNotification.cpp @@ -41,17 +41,17 @@ BaseNotification::BaseNotification(uint64_t notificationId, std::shared_ptrsignal_close.emit(this->notificationId); + this->getSignalClose().emit(this->notificationId); }); add_controller(window_click); auto window_motion = Gtk::EventControllerMotion::create(); window_motion->signal_enter().connect([this](double, double) { - signal_hover_changed.emit(true); + getSignalHoverChanged().emit(true); pause_auto_close(); }); window_motion->signal_leave().connect([this]() { - signal_hover_changed.emit(false); + getSignalHoverChanged().emit(false); resume_auto_close(); }); add_controller(window_motion); @@ -82,7 +82,7 @@ void BaseNotification::start_auto_close_timeout(int timeoutMs) { autoCloseDeadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); autoCloseConnection = Glib::signal_timeout().connect([this]() { - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->notificationId); return false; // Don't repeat }, timeoutMs); @@ -110,7 +110,7 @@ void BaseNotification::resume_auto_close() { autoClosePaused = false; if (autoCloseRemainingMs <= 0) { - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->notificationId); return; } diff --git a/src/widgets/notification/copyNotification.cpp b/src/widgets/notification/copyNotification.cpp index e812b5e..dd1f405 100644 --- a/src/widgets/notification/copyNotification.cpp +++ b/src/widgets/notification/copyNotification.cpp @@ -92,7 +92,7 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { copyToClipboard(this->copiedImage); spdlog::info("Copied image to clipboard"); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); }); saveToClipboardButton->set_tooltip_text("Copy to Clipboard"); saveToClipboardButton->add_css_class("notification-button"); @@ -100,7 +100,6 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { auto saveToFileButton = Gtk::make_managed(Icon::SAVE); saveToFileButton->signal_clicked().connect([this]() { - // xdg-pic/screenshot // use env auto xdgPicturesDir = Glib::get_user_special_dir(Glib::UserDirectory::PICTURES); auto dateStamp = Glib::DateTime::create_now_local().format("%Y%m%d_%H%M%S"); auto filepath = xdgPicturesDir + "/screenshot"; @@ -108,7 +107,7 @@ void CopyNotification::createImageNotification(NotifyMessage notify) { saveImageToFile(this->copiedImage, filepath, filename); spdlog::info("Saved image to {}", filepath.c_str()); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); }); saveToFileButton->set_tooltip_text("Save to File"); saveToFileButton->add_css_class("notification-button"); @@ -134,7 +133,7 @@ void CopyNotification::createTextNotification(NotifyMessage notify) { auto copyToClipboardButton = Gtk::make_managed(Icon::CONTENT_COPY); copyToClipboardButton->signal_clicked().connect([this]() { copyToClipboard(this->copiedText); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); }); copyToClipboardButton->set_tooltip_text("Copy to Clipboard"); copyToClipboardButton->add_css_class("notification-icon-button"); diff --git a/src/widgets/notification/notificationWindow.cpp b/src/widgets/notification/notificationWindow.cpp index c44fb8c..463d4c1 100644 --- a/src/widgets/notification/notificationWindow.cpp +++ b/src/widgets/notification/notificationWindow.cpp @@ -91,7 +91,7 @@ NotificationWindow::NotificationWindow(uint64_t notificationId, std::shared_ptr< if (cb && guard && !*guard) { *guard = true; cb(action_id); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); } }); actions_box->append(*btn); diff --git a/src/widgets/notification/spotifyNotification.cpp b/src/widgets/notification/spotifyNotification.cpp index 84724ae..78e3f3c 100644 --- a/src/widgets/notification/spotifyNotification.cpp +++ b/src/widgets/notification/spotifyNotification.cpp @@ -66,7 +66,7 @@ std::unique_ptr SpotifyNotification::createButtonBox(MprisPlayer backButton->signal_clicked().connect([this, mpris]() { if (mpris.previous) { mpris.previous(); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); } }); @@ -93,7 +93,7 @@ std::unique_ptr SpotifyNotification::createButtonBox(MprisPlayer nextButton->signal_clicked().connect([this, mpris]() { if (mpris.next) { mpris.next(); - this->signal_close.emit(this->notificationId); + this->getSignalClose().emit(this->getNotificationId()); } }); buttonBox->set_start_widget(*backButton);