diff --git a/include/services/hyprland.hpp b/include/services/hyprland.hpp index 41268f7..11d2eab 100644 --- a/include/services/hyprland.hpp +++ b/include/services/hyprland.hpp @@ -48,6 +48,11 @@ class HyprlandService const Monitor *getMonitorById(int id) const; Monitor *getMonitorByIndex(std::size_t index); const Monitor *getMonitorByIndex(std::size_t index) const; + // Switch to a workspace slot on a given monitor. If the workspace has an + // associated Hyprland workspace id (hyprId >= 0) that id will be used. + // Otherwise the slot and monitor name will be used to request creation + // / activation via `hyprctl`. + void switchToWorkspace(int monitorId, int slot); private: int m_fd = -1; diff --git a/src/bar/bar.cpp b/src/bar/bar.cpp index 587a2ab..47f1835 100644 --- a/src/bar/bar.cpp +++ b/src/bar/bar.cpp @@ -47,19 +47,13 @@ void Bar::setup_ui() main_box.set_end_widget(right_box); main_box.set_valign(Gtk::Align::CENTER); - left_box.set_margin_start(12); - left_box.set_margin_end(12); 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_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_margin_start(12); - right_box.set_margin_end(12); right_box.set_valign(Gtk::Align::CENTER); m_workspaceIndicator = Gtk::make_managed(m_hyprlandService, m_monitorId); @@ -86,6 +80,9 @@ void Bar::load_css() .workspace-pill-focused { background-color: rgba(255, 255, 255, 0.18); } .workspace-pill-active { background-color: rgba(255, 255, 255, 0.25); } .workspace-pill-urgent { background-color: #ff5555; color: #111; } + /* Hover effect: slightly brighten background and show pointer */ + .workspace-pill:hover { background-color: rgba(255, 255, 255, 0.20); } + .workspace-pill:hover { cursor: pointer; } // TODO: cursors has to be set differently in GTK4? .tray-icon { padding: 0; margin: 0; border: none; background: transparent; min-width: 0; min-height: 0; } )"); diff --git a/src/services/hyprland.cpp b/src/services/hyprland.cpp index d08d40a..9c78b4e 100644 --- a/src/services/hyprland.cpp +++ b/src/services/hyprland.cpp @@ -335,6 +335,54 @@ HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) return &it->second; } +void HyprlandService::switchToWorkspace(int monitorId, int slot) +{ + auto it = m_monitors.find(monitorId); + if (it == m_monitors.end()) + { + std::cerr << "[Hyprland] switchToWorkspace: monitor " << monitorId << " not found\n"; + return; + } + + const auto &monitor = it->second; + auto wsIt = monitor.workspaceStates.find(slot); + std::string cmd; + + if (wsIt != monitor.workspaceStates.end() && wsIt->second.hyprId >= 0) + { + // Use the Hyprland workspace id if available + cmd = "hyprctl dispatch workspace " + std::to_string(wsIt->second.hyprId); + } + else + { + // Fallback: ask Hyprland to switch/create a workspace on the monitor + // by using the slot number and the monitor name. Syntax: ":" + // Example: hyprctl dispatch workspace 2:DP-1 + // This may vary by Hyprland version; if it doesn't work on your system + // adjust the command accordingly. + std::string monName = monitor.name; + if (monName.empty()) + { + // As a last resort, just try the slot number globally + cmd = "hyprctl dispatch workspace " + std::to_string(slot); + } + else + { + // Quote monitor name in case it contains spaces + cmd = "hyprctl dispatch workspace " + std::to_string(slot) + ":\"" + monName + "\""; + } + } + + try + { + (void)SystemHelper::get_command_output(cmd.c_str()); + } + catch (const std::exception &ex) + { + std::cerr << "[Hyprland] Failed to dispatch workspace command: " << ex.what() << " cmd=" << cmd << std::endl; + } +} + const HyprlandService::Monitor *HyprlandService::getMonitorByIndex(std::size_t index) const { if (index >= m_monitors.size()) diff --git a/src/widgets/tray.cpp b/src/widgets/tray.cpp index 86a0e88..e43f566 100644 --- a/src/widgets/tray.cpp +++ b/src/widgets/tray.cpp @@ -13,11 +13,9 @@ TrayIconWidget::TrayIconWidget(TrayService &service, std::string id) set_focusable(false); set_valign(Gtk::Align::CENTER); set_halign(Gtk::Align::CENTER); - add_css_class("tray-icon"); m_picture.set_halign(Gtk::Align::CENTER); m_picture.set_valign(Gtk::Align::CENTER); - m_picture.set_content_fit(Gtk::ContentFit::CONTAIN); m_picture.set_can_shrink(true); m_picture.set_size_request(20, 20); diff --git a/src/widgets/workspaceIndicator.cpp b/src/widgets/workspaceIndicator.cpp index 189b106..d7715ed 100644 --- a/src/widgets/workspaceIndicator.cpp +++ b/src/widgets/workspaceIndicator.cpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include WorkspaceIndicator::WorkspaceIndicator(HyprlandService &service, int monitorId) @@ -79,6 +81,23 @@ void WorkspaceIndicator::rebuild() auto label = Gtk::make_managed(display); label->add_css_class("workspace-pill"); + // Make the label clickable using a gesture click (primary button) + auto gesture = Gtk::GestureClick::create(); + gesture->set_button(GDK_BUTTON_PRIMARY); + // Use a lambda to capture the workspace slot id + gesture->signal_released().connect([this, workspaceId](int /*n_press*/, double /*x*/, double /*y*/) + { + try + { + m_service.switchToWorkspace(m_monitorId, workspaceId); + } + catch (const std::exception &ex) + { + std::cerr << "[WorkspaceIndicator] switchToWorkspace failed: " << ex.what() << std::endl; + } + }); + label->add_controller(gesture); + if (state != nullptr) { if (state->focused)