From c245fa7277d857d057a5469fed2211a325838f25 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Thu, 18 Dec 2025 11:04:30 +0100 Subject: [PATCH] deine cousine --- include/services/hyprland.hpp | 2 + include/widgets/workspaceIndicator.hpp | 4 +- resources/bar.css | 26 ++++- src/bar/bar.cpp | 8 +- src/services/hyprland.cpp | 126 ++++++++++++++++--------- src/widgets/workspaceIndicator.cpp | 17 ++-- 6 files changed, 120 insertions(+), 63 deletions(-) diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp index 613f9a7..327d142 100644 --- a/include/services/hyprland.hpp +++ b/include/services/hyprland.hpp @@ -59,6 +59,8 @@ class HyprlandService { int fd = -1; std::map monitors; std::map workspaces; + // persistent buffer for socket reads to handle partial messages + std::string socket_buffer; 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 a2cc648..671f78b 100644 --- a/include/widgets/workspaceIndicator.hpp +++ b/include/widgets/workspaceIndicator.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "services/hyprland.hpp" @@ -17,9 +18,10 @@ class WorkspaceIndicator : public Gtk::Box { sigc::connection workspaceConnection; sigc::connection monitorConnection; std::map workspaceLabels; + std::map> workspaceGestures; void rebuild(); void on_workspace_update(); void on_monitor_update(); - void refreshLabel(Gtk::Label *label, HyprlandService::WorkspaceState state); + void refreshLabel(Gtk::Label *label, const HyprlandService::WorkspaceState &state); }; diff --git a/resources/bar.css b/resources/bar.css index d781daa..1d5a1f9 100644 --- a/resources/bar.css +++ b/resources/bar.css @@ -30,7 +30,11 @@ window { background-color: #ffffff; color: #1e1e1e; font-weight: bold; - border-bottom: #89b4fa 2px; + box-shadow: 0 0 6px rgba(255, 255, 255, 0.8); +} + +.workspace-pill-focused:hover { + box-shadow: none; } .workspace-pill-active { @@ -40,6 +44,24 @@ window { .workspace-pill-urgent { background-color: #ff5555; color: #fff; + + /* base glow (will be animated) */ + animation: workspace-blink 1s linear infinite; +} + +/* blinking animation for urgent workspaces */ +@keyframes workspace-blink { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 1; + } } .workspace-pill:last-child { @@ -88,4 +110,4 @@ tooltip { .icon-label { font-family: "Material Icons, Font Awesome 7 Brands, Hack Nerd Font Mono"; font-size: 19px; -} +} \ No newline at end of file diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index fc6a6d5..dd257d8 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -32,6 +32,7 @@ Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService, gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_TOP, true); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_LEFT, true); gtk_layer_set_anchor(this->gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, true); + gtk_layer_set_layer(this->gobj(), GTK_LAYER_SHELL_LAYER_TOP); gtk_layer_auto_exclusive_zone_enable(this->gobj()); @@ -62,15 +63,14 @@ void Bar::setup_ui() { left_box.set_valign(Gtk::Align::CENTER); - // Don't expand the center box — keep it centered by alignment center_box.set_hexpand(false); center_box.set_valign(Gtk::Align::CENTER); center_box.set_halign(Gtk::Align::CENTER); right_box.set_valign(Gtk::Align::CENTER); - workspaceIndicator = - Gtk::make_managed(hyprlandService, monitorId); + workspaceIndicator = Gtk::make_managed(hyprlandService, monitorId); + left_box.append(*workspaceIndicator); clock.set_name("clock-label"); @@ -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 34ec4ef..8d79317 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -21,17 +21,25 @@ HyprlandService::~HyprlandService() { close(fd); fd = -1; } + + // free allocated workspace pointers + for (auto &p : this->workspaces) { + delete p.second; + } + this->workspaces.clear(); + this->monitors.clear(); } void HyprlandService::on_hyprland_event(std::string event, std::string data) { - if (event == "monitoradded" || event == "monitorremoved") { - refresh_monitors(); - } - + if (event == "urgent") { onUrgentEvent(data); } + if (event == "activewindowv2") { + onActiveWindowEvent(data); + } + if (event == "workspace" || event == "movewindow") { refresh_workspaces(); } @@ -39,8 +47,8 @@ void HyprlandService::on_hyprland_event(std::string event, std::string data) { // use for // event == "focusedmon" - if (event == "activewindowv2") { - onActiveWindowEvent(data); + if (event == "monitoradded" || event == "monitorremoved") { + refresh_monitors(); } } @@ -81,31 +89,38 @@ void HyprlandService::start() { } bool HyprlandService::on_socket_read(Glib::IOCondition condition) { - const auto error_mask = - Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR; + const auto error_mask = Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR; if (static_cast(condition & error_mask) != 0) { std::cerr << "[Hyprland] Socket disconnected." << std::endl; - close(fd); - fd = -1; + if (fd != -1) { + close(fd); + fd = -1; + } return false; } - std::string buffer; char temp_buffer[4096]; - const ssize_t bytes_read = read(fd, temp_buffer, sizeof(temp_buffer) - 1); + const ssize_t bytes_read = read(fd, temp_buffer, sizeof(temp_buffer)); - if (bytes_read > 0) { - temp_buffer[bytes_read] = '\0'; - buffer.append(temp_buffer); - - size_t pos = 0; - - while ((pos = buffer.find('\n')) != std::string::npos) { - const std::string line = buffer.substr(0, pos); - parse_message(line); - buffer.erase(0, pos + 1); + if (bytes_read <= 0) { + // peer closed or error + std::cerr << "[Hyprland] Socket read returned " << bytes_read << std::endl; + if (fd != -1) { + close(fd); + fd = -1; } + return false; + } + + // append exactly bytes_read bytes (may contain embedded nulls) + this->socket_buffer.append(temp_buffer, static_cast(bytes_read)); + + size_t pos = 0; + while ((pos = this->socket_buffer.find('\n')) != std::string::npos) { + const std::string line = this->socket_buffer.substr(0, pos); + parse_message(line); + this->socket_buffer.erase(0, pos + 1); } return true; @@ -134,8 +149,12 @@ std::string HyprlandService::get_socket_path() { } void HyprlandService::refresh_monitors() { - this->monitors.clear(); + // free any previously allocated WorkspaceState objects before rebuilding + for (auto &p : this->workspaces) { + delete p.second; + } this->workspaces.clear(); + this->monitors.clear(); std::string output = SystemHelper::get_command_output(kMonitorCommand); auto monitorsJson = nlohmann::json::parse(output, nullptr, false); @@ -160,10 +179,12 @@ void HyprlandService::refresh_monitors() { wsState.label = std::to_string(slot); wsState.monitorId = monitor.id; - int id = slot + monitor.id * HyprlandService::kWorkspaceSlotCount; - wsState.hyprId = id; - this->workspaces[id] = new WorkspaceState(wsState); - this->monitors[monitor.id].workspaceStates[slot] = this->workspaces[id]; + int id = slot + monitor.id * HyprlandService::kWorkspaceSlotCount; + wsState.hyprId = id; + this->workspaces[id] = new WorkspaceState(wsState); + if (monitor.id >= 0) { + this->monitors[monitor.id].workspaceStates[slot] = this->workspaces[id]; + } } } @@ -191,17 +212,28 @@ void HyprlandService::refresh_workspaces() { const int monitorId = monitorJson.value("id", -1); const int focusedWorkspaceId = monitorJson["activeWorkspace"].value("id", -1); - auto monitor = this->monitors[monitorId]; - monitor.focusedWorkspaceId = focusedWorkspaceId; + // write into the stored monitor (use reference) + auto it = this->monitors.find(monitorId); + if (it != this->monitors.end()) { + it->second.focusedWorkspaceId = focusedWorkspaceId; + } } for (const auto &workspaceJson : workspacesJson) { const int workspaceId = workspaceJson.value("id", -1); - std::map::iterator workspaceStateIt = this->workspaces.find(workspaceId); - WorkspaceState *workspaceState = workspaceStateIt->second; + auto workspaceStateIt = this->workspaces.find(workspaceId); + if (workspaceStateIt == this->workspaces.end()) { + continue; + } - workspaceState->focused = monitors[workspaceState->monitorId].focusedWorkspaceId == workspaceId; - workspaceState->active = true; + WorkspaceState *workspaceState = workspaceStateIt->second; + auto mit = this->monitors.find(workspaceState->monitorId); + if (mit != this->monitors.end()) { + workspaceState->focused = mit->second.focusedWorkspaceId == workspaceId; + } else { + workspaceState->focused = false; + } + workspaceState->active = true; } workspaceStateChanged.emit(); @@ -229,12 +261,16 @@ void HyprlandService::onUrgentEvent(std::string windowAddress) { if (addr == "0x" + windowAddress) { int workspaceId = clientJson["workspace"].value("id", -1); - WorkspaceState *ws = this->workspaces[workspaceId]; + auto it = this->workspaces.find(workspaceId); + if (it != this->workspaces.end() && it->second) { + + if (std::find(it->second->urgentWindows.begin(), + it->second->urgentWindows.end(), windowAddress) == + it->second->urgentWindows.end()) { + it->second->urgentWindows.push_back(windowAddress); + workspaceStateChanged.emit(); + } - // todo: maybee better method of access - if (ws) { - ws->urgentWindows.push_back(windowAddress); - workspaceStateChanged.emit(); } break; @@ -250,13 +286,13 @@ void HyprlandService::onActiveWindowEvent(std::string windowAddress) { const std::string addr = clientJson.value("address", ""); if (addr == "0x" + windowAddress) { - int workspaceId = clientJson["workspace"]["id"]; - WorkspaceState *ws = this->workspaces[workspaceId]; - - if (ws) { - auto it = std::find(ws->urgentWindows.begin(), ws->urgentWindows.end(), windowAddress); - if (it != ws->urgentWindows.end()) { - ws->urgentWindows.erase(it); + int workspaceId = clientJson["workspace"]["id"]; + auto it = this->workspaces.find(workspaceId); + if (it != this->workspaces.end() && it->second) { + WorkspaceState *ws = it->second; + auto uit = std::find(ws->urgentWindows.begin(), ws->urgentWindows.end(), windowAddress); + if (uit != ws->urgentWindows.end()) { + ws->urgentWindows.erase(uit); workspaceStateChanged.emit(); } diff --git a/src/widgets/workspaceIndicator.cpp b/src/widgets/workspaceIndicator.cpp index 1c27268..046d6df 100644 --- a/src/widgets/workspaceIndicator.cpp +++ b/src/widgets/workspaceIndicator.cpp @@ -22,14 +22,16 @@ WorkspaceIndicator::WorkspaceIndicator(HyprlandService &service, int monitorId) auto label = Gtk::make_managed(std::to_string(i)); label->add_css_class("workspace-pill"); + // create and store a gesture controller once to avoid duplicate handlers auto gesture = Gtk::GestureClick::create(); gesture->set_button(GDK_BUTTON_PRIMARY); gesture->signal_released().connect( - [this, i, &service](int, double, double) { - service.switchToWorkspace( + [this, i](int, double, double) { + this->service.switchToWorkspace( i + this->monitorId * HyprlandService::kWorkspaceSlotCount); }); label->add_controller(gesture); + workspaceGestures[i] = gesture; workspaceLabels[i] = label; append(*label); @@ -54,19 +56,12 @@ void WorkspaceIndicator::on_workspace_update() { void WorkspaceIndicator::on_monitor_update() { rebuild(); } -void WorkspaceIndicator::refreshLabel(Gtk::Label *label, HyprlandService::WorkspaceState state) { +void WorkspaceIndicator::refreshLabel(Gtk::Label *label, const 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); - + // controller created once in constructor and reused if (state.urgentWindows.size() > 0) { label->add_css_class("workspace-pill-urgent"); } else {