From 11ccd55a52419abdc59908513090d537f48a69be Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Wed, 17 Dec 2025 22:45:30 +0100 Subject: [PATCH] fix workspace interactivity --- include/services/hyprland.hpp | 7 +- include/widgets/workspaceIndicator.hpp | 5 +- src/bar/bar.cpp | 2 +- src/services/hyprland.cpp | 197 +++++++++---------------- src/widgets/workspaceIndicator.cpp | 90 ++++++----- 5 files changed, 125 insertions(+), 176 deletions(-) diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp index 8ed006a..96c389d 100644 --- a/include/services/hyprland.hpp +++ b/include/services/hyprland.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -43,7 +42,7 @@ class HyprlandService { void printMonitor(const Monitor &mon) const; sigc::signal socketEventSignal; - sigc::signal workspaceStateChanged; + sigc::signal workspaceStateChanged; sigc::signal monitorStateChanged; Monitor *getMonitorById(int id); @@ -52,14 +51,14 @@ class HyprlandService { const Monitor *getMonitorByIndex(std::size_t index) const; void switchToWorkspace(int workspaceId); - std::map getAllWorkspaces() const { + std::map getAllWorkspaces() const { return this->workspaces; } private: int fd = -1; std::map monitors; - std::map workspaces; + std::map workspaces; std::string get_socket_path(); bool on_socket_read(Glib::IOCondition condition); diff --git a/include/widgets/workspaceIndicator.hpp b/include/widgets/workspaceIndicator.hpp index 4dd3045..a2cc648 100644 --- a/include/widgets/workspaceIndicator.hpp +++ b/include/widgets/workspaceIndicator.hpp @@ -16,9 +16,10 @@ class WorkspaceIndicator : public Gtk::Box { int monitorId; sigc::connection workspaceConnection; sigc::connection monitorConnection; + std::map workspaceLabels; void rebuild(); - void on_workspace_update(int monitorId); + void on_workspace_update(); void on_monitor_update(); - void clear_children(); + void refreshLabel(Gtk::Label *label, HyprlandService::WorkspaceState state); }; diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index 8f8dd27..5c24178 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -82,7 +82,7 @@ void Bar::setup_ui() { trayWidget = Gtk::make_managed(trayService); right_box.append(*trayWidget); - right_box.append(homeAssistant); + // right_box.append(homeAssistant); } void Bar::load_css() { diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index 52b2772..9e57489 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -1,6 +1,5 @@ #include "services/hyprland.hpp" -#include #include #include #include @@ -35,13 +34,16 @@ HyprlandService::~HyprlandService() { } void HyprlandService::on_hyprland_event(std::string event, std::string data) { + if (event == "monitoradded" || event == "monitorremoved") { + refresh_monitors(); + } + if (event == "urgent") { handle_urgent_window(data); } - if (is_workspace_event(event) || event == "focusedmon" || - event == "monitoradded" || event == "monitorremoved") { - refresh_monitors(); + if (event == "workspace" || event == "focusedmon" || event == "monitoradded" || + event == "monitorremoved" || event == "movewindow" || event == "activewindow") { refresh_workspaces(); } } @@ -80,7 +82,6 @@ void HyprlandService::start() { Glib::IOCondition::IO_ERR); refresh_monitors(); - refresh_workspaces(); } bool HyprlandService::on_socket_read(Glib::IOCondition condition) { @@ -140,26 +141,11 @@ void HyprlandService::refresh_monitors() { this->monitors.clear(); this->workspaces.clear(); - std::string output; - - try { - output = SystemHelper::get_command_output(kMonitorCommand); - } catch (const std::exception &ex) { - std::cerr << "[Hyprland] Failed to query monitors: " << ex.what() - << std::endl; - return; - } - + std::string output = SystemHelper::get_command_output(kMonitorCommand); auto monitorsJson = nlohmann::json::parse(output, nullptr, false); - if (!monitorsJson.is_array()) { - std::cerr << "[Hyprland] Unexpected monitor payload" << std::endl; - return; - } for (const auto &monitorJson : monitorsJson) { - if (!monitorJson.is_object()) { - continue; - } + assert(monitorJson.is_object()); Monitor monitor; monitor.id = monitorJson.value("id", -1); @@ -167,11 +153,7 @@ void HyprlandService::refresh_monitors() { monitor.x = monitorJson.value("x", 0); monitor.y = monitorJson.value("y", 0); - if (monitorJson.contains("activeWorkspace") && - monitorJson["activeWorkspace"].is_object()) { - monitor.focusedWorkspaceId = - monitorJson["activeWorkspace"].value("id", -1); - } + monitor.focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); if (monitor.id >= 0) { this->monitors[monitor.id] = monitor; @@ -181,78 +163,63 @@ void HyprlandService::refresh_monitors() { WorkspaceState wsState; wsState.focused = false; wsState.active = false; - wsState.urgentWindows.clear(); wsState.label = std::to_string(slot); wsState.monitorId = monitor.id; int id = slot + monitor.id * HyprlandService::kWorkspaceSlotCount; wsState.hyprId = id; - this->workspaces[id] = wsState; - this->monitors[monitor.id].workspaceStates[slot] = &this->workspaces[id]; + this->workspaces[id] = new WorkspaceState(wsState); + this->monitors[monitor.id].workspaceStates[slot] = this->workspaces[id]; } } + this->refresh_workspaces(); monitorStateChanged.emit(); } +/** + * Called every time when workspace changes have been detected. + * Used to Update the internal state for the Workspaces, + */ void HyprlandService::refresh_workspaces() { - if (monitors.empty()) { - return; - } - - std::string output; - - try { - output = SystemHelper::get_command_output(kWorkspaceCommand); - } catch (const std::exception &ex) { - std::cerr << "[Hyprland] Failed to query workspaces: " << ex.what() - << std::endl; - return; - } - + std::string output = SystemHelper::get_command_output(kWorkspaceCommand); auto workspacesJson = nlohmann::json::parse(output, nullptr, false); - if (!workspacesJson.is_array()) { - std::cerr << "[Hyprland] Unexpected workspace payload" << std::endl; - return; + + for (auto &[id, ws] : this->workspaces) { + ws->focused = false; + ws->active = false; + } + + output = SystemHelper::get_command_output(kMonitorCommand); + auto monitorsJson = nlohmann::json::parse(output, nullptr, false); + + for (const auto &monitorJson : monitorsJson) { + + + const int monitorId = monitorJson.value("id", -1); + const int focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); + + auto monitorIt = this->monitors.find(monitorId); + + monitorIt->second.focusedWorkspaceId = focusedWorkspaceId; } for (const auto &workspaceJson : workspacesJson) { - if (!workspaceJson.is_object()) { - continue; - } - const int workspaceId = workspaceJson.value("id", -1); + std::map::iterator workspaceStateIt = this->workspaces.find(workspaceId); + WorkspaceState *workspaceState = workspaceStateIt->second; - std::cout << "Workspace ID: " << workspaceId << std::endl; - assert(workspaceId != -1); + workspaceState->focused = monitors + .at(workspaceState->monitorId) + .focusedWorkspaceId == + workspaceId; + workspaceState->active = true; - std::map::iterator workspaceStateIt = this->workspaces.find(workspaceId); - - if (workspaceStateIt == this->workspaces.end()) { - assert(false); // should not happen - } - - WorkspaceState *workspaceState = &workspaceStateIt->second; - workspaceState->hyprId = workspaceId; - workspaceState->focused = (this->monitors[workspaceState->monitorId].focusedWorkspaceId == workspaceId); - workspaceState->active = - workspaceState->focused || workspaceJson.value("windows", 0) > 0; - workspaceState->urgentWindows.clear(); - - if (workspaceJson.contains("urgent") && - workspaceJson["urgent"].is_boolean()) { - if (workspaceJson["urgent"].get()) { - workspaceState->urgentWindows.push_back(workspaceId); - } - } - - this->workspaces[workspaceId] = *workspaceState; + this->workspaces[workspaceId] = workspaceState; } - for (const auto &pair : monitors) { - workspaceStateChanged.emit(pair.first); - } + workspaceStateChanged.emit(); } HyprlandService::Monitor *HyprlandService::getMonitorById(int id) { @@ -275,15 +242,14 @@ const HyprlandService::Monitor *HyprlandService::getMonitorById(int id) const { return &it->second; } -HyprlandService::Monitor * -HyprlandService::getMonitorByIndex(std::size_t index) { +HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) { if (index >= monitors.size()) { - throw std::runtime_error("Monitor index out of bounds: " + - std::to_string(index)); + throw std::runtime_error("Monitor index out of bounds: " + std::to_string(index)); } auto it = monitors.begin(); std::advance(it, static_cast(index)); + return &it->second; } @@ -300,60 +266,35 @@ void HyprlandService::switchToWorkspace(int workspaceId) { } void HyprlandService::handle_urgent_window(std::string windowAddress) { - std::string output; - // TODO: fix bugs first - return; - - try { - output = SystemHelper::get_command_output(kClientsCommand); - } catch (const std::exception &ex) { - std::cerr << "[Hyprland] Failed to query clients: " << ex.what() - << std::endl; - return; - } - + std::string output = SystemHelper::get_command_output(kClientsCommand); auto clientsJson = nlohmann::json::parse(output, nullptr, false); - if (!clientsJson.is_array()) { - return; - } - int workspaceId = -1; - - for (const auto &client : clientsJson) { - if (!client.is_object()) + for (const auto &clientJson : clientsJson) { + if (!clientJson.is_object()) { continue; - - std::string addr = client.value("address", ""); - if (addr == "0x" + windowAddress) { - if (client.contains("workspace") && - client["workspace"].is_object()) { - workspaceId = client["workspace"].value("id", -1); - } - break; - } - } - - if (workspaceId == -1) { - return; - } - - for (auto &pair : monitors) { - auto &monitor = pair.second; - bool changed = false; - - for (auto &wsPair : monitor.workspaceStates) { - if (wsPair.second->hyprId == workspaceId) { - if (wsPair.second->urgentWindows.empty()) { - wsPair.second->urgentWindows.push_back(workspaceId); - changed = true; - } - } } - if (changed) { - this->workspaceStateChanged.emit(monitor.id); + const std::string addr = clientJson.value("address", ""); + if (addr != windowAddress) { + continue; } + + const int workspaceId = clientJson.value("workspace", -1); + if (workspaceId < 0) { + continue; + } + + auto workspaceIt = this->workspaces.find(workspaceId); + if (workspaceIt == this->workspaces.end()) { + continue; + } + + WorkspaceState *wsState = workspaceIt->second; + wsState->urgentWindows.push_back(1); + + break; } + workspaceStateChanged.emit(); } void HyprlandService::printMonitor(const Monitor &mon) const { diff --git a/src/widgets/workspaceIndicator.cpp b/src/widgets/workspaceIndicator.cpp index 81406f1..93683a5 100644 --- a/src/widgets/workspaceIndicator.cpp +++ b/src/widgets/workspaceIndicator.cpp @@ -1,5 +1,7 @@ #include "widgets/workspaceIndicator.hpp" +#include "services/hyprland.hpp" +#include #include #include #include @@ -16,6 +18,23 @@ WorkspaceIndicator::WorkspaceIndicator(HyprlandService &service, int monitorId) monitorConnection = service.monitorStateChanged.connect( sigc::mem_fun(*this, &WorkspaceIndicator::on_monitor_update)); + for (int i = 1; i <= HyprlandService::kWorkspaceSlotCount; ++i) { + auto label = Gtk::make_managed(std::to_string(i)); + label->add_css_class("workspace-pill"); + + auto gesture = Gtk::GestureClick::create(); + gesture->set_button(GDK_BUTTON_PRIMARY); + gesture->signal_released().connect( + [this, i, &service](int, double, double) { + service.switchToWorkspace( + i + this->monitorId * HyprlandService::kWorkspaceSlotCount); + }); + label->add_controller(gesture); + + workspaceLabels[i] = label; + append(*label); + } + rebuild(); } @@ -29,53 +48,42 @@ WorkspaceIndicator::~WorkspaceIndicator() { } } -void WorkspaceIndicator::on_workspace_update(int monitorId) { - if (this->monitorId != monitorId && monitorId != -1) { - return; - } - +void WorkspaceIndicator::on_workspace_update() { rebuild(); } void WorkspaceIndicator::on_monitor_update() { rebuild(); } +void WorkspaceIndicator::refreshLabel(Gtk::Label *label, HyprlandService::WorkspaceState state) { + label->remove_css_class("workspace-pill-active"); + label->remove_css_class("workspace-pill-focused"); + label->remove_css_class("workspace-pill-urgent"); + + auto gesture = Gtk::GestureClick::create(); + gesture->set_button(GDK_BUTTON_PRIMARY); + gesture->signal_released().connect( + [this, state](int, double, double) { + service.switchToWorkspace(state.hyprId); + }); + label->add_controller(gesture); + + if (state.urgentWindows.size() > 0) { + label->add_css_class("workspace-pill-urgent"); + } else { + if (state.focused) { + label->add_css_class("workspace-pill-focused"); + } else if (state.active) { + label->add_css_class("workspace-pill-active"); + } + } +} + void WorkspaceIndicator::rebuild() { - HyprlandService hypr = this->service; - int i = 0; - for (auto &[id, workspaceState] : hypr.getAllWorkspaces()) { - if (workspaceState.monitorId != this->monitorId) { - continue; - } + HyprlandService::Monitor *mon = service.getMonitorById(this->monitorId); - - auto label = Gtk::make_managed(workspaceState.label); - label->add_css_class("workspace-pill"); - - auto gesture = Gtk::GestureClick::create(); - gesture->set_button(GDK_BUTTON_PRIMARY); - gesture->signal_released().connect( - [this, id](int, double, double) { service.switchToWorkspace(id); }); - label->add_controller(gesture); - - if (workspaceState.urgentWindows.empty()) { - if (workspaceState.focused) { - label->add_css_class("workspace-pill-focused"); - } else if (workspaceState.active) { - label->add_css_class("workspace-pill-active"); - } - } else { - label->add_css_class("workspace-pill-urgent"); - } - - append(*label); - } -} - -void WorkspaceIndicator::clear_children() { - Gtk::Widget *child = get_first_child(); - while (child != nullptr) { - Gtk::Widget *next = child->get_next_sibling(); - remove(*child); - child = next; + for (auto [id, workspaceState] : mon->workspaceStates) { + assert(id > 0); + Gtk::Label *label = workspaceLabels[id]; + this->refreshLabel(label, *workspaceState); } }