From d53dfa27f1cc25e6e9277be4dbbd678b97d66857 Mon Sep 17 00:00:00 2001 From: Arif Hasanic Date: Tue, 9 Dec 2025 22:53:29 +0100 Subject: [PATCH] add workspace indicators --- CMakeLists.txt | 1 + include/bar/bar.hpp | 11 +- include/services/hyprland.hpp | 112 +++++---- include/widgets/workspaceIndicator.hpp | 25 ++ src/app.cpp | 17 +- src/bar/bar.cpp | 52 +++-- src/services/hyprland.cpp | 301 +++++++++++++++++++------ src/widgets/workspaceIndicator.cpp | 114 ++++++++++ 8 files changed, 501 insertions(+), 132 deletions(-) create mode 100644 include/widgets/workspaceIndicator.hpp create mode 100644 src/widgets/workspaceIndicator.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 564e844..9f998f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ target_sources(bar_lib src/app.cpp src/bar/bar.cpp src/widgets/clock.cpp + src/widgets/workspaceIndicator.cpp src/services/hyprland.cpp ) include_directories(bar_lib PRIVATE diff --git a/include/bar/bar.hpp b/include/bar/bar.hpp index 31c6d8f..2a0c3ee 100644 --- a/include/bar/bar.hpp +++ b/include/bar/bar.hpp @@ -5,15 +5,24 @@ #include "services/hyprland.hpp" #include "widgets/clock.hpp" +#include "widgets/workspaceIndicator.hpp" class Bar : public Gtk::Window { public: - Bar(GdkMonitor* monitor, HyprlandService::Monitor* hyprlandMonitor); + Bar(GdkMonitor *monitor, HyprlandService &hyprlandService, int monitorId); protected: Clock clock; Gtk::CenterBox main_box{}; + Gtk::Box left_box{Gtk::Orientation::HORIZONTAL}; + Gtk::Box center_box{Gtk::Orientation::HORIZONTAL}; + Gtk::Box right_box{Gtk::Orientation::HORIZONTAL}; + + private: + HyprlandService &m_hyprlandService; + int m_monitorId; + WorkspaceIndicator *m_workspaceIndicator = nullptr; void setup_ui(); void load_css(); diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp index 3b3fb03..41268f7 100644 --- a/include/services/hyprland.hpp +++ b/include/services/hyprland.hpp @@ -1,68 +1,100 @@ #pragma once +#include #include #include #include #include #include -class HyprlandService { - +class HyprlandService +{ public: - typedef struct WorkspaceState { - int id; - bool active; - bool focused; - bool urgent; - } WorkspaceState; + static constexpr int kWorkspaceSlotCount = 5; - typedef struct Monitor { + struct WorkspaceState + { + int id = -1; + int hyprId = -1; + bool active = false; + bool focused = false; + bool urgent = false; + std::string label; + }; + + struct Monitor + { std::map workspaceStates; std::string name; - int x; - int y; - int id; - int focusedWorkspaceId; - } Monitor; + int x = 0; + int y = 0; + int id = -1; + int focusedWorkspaceId = -1; + }; HyprlandService(); ~HyprlandService(); - // For debugging purposes - void printMonitor(const Monitor mon) { - std::cout << "=== Monitor Info ===\n"; - std::cout << "Name: " << mon.name << " (ID: " << mon.id << ")\n"; - std::cout << "Position: (" << mon.x << ", " << mon.y << ")\n"; - std::cout << "Focused Workspace ID: " << mon.focusedWorkspaceId << "\n"; - - std::cout << "Workspaces:\n"; - if (mon.workspaceStates.empty()) { - std::cout << " (None)\n"; - } else { - for (const auto &pair : mon.workspaceStates) { - const WorkspaceState ws = pair.second; - std::cout << " - [ID: " << ws.id << "] " - << "Active: " << (ws.active ? "Yes" : "No") << " | " - << "Focused: " << (ws.focused ? "Yes" : "No") << " | " - << "Urgent: " << (ws.urgent ? "Yes" : "No") << "\n"; - } - } - std::cout << "====================\n"; - } - - sigc::signal socketEventSignal; - void start(); void on_hyprland_event(std::string event, std::string data); - Monitor* getMonitorById(const int &name); + void printMonitor(const Monitor &mon) const; + + sigc::signal socketEventSignal; + sigc::signal workspaceStateChanged; + sigc::signal monitorStateChanged; + + Monitor *getMonitorById(int id); + const Monitor *getMonitorById(int id) const; + Monitor *getMonitorByIndex(std::size_t index); + const Monitor *getMonitorByIndex(std::size_t index) const; private: int m_fd = -1; std::string m_buffer; - std::map monitors; + std::map m_monitors; bool on_socket_read(Glib::IOCondition condition); void parse_message(const std::string &line); std::string get_socket_path(); + void refresh_monitors(); + void refresh_workspaces(); }; + +inline void HyprlandService::printMonitor(const Monitor &mon) const +{ + std::cout << "=== Monitor Info ===\n"; + std::cout << "Name: " << mon.name << " (ID: " << mon.id << ")\n"; + std::cout << "Position: (" << mon.x << ", " << mon.y << ")\n"; + std::cout << "Focused Workspace ID: " << mon.focusedWorkspaceId << "\n"; + + std::cout << "Workspaces:\n"; + + if (mon.workspaceStates.empty()) + { + std::cout << " (None)\n"; + } + else + { + for (int slot = 1; slot <= HyprlandService::kWorkspaceSlotCount; ++slot) + { + const auto it = mon.workspaceStates.find(slot); + if (it == mon.workspaceStates.end()) + { + std::cout << " - [Slot: " << slot << " | HyprID: n/a]" + << " Label: | Active: No | Focused: No | Urgent: No\n"; + continue; + } + + const WorkspaceState &ws = it->second; + std::cout << " - [Slot: " << ws.id << " | HyprID: " + << (ws.hyprId >= 0 ? std::to_string(ws.hyprId) : std::string("n/a")) << "] " + << "Label: " << (ws.label.empty() ? "" : ws.label) << " | " + << "Active: " << (ws.active ? "Yes" : "No") << " | " + << "Focused: " << (ws.focused ? "Yes" : "No") << " | " + << "Urgent: " << (ws.urgent ? "Yes" : "No") << "\n"; + } + } + + std::cout << "====================\n"; +} diff --git a/include/widgets/workspaceIndicator.hpp b/include/widgets/workspaceIndicator.hpp new file mode 100644 index 0000000..a22352c --- /dev/null +++ b/include/widgets/workspaceIndicator.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "services/hyprland.hpp" + +class WorkspaceIndicator : public Gtk::Box +{ + public: + WorkspaceIndicator(HyprlandService &service, int monitorId); + ~WorkspaceIndicator() override; + + private: + HyprlandService &m_service; + int m_monitorId; + sigc::connection m_workspaceConnection; + sigc::connection m_monitorConnection; + + void rebuild(); + void on_workspace_update(int monitorId); + void on_monitor_update(); + void clear_children(); +}; diff --git a/src/app.cpp b/src/app.cpp index a4dc765..03852dd 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,5 +1,7 @@ #include "app.hpp" +#include +#include #include App::App() { @@ -15,11 +17,18 @@ App::App() { auto monitor = std::dynamic_pointer_cast( monitors->get_object(i)); if (monitor) { - auto hyprlanMonitor = this->hyprlandService.getMonitorById(i); + HyprlandService::Monitor* hyprlandMonitor = nullptr; + + try { + hyprlandMonitor = this->hyprlandService.getMonitorByIndex(i); + } catch (const std::exception &ex) { + std::cerr << "[App] Failed to fetch Hyprland monitor: " << ex.what() << std::endl; + continue; + } + + auto bar = new Bar(monitor->gobj(), this->hyprlandService, hyprlandMonitor->id); + this->hyprlandService.printMonitor(*hyprlandMonitor); // Debugging output - auto bar = new Bar(monitor->gobj(), hyprlanMonitor); - this->hyprlandService.printMonitor(*hyprlanMonitor); // Debugging output - bar->set_application(app); bar->show(); bars.push_back(bar); diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index cf71e93..2f6aab8 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -1,19 +1,20 @@ #include "bar/bar.hpp" +#include "widgets/workspaceIndicator.hpp" #include #include - -#include "services/hyprland.hpp" -#include "widgets/clock.hpp" +#include #include "glibmm/main.h" #include "sigc++/functors/mem_fun.h" -Bar::Bar(GdkMonitor* monitor, HyprlandService::Monitor* hyprlandMonitor) +Bar::Bar(GdkMonitor *monitor, HyprlandService &hyprlandService, int monitorId) + : m_hyprlandService(hyprlandService), m_monitorId(monitorId) { gtk_layer_init_for_window(this->gobj()); - if (monitor) { + if (monitor) + { gtk_layer_set_monitor(this->gobj(), monitor); } @@ -28,31 +29,49 @@ Bar::Bar(GdkMonitor* monitor, HyprlandService::Monitor* hyprlandMonitor) load_css(); setup_ui(); - this->clock.onUpdate(); + clock.onUpdate(); Glib::signal_timeout().connect( sigc::mem_fun( - this->clock, + clock, &Clock::onUpdate), 1000); } void Bar::setup_ui() { - Gtk::Box left_box{Gtk::Orientation::HORIZONTAL}; - Gtk::Box center_box{Gtk::Orientation::HORIZONTAL}; - Gtk::Box right_box{Gtk::Orientation::HORIZONTAL}; + main_box.set_hexpand(true); main_box.set_start_widget(left_box); main_box.set_center_widget(center_box); main_box.set_end_widget(right_box); - Gtk::Label labelLeft("labelLeft"); - Gtk::Label labelRight("labelRight"); + left_box.set_spacing(6); + left_box.set_margin_start(12); + left_box.set_margin_end(12); + left_box.set_valign(Gtk::Align::CENTER); + left_box.set_hexpand(false); - left_box.append(labelLeft); + center_box.set_spacing(6); + center_box.set_hexpand(true); + center_box.set_margin_top(2); + center_box.set_margin_bottom(2); + center_box.set_valign(Gtk::Align::CENTER); + center_box.set_halign(Gtk::Align::CENTER); + + right_box.set_spacing(6); + right_box.set_margin_start(12); + right_box.set_margin_end(12); + right_box.set_valign(Gtk::Align::CENTER); + right_box.set_hexpand(false); + + m_workspaceIndicator = Gtk::make_managed(m_hyprlandService, m_monitorId); + left_box.append(*m_workspaceIndicator); + + clock.set_name("clock-label"); + clock.set_halign(Gtk::Align::CENTER); + clock.set_valign(Gtk::Align::CENTER); center_box.append(clock); - right_box.append(labelRight); } void Bar::load_css() @@ -62,6 +81,11 @@ void Bar::load_css() css_provider->load_from_data(R"( window { background-color: #222; color: #fff; } #clock-label { font-weight: bold; font-family: monospace; } + .workspace-pill { background-color: rgba(255, 255, 255, 0.12); border-radius: 8px; padding: 2px 8px; margin-right: 6px; } + .workspace-pill:last-child { margin-right: 0; } + .workspace-pill-focused { background-color: #82e9de; color: #111; } + .workspace-pill-active { background-color: rgba(255, 255, 255, 0.25); } + .workspace-pill-urgent { background-color: #ff5555; color: #111; } )"); Gtk::StyleContext::add_provider_for_display( diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index 3bda581..d08d40a 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -2,38 +2,52 @@ #include #include -#include +#include #include -#include +#include #include #include #include #include +#include + #include "helpers/systemHelper.hpp" -HyprlandService::HyprlandService() {} +namespace +{ +const char *kMonitorCommand = "hyprctl monitors -j"; +const char *kWorkspaceCommand = "hyprctl workspaces -j"; + +bool is_workspace_event(const std::string &event) +{ + return event.find("workspace") != std::string::npos; +} +} + +HyprlandService::HyprlandService() = default; HyprlandService::~HyprlandService() { if (m_fd != -1) { close(m_fd); + m_fd = -1; } } -void HyprlandService::on_hyprland_event(std::string event, std::string data) +void HyprlandService::on_hyprland_event(std::string event, std::string /*data*/) { - if (event == "workspacev2") + if (is_workspace_event(event) || event == "focusedmon" || event == "monitoradded" || event == "monitorremoved") { - std::cout << event << " " << data << std::endl; + refresh_monitors(); + refresh_workspaces(); } } void HyprlandService::start() { - // setup socket - std::string socket_path = get_socket_path(); + const std::string socket_path = get_socket_path(); if (socket_path.empty()) { return; @@ -49,9 +63,9 @@ void HyprlandService::start() struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1); + std::strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1); - if (connect(m_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) + if (connect(m_fd, reinterpret_cast(&addr), sizeof(addr)) == -1) { std::cerr << "[Hyprland] Failed to connect to " << socket_path << std::endl; close(m_fd); @@ -66,63 +80,13 @@ void HyprlandService::start() m_fd, Glib::IOCondition::IO_IN | Glib::IOCondition::IO_HUP | Glib::IOCondition::IO_ERR); - // setup monitors - std::string hyprctlMonitorsOutput = SystemHelper::get_command_output("hyprctl monitors -j"); - auto monitorsJson = nlohmann::json::parse(hyprctlMonitorsOutput); - - if (monitorsJson.is_array()) - { - for (auto &monitorJson : monitorsJson) - { - Monitor* monitor = new Monitor(); - - monitor->id = monitorJson["id"]; - monitor->name = monitorJson["name"]; - monitor->x = monitorJson["x"]; - monitor->y = monitorJson["y"]; - monitor->focusedWorkspaceId = monitorJson["activeWorkspace"]["id"]; - - this->monitors.insert({monitor->id, monitor}); - } - } - - int numWorkspaces = 6; - int numMonitors = 2; - - for (int i = 0; i < numWorkspaces * numMonitors; i++) - { - WorkspaceState tempState; - - tempState.active = false; - tempState.focused = false; - tempState.urgent = false; - tempState.id = i; - - this->monitors[i / numWorkspaces]->workspaceStates.insert({i % numWorkspaces, tempState}); - } - - std::string hyprctlWorkspacesOutput = SystemHelper::get_command_output("hyprctl workspaces -j"); - auto workspacesJson = nlohmann::json::parse(hyprctlWorkspacesOutput); - - if (workspacesJson.is_array()) - { - for (auto &workspaceJson : workspacesJson) - { - int currentId = workspaceJson["id"]; - int monitorId = workspaceJson["monitorID"]; - Monitor* monitor = this->monitors[monitorId]; - WorkspaceState &workspaceState = monitor->workspaceStates.at((currentId - 1) % numWorkspaces); - - int focusedWorkspaceId = monitor[monitorId].focusedWorkspaceId; - workspaceState.focused = currentId == focusedWorkspaceId; - workspaceState.active = true; - } - } + refresh_monitors(); + refresh_workspaces(); } bool HyprlandService::on_socket_read(Glib::IOCondition condition) { - 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) { @@ -133,7 +97,7 @@ bool HyprlandService::on_socket_read(Glib::IOCondition condition) } char buffer[4096]; - ssize_t bytes_read = read(m_fd, buffer, sizeof(buffer) - 1); + const ssize_t bytes_read = read(m_fd, buffer, sizeof(buffer) - 1); if (bytes_read > 0) { @@ -144,7 +108,7 @@ bool HyprlandService::on_socket_read(Glib::IOCondition condition) while ((pos = m_buffer.find('\n')) != std::string::npos) { - std::string line = m_buffer.substr(0, pos); + const std::string line = m_buffer.substr(0, pos); parse_message(line); m_buffer.erase(0, pos + 1); } @@ -155,11 +119,11 @@ bool HyprlandService::on_socket_read(Glib::IOCondition condition) void HyprlandService::parse_message(const std::string &line) { - size_t split = line.find(">>"); + const size_t split = line.find(">>"); if (split != std::string::npos) { - std::string event_name = line.substr(0, split); - std::string event_data = line.substr(split + 2); + const std::string event_name = line.substr(0, split); + const std::string event_data = line.substr(split + 2); socketEventSignal.emit(event_name, event_data); } @@ -179,15 +143,206 @@ std::string HyprlandService::get_socket_path() return std::string(runtime) + "/hypr/" + sig + "/.socket2.sock"; } - -HyprlandService::Monitor* HyprlandService::getMonitorById(const int &id) +void HyprlandService::refresh_monitors() { - if (this->monitors.find(id) != this->monitors.end()) + std::string output; + + try { - return this->monitors.at(id); + output = SystemHelper::get_command_output(kMonitorCommand); } - else + catch (const std::exception &ex) + { + std::cerr << "[Hyprland] Failed to query monitors: " << ex.what() << std::endl; + return; + } + + auto monitorsJson = nlohmann::json::parse(output, nullptr, false); + if (!monitorsJson.is_array()) + { + std::cerr << "[Hyprland] Unexpected monitor payload" << std::endl; + return; + } + + std::map updated; + + for (const auto &monitorJson : monitorsJson) + { + if (!monitorJson.is_object()) + { + continue; + } + + Monitor monitor; + monitor.id = monitorJson.value("id", -1); + monitor.name = monitorJson.value("name", ""); + 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); + } + + if (monitor.id >= 0) + { + updated.emplace(monitor.id, std::move(monitor)); + } + } + + m_monitors.swap(updated); + monitorStateChanged.emit(); +} + +void HyprlandService::refresh_workspaces() +{ + if (m_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; + } + + auto workspacesJson = nlohmann::json::parse(output, nullptr, false); + if (!workspacesJson.is_array()) + { + std::cerr << "[Hyprland] Unexpected workspace payload" << std::endl; + return; + } + + for (auto &pair : m_monitors) + { + auto &monitor = pair.second; + monitor.workspaceStates.clear(); + + int focusedSlot = -1; + if (monitor.focusedWorkspaceId > 0) + { + focusedSlot = ((monitor.focusedWorkspaceId - 1) % HyprlandService::kWorkspaceSlotCount) + 1; + } + + for (int slot = 1; slot <= HyprlandService::kWorkspaceSlotCount; ++slot) + { + WorkspaceState state; + state.id = slot; + state.hyprId = -1; + state.label = std::to_string(slot); + state.focused = (slot == focusedSlot); + state.active = state.focused; + state.urgent = false; + monitor.workspaceStates.emplace(slot, state); + } + } + + for (const auto &workspaceJson : workspacesJson) + { + if (!workspaceJson.is_object()) + { + continue; + } + + const int monitorId = workspaceJson.value("monitorID", -1); + const int workspaceId = workspaceJson.value("id", -1); + + auto monitorIt = m_monitors.find(monitorId); + if (monitorIt == m_monitors.end() || workspaceId < 0) + { + continue; + } + + auto &monitor = monitorIt->second; + const int slot = ((workspaceId - 1) % HyprlandService::kWorkspaceSlotCount) + 1; + auto &workspaceState = monitor.workspaceStates[slot]; + + workspaceState.id = slot; + workspaceState.hyprId = workspaceId; + workspaceState.focused = (monitor.focusedWorkspaceId == workspaceId); + workspaceState.active = workspaceState.focused || workspaceJson.value("windows", 0) > 0; + workspaceState.urgent = false; + + std::string labelCandidate; + if (workspaceJson.contains("name") && workspaceJson["name"].is_string()) + { + labelCandidate = workspaceJson["name"].get(); + } + + if (labelCandidate.empty() || labelCandidate == std::to_string(workspaceId)) + { + workspaceState.label = std::to_string(slot); + } + else + { + workspaceState.label = labelCandidate; + } + + if (workspaceJson.contains("urgent") && workspaceJson["urgent"].is_boolean()) + { + workspaceState.urgent = workspaceJson["urgent"].get(); + } + else if (workspaceJson.contains("hasurgent") && workspaceJson["hasurgent"].is_boolean()) + { + workspaceState.urgent = workspaceJson["hasurgent"].get(); + } + } + + for (const auto &pair : m_monitors) + { + workspaceStateChanged.emit(pair.first); + } +} + +HyprlandService::Monitor *HyprlandService::getMonitorById(int id) +{ + auto it = m_monitors.find(id); + if (it == m_monitors.end()) { throw std::runtime_error("Monitor with ID " + std::to_string(id) + " not found."); } + + return &it->second; +} + +const HyprlandService::Monitor *HyprlandService::getMonitorById(int id) const +{ + auto it = m_monitors.find(id); + if (it == m_monitors.end()) + { + throw std::runtime_error("Monitor with ID " + std::to_string(id) + " not found."); + } + + return &it->second; +} + +HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) +{ + if (index >= m_monitors.size()) + { + throw std::runtime_error("Monitor index out of bounds: " + std::to_string(index)); + } + + auto it = m_monitors.begin(); + std::advance(it, static_cast(index)); + return &it->second; +} + +const HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) const +{ + if (index >= m_monitors.size()) + { + throw std::runtime_error("Monitor index out of bounds: " + std::to_string(index)); + } + + auto it = m_monitors.begin(); + std::advance(it, static_cast(index)); + return &it->second; } \ No newline at end of file diff --git a/src/widgets/workspaceIndicator.cpp b/src/widgets/workspaceIndicator.cpp new file mode 100644 index 0000000..e05dc80 --- /dev/null +++ b/src/widgets/workspaceIndicator.cpp @@ -0,0 +1,114 @@ +#include "widgets/workspaceIndicator.hpp" + +#include +#include +#include + +WorkspaceIndicator::WorkspaceIndicator(HyprlandService &service, int monitorId) + : Gtk::Box(Gtk::Orientation::HORIZONTAL), m_service(service), m_monitorId(monitorId) +{ + set_spacing(6); + set_margin_top(2); + set_margin_bottom(2); + + m_workspaceConnection = m_service.workspaceStateChanged.connect( + sigc::mem_fun(*this, &WorkspaceIndicator::on_workspace_update)); + m_monitorConnection = m_service.monitorStateChanged.connect( + sigc::mem_fun(*this, &WorkspaceIndicator::on_monitor_update)); + + rebuild(); +} + +WorkspaceIndicator::~WorkspaceIndicator() +{ + if (m_workspaceConnection.connected()) + { + m_workspaceConnection.disconnect(); + } + + if (m_monitorConnection.connected()) + { + m_monitorConnection.disconnect(); + } +} + +void WorkspaceIndicator::on_workspace_update(int monitorId) +{ + if (monitorId != m_monitorId && monitorId != -1) + { + return; + } + + rebuild(); +} + +void WorkspaceIndicator::on_monitor_update() +{ + rebuild(); +} + +void WorkspaceIndicator::rebuild() +{ + clear_children(); + + HyprlandService::Monitor *monitor = nullptr; + try + { + monitor = m_service.getMonitorById(m_monitorId); + } + catch (const std::exception &) + { + return; + } + + if (monitor == nullptr) + { + return; + } + + for (int workspaceId = 1; workspaceId <= HyprlandService::kWorkspaceSlotCount; ++workspaceId) + { + const HyprlandService::WorkspaceState *state = nullptr; + auto it = monitor->workspaceStates.find(workspaceId); + if (it != monitor->workspaceStates.end()) + { + state = &it->second; + } + + const std::string display = (state && !state->label.empty()) ? state->label : std::to_string(workspaceId); + + auto label = Gtk::make_managed(display); + label->add_css_class("workspace-pill"); + + if (state != nullptr) + { + if (state->focused) + { + label->add_css_class("workspace-pill-focused"); + } + else if (state->active) + { + label->add_css_class("workspace-pill-active"); + } + + if (state->urgent) + { + 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; + } +}