From ce11e56f0d80a80681fb4d0c6081fa6225bd16ae Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Mon, 16 Feb 2026 20:28:20 -0700 Subject: [PATCH 1/9] panel: Add workspace-switcher widget --- data/css/default.css | 22 ++ metadata/panel.xml | 7 + src/panel/meson.build | 1 + src/panel/panel.cpp | 15 + src/panel/widgets/workspace-switcher.cpp | 396 +++++++++++++++++++++++ src/panel/widgets/workspace-switcher.hpp | 66 ++++ 6 files changed, 507 insertions(+) create mode 100644 src/panel/widgets/workspace-switcher.cpp create mode 100644 src/panel/widgets/workspace-switcher.hpp diff --git a/data/css/default.css b/data/css/default.css index eed407636..9c46dcb07 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -72,6 +72,28 @@ border-radius: 3px; color: #41A6B5; } + +.wf-panel .workspace-switcher { + background-color: #222222FF; + padding-right: 1px; + padding-top: 1px; + padding-bottom: 1px; +} + +.wf-panel .workspace-switcher .active { + background-color: #337755FF; + margin-left: 1px; +} + +.wf-panel .workspace-switcher .inactive { + background-color: #0077AAFF; + margin-left: 1px; +} + +.wf-panel .workspace-switcher .workspace .view { + background-color: #0000AA77; + border: 1px solid #222 +} .excellent { color: #00ff00; } diff --git a/metadata/panel.xml b/metadata/panel.xml index b59900e7b..49f036546 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -467,5 +467,12 @@ Set to -1 to only run it by clicking the button. 1 + + <_short>Workspace Switcher + + diff --git a/src/panel/meson.build b/src/panel/meson.build index bff4e6b3e..4097c1d37 100644 --- a/src/panel/meson.build +++ b/src/panel/meson.build @@ -20,6 +20,7 @@ widget_sources = [ 'widgets/tray/item.cpp', 'widgets/tray/host.cpp', 'widgets/tray/dbusmenu.cpp', + 'widgets/workspace-switcher.cpp', ] deps = [ diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index e31615c6c..5d38c21d3 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -23,6 +23,7 @@ #include "widgets/network.hpp" #include "widgets/spacing.hpp" #include "widgets/separator.hpp" +#include "widgets/workspace-switcher.hpp" #ifdef HAVE_PULSE #include "widgets/volume.hpp" #endif @@ -211,6 +212,20 @@ class WayfirePanel::impl } } + if (name == "workspace-switcher") + { + if (WayfireIPC::get_instance()->connected) + { + return Widget(new WayfireWorkspaceSwitcher(output)); + } else + { + std::cerr << + "Wayfire IPC not connected, which is required to load workspace-switcher widget." << + std::endl; + return nullptr; + } + } + if (auto pixel = widget_with_value(name, "spacing")) { return Widget(new WayfireSpacing(*pixel)); diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp new file mode 100644 index 000000000..fcd91362d --- /dev/null +++ b/src/panel/widgets/workspace-switcher.cpp @@ -0,0 +1,396 @@ +#include +#include +#include +#include "workspace-switcher.hpp" + +void WayfireWorkspaceSwitcher::init(Gtk::Box *container) +{ + box.add_css_class("workspace-switcher"); + box.add_css_class("flat"); + + ipc_client->subscribe(this, {"view-mapped"}); + ipc_client->subscribe(this, {"view-unmapped"}); + ipc_client->subscribe(this, {"view-set-output"}); + ipc_client->subscribe(this, {"view-geometry-changed"}); + ipc_client->subscribe(this, {"output-layout-changed"}); + ipc_client->subscribe(this, {"wset-workspace-changed"}); + workspace_switcher_target_height.set_callback([=] () + { + get_wsets(); + }); + get_wsets(); + + container->append(box); +} + +void WayfireWorkspaceSwitcher::get_wsets() +{ + ipc_client->send("{\"method\":\"window-rules/list-wsets\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting wsets list for workspace-switcher widget!" << std::endl; + return; + } + + process_workspaces(data); + }); +} + +void WayfireWorkspaceSwitcher::clear_box() +{ + for (auto child : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)child; + for (auto widget : child->get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + ws->remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + } + + box.remove(*ws); + } +} + +std::pair WayfireWorkspaceSwitcher::get_workspace(WayfireWorkspaceBox *ws, + WayfireWorkspaceWindow *w) +{ + std::pair workspace; + double scaled_output_width = ws->get_scaled_width(); + double scaled_output_height = workspace_switcher_target_height; + workspace.first = std::floor((w->x + (w->w / 2)) / scaled_output_width) + this->current_ws_x; + workspace.second = std::floor((w->y + (w->h / 2)) / scaled_output_height) + this->current_ws_y; + return workspace; +} + +bool WayfireWorkspaceSwitcher::on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation) +{ + if (auto w = static_cast(widget)) + { + allocation.set_x(w->x + (this->current_ws_x - w->x_index) * w->ws->get_width()); + allocation.set_y(w->y); + allocation.set_width(w->w); + allocation.set_height(w->h); + return true; + } + + return false; +} + +void WayfireWorkspaceBox::on_workspace_clicked(int count, double x, double y) +{ + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } +} + +int WayfireWorkspaceBox::get_scaled_width() +{ + return this->switcher->workspace_switcher_target_height * + (this->output_width / float(this->output_height)); +} + +bool WayfireWorkspaceBox::on_workspace_scrolled(double x, double y) +{ + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if (y > 0) + { + if (ws->y_index < ws->switcher->grid_height - 1) + { + ws->y_index++; + } + } else + { + if (ws->y_index > 0) + { + ws->y_index--; + } + } + } + + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } + + return false; +} + +void WayfireWorkspaceSwitcher::render_workspace(wf::json_t workspace, int j, int output_id, int output_width, + int output_height) +{ + auto ws = Gtk::make_managed(this); + ws->x_index = j; + ws->y_index = workspace["workspace"]["y"].as_int(); + ws->output_id = output_id; + ws->output_width = output_width; + ws->output_height = output_height; + ws->add_css_class("workspace"); + if (workspace["workspace"]["x"].as_int() == j) + { + ws->add_css_class("active"); + this->current_ws_x = j; + this->current_ws_y = ws->y_index; + } else + { + ws->add_css_class("inactive"); + } + + ws->signal_get_child_position().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_get_child_position), false); + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_released().connect(sigc::mem_fun(*ws, &WayfireWorkspaceBox::on_workspace_clicked)); + auto scroll_controller = Gtk::EventControllerScroll::create(); + scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL); + scroll_controller->signal_scroll().connect(sigc::mem_fun(*ws, + &WayfireWorkspaceBox::on_workspace_scrolled), + false); + ws->set_hexpand(false); + ws->set_vexpand(false); + ws->set_size_request(ws->get_scaled_width(), workspace_switcher_target_height); + ws->add_controller(click_gesture); + ws->add_controller(scroll_controller); + box.append(*ws); + if (j == this->grid_width - 1) + { + ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting views list for workspace-switcher widget!" << std::endl; + return; + } + + render_views(data); + }); + } +} + +void WayfireWorkspaceSwitcher::process_workspaces(wf::json_t workspace_data) +{ + size_t i = 0; + + this->grid_width = workspace_data[i]["workspace"]["grid_width"].as_int(); + this->grid_height = workspace_data[i]["workspace"]["grid_height"].as_int(); + + for (i = 0; i < workspace_data.size(); i++) + { + wf::json_t output_info_request; + output_info_request["method"] = "window-rules/output-info"; + wf::json_t output_id; + output_id["id"] = workspace_data[i]["output-id"].as_int(); + output_info_request["data"] = output_id; + ipc_client->send(output_info_request.serialize(), [=] (wf::json_t output_data) + { + if (output_data.serialize().find("error") != std::string::npos) + { + std::cerr << output_data.serialize() << std::endl; + std::cerr << "Error getting output information!" << std::endl; + return; + } + + auto output_id = output_data["id"].as_int(); + auto output_width = output_data["geometry"]["width"].as_int(); + auto output_height = output_data["geometry"]["height"].as_int(); + if (this->output_name == output_data["name"].as_string()) + { + clear_box(); + for (int j = 0; j < this->grid_width; j++) + { + render_workspace(workspace_data[i], j, output_id, output_width, output_height); + } + } + }); + } +} + +void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) +{ + if (view_data["type"].as_string() != "toplevel") + { + return; + } + + if (view_data["output-name"].as_string() != this->output_name) + { + return; + } + + auto v = Gtk::make_managed(); + v->add_css_class("view"); + v->add_css_class(view_data["app-id"].as_string()); + v->id = view_data["id"].as_int(); + v->output_id = view_data["output-id"].as_int(); + auto x = view_data["geometry"]["x"].as_int(); + auto y = view_data["geometry"]["y"].as_int(); + auto w = view_data["geometry"]["width"].as_int(); + auto h = view_data["geometry"]["height"].as_int(); + for (auto widget : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + for (auto child : widget->get_children()) + { + WayfireWorkspaceWindow *window = (WayfireWorkspaceWindow*)child; + if (window->id == v->id) + { + return; + } + } + + double width = ws->get_scaled_width(); + double height = workspace_switcher_target_height; + + v->ws = ws; + v->x = x * (width / float(ws->output_width)); + v->y = y * (height / float(ws->output_height)); + v->w = (w / float(ws->output_width)) * width; + v->h = (h / float(ws->output_height)) * height; + std::pair workspace; + workspace = get_workspace(ws, v); + v->x_index = workspace.first; + v->y_index = workspace.second; + if ((v->x_index == ws->x_index) && (v->y_index == ws->y_index)) + { + v->set_can_target(false); + // add to workspace box + ws->add_overlay(*v); + windows.push_back(v); + return; + } + } +} + +void WayfireWorkspaceSwitcher::remove_view(wf::json_t view_data) +{ + for (auto w : this->windows) + { + if (w->id == view_data["id"].as_int()) + { + for (auto widget : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if (w->get_parent() == ws) + { + ws->remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + return; + } + } + } + } +} + +void WayfireWorkspaceSwitcher::render_views(wf::json_t views_data) +{ + for (size_t i = 0; i < views_data.size(); i++) + { + add_view(views_data[i]); + } +} + +void WayfireWorkspaceSwitcher::on_event(wf::json_t data) +{ + if (data["event"].as_string() == "view-geometry-changed") + { + for (auto child : box.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)child; + for (auto widget : child->get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + ws->remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + } + } + } + + add_view(data["view"]); + } else if (data["event"].as_string() == "view-mapped") + { + add_view(data["view"]); + } else if (data["event"].as_string() == "view-unmapped") + { + remove_view(data["view"]); + } else if (data["event"].as_string() == "view-set-output") + { + add_view(data["view"]); + } else if ((data["event"].as_string() == "output-layout-changed") || + (data["event"].as_string() == "wset-workspace-changed")) + { + get_wsets(); + } +} + +WayfireWorkspaceSwitcher::WayfireWorkspaceSwitcher(WayfireOutput *output) +{ + this->output_name = output->monitor->get_connector(); + ipc_client = WayfireIPC::get_instance()->create_client(); + box.set_vexpand(false); + box.set_valign(Gtk::Align::CENTER); +} + +WayfireWorkspaceSwitcher::~WayfireWorkspaceSwitcher() +{ + ipc_client->unsubscribe(this); + clear_box(); +} diff --git a/src/panel/widgets/workspace-switcher.hpp b/src/panel/widgets/workspace-switcher.hpp new file mode 100644 index 000000000..5fad9a4bd --- /dev/null +++ b/src/panel/widgets/workspace-switcher.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include "../widget.hpp" +#include "wf-ipc.hpp" + +class WayfireWorkspaceBox; +class WayfireWorkspaceWindow : public Gtk::Widget +{ + public: + int x = 0, y = 0, w = 10, h = 10; + int x_index, y_index; + int id, output_id; + WayfireWorkspaceBox *ws; + WayfireWorkspaceWindow() + {} + ~WayfireWorkspaceWindow() override + {} +}; + +class WayfireWorkspaceSwitcher : public WayfireWidget, public IIPCSubscriber +{ + std::string output_name; + void on_event(wf::json_t data) override; + void render_workspace(wf::json_t workspace_data, int j, int output_id, int output_width, + int output_height); + void process_workspaces(wf::json_t workspace_data); + void render_views(wf::json_t views_data); + void add_view(wf::json_t view_data); + void remove_view(wf::json_t view_data); + void clear_box(); + void get_wsets(); + bool on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation); + + public: + Gtk::Box box; + void init(Gtk::Box *container) override; + WayfireWorkspaceSwitcher(WayfireOutput *output); + ~WayfireWorkspaceSwitcher(); + int grid_width, grid_height; + std::shared_ptr ipc_client; + std::pair get_workspace(WayfireWorkspaceBox *ws, WayfireWorkspaceWindow *w); + int current_ws_x, current_ws_y; + std::vector windows; + WfOption workspace_switcher_target_height{"panel/workspace_switcher_target_height"}; +}; + +class WayfireWorkspaceBox : public Gtk::Overlay +{ + WayfireWorkspaceSwitcher *switcher; + + public: + int x_index, y_index; + int output_id, output_width, output_height; + int get_scaled_width(); + WayfireWorkspaceBox(WayfireWorkspaceSwitcher *switcher) + { + this->switcher = switcher; + } + + ~WayfireWorkspaceBox() override + {} + void on_workspace_clicked(int count, double x, double y); + bool on_workspace_scrolled(double x, double y); +}; From 70821a03f61b3935eecb191d0d41a3295d6f3303 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 09:39:24 -0700 Subject: [PATCH 2/9] Merge popover-pager concept Thanks to Hue for the idea. --- metadata/panel.xml | 16 ++ src/panel/widgets/workspace-switcher.cpp | 352 +++++++++++++++++++++-- src/panel/widgets/workspace-switcher.hpp | 20 ++ 3 files changed, 372 insertions(+), 16 deletions(-) diff --git a/metadata/panel.xml b/metadata/panel.xml index 49f036546..0db15b88c 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -469,10 +469,26 @@ Set to -1 to only run it by clicking the button. <_short>Workspace Switcher + + diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index fcd91362d..48a4b9b2d 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -18,9 +18,32 @@ void WayfireWorkspaceSwitcher::init(Gtk::Box *container) { get_wsets(); }); - get_wsets(); + auto mode_cb = ([=] () + { + clear_switcher_box(); + if (std::string(workspace_switcher_mode) == "classic") + { + switcher_box.append(box); + } else // "popover" + { + switcher_box.append(grid); + } + get_wsets(); + }); + workspace_switcher_mode.set_callback(mode_cb); + workspace_switcher_render_views.set_callback([=] () + { + get_wsets(); + }); - container->append(box); + grid.add_css_class("workspace-switcher"); + auto click_gesture = Gtk::GestureClick::create(); + click_gesture->set_button(0); + click_gesture->signal_released().connect(sigc::mem_fun(*this, &WayfireWorkspaceSwitcher::on_grid_clicked)); + grid.add_controller(click_gesture); + + container->append(switcher_box); + mode_cb(); } void WayfireWorkspaceSwitcher::get_wsets() @@ -34,24 +57,37 @@ void WayfireWorkspaceSwitcher::get_wsets() return; } - process_workspaces(data); + if (std::string(workspace_switcher_mode) == "classic") + { + process_workspaces(data); + } else // "popover" + { + popover_process_workspaces(data); + } }); } +void WayfireWorkspaceSwitcher::clear_switcher_box() +{ + for (auto child : switcher_box.get_children()) + { + switcher_box.remove(*child); + } +} + void WayfireWorkspaceSwitcher::clear_box() { for (auto child : box.get_children()) { - WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)child; - for (auto widget : child->get_children()) - { - WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; - ws->remove_overlay(*w); - auto elem = std::remove(windows.begin(), windows.end(), w); - windows.erase(elem, windows.end()); - } - - box.remove(*ws); + box.remove(*child); + } + for (auto child : grid.get_children()) + { + grid.remove(*child); + } + for (auto child : popover_grid.get_children()) + { + popover_grid.remove(*child); } } @@ -66,6 +102,16 @@ std::pair WayfireWorkspaceSwitcher::get_workspace(WayfireWorkspaceBox return workspace; } +std::pair WayfireWorkspaceSwitcher::popover_get_workspace(WayfireWorkspaceWindow *w) +{ + std::pair workspace; + double scaled_output_width = this->get_scaled_width(); + double scaled_output_height = workspace_switcher_target_height; + workspace.first = std::floor((w->x + (w->w / 2)) / scaled_output_width) + this->current_ws_x; + workspace.second = std::floor((w->y + (w->h / 2)) / scaled_output_height) + this->current_ws_y; + return workspace; +} + bool WayfireWorkspaceSwitcher::on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation) { if (auto w = static_cast(widget)) @@ -80,6 +126,57 @@ bool WayfireWorkspaceSwitcher::on_get_child_position(Gtk::Widget *widget, Gdk::R return false; } +bool WayfireWorkspaceSwitcher::on_popover_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation) +{ + if (auto w = static_cast(widget)) + { + allocation.set_x(w->x + this->current_ws_x * this->get_scaled_width()); + allocation.set_y(w->y + this->current_ws_y * this->workspace_switcher_target_height); + allocation.set_width(w->w); + allocation.set_height(w->h); + return true; + } + + return false; +} + +void WayfireWorkspaceBox::on_popover_grid_clicked(int count, double x, double y) +{ + wf::json_t workspace_switch_request; + workspace_switch_request["method"] = "vswitch/set-workspace"; + wf::json_t workspace; + workspace["x"] = this->switcher->current_ws_x = this->x_index; + workspace["y"] = this->switcher->current_ws_y = this->y_index; + workspace["output-id"] = this->output_id; + workspace_switch_request["data"] = workspace; + this->switcher->ipc_client->send(workspace_switch_request.serialize(), [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error switching workspaces. Is vswitch plugin enabled?" << std::endl; + } + }); + for (auto widget : this->switcher->grid.get_children()) + { + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)widget; + if ((ws->x_index == this->x_index) && (ws->y_index == this->y_index)) + { + ws->remove_css_class("inactive"); + ws->add_css_class("active"); + } else + { + ws->add_css_class("inactive"); + ws->remove_css_class("active"); + } + } +} + +void WayfireWorkspaceSwitcher::on_grid_clicked(int count, double x, double y) +{ + this->popover->popup(); +} + void WayfireWorkspaceBox::on_workspace_clicked(int count, double x, double y) { wf::json_t workspace_switch_request; @@ -112,6 +209,12 @@ void WayfireWorkspaceBox::on_workspace_clicked(int count, double x, double y) } } +double WayfireWorkspaceSwitcher::get_scaled_width() +{ + return this->workspace_switcher_target_height * + (this->output_width / float(this->output_height)); +} + int WayfireWorkspaceBox::get_scaled_width() { return this->switcher->workspace_switcher_target_height * @@ -206,7 +309,7 @@ void WayfireWorkspaceSwitcher::render_workspace(wf::json_t workspace, int j, int ws->add_controller(click_gesture); ws->add_controller(scroll_controller); box.append(*ws); - if (j == this->grid_width - 1) + if (workspace_switcher_render_views && j == this->grid_width - 1) { ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) { @@ -260,6 +363,105 @@ void WayfireWorkspaceSwitcher::process_workspaces(wf::json_t workspace_data) } } +void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_data) +{ + size_t i = 0; + + this->grid_width = workspace_data[i]["workspace"]["grid_width"].as_int(); + this->grid_height = workspace_data[i]["workspace"]["grid_height"].as_int(); + + for (i = 0; i < workspace_data.size(); i++) + { + wf::json_t output_info_request; + output_info_request["method"] = "window-rules/output-info"; + wf::json_t output_id; + output_id["id"] = workspace_data[i]["output-id"].as_int(); + output_info_request["data"] = output_id; + ipc_client->send(output_info_request.serialize(), [=] (wf::json_t output_data) + { + if (output_data.serialize().find("error") != std::string::npos) + { + std::cerr << output_data.serialize() << std::endl; + std::cerr << "Error getting output information!" << std::endl; + return; + } + + this->output_width = output_data["geometry"]["width"].as_int(); + this->output_height = output_data["geometry"]["height"].as_int(); + if (this->output_name == output_data["name"].as_string()) + { + clear_box(); + popover = Gtk::make_managed(); + popover->set_parent(grid); + popover->set_child(overlay); + overlay.set_child(popover_grid); + overlay.signal_get_child_position().connect(sigc::mem_fun(*this, &WayfireWorkspaceSwitcher::on_popover_get_child_position), false); + for (int j = 0; j < this->grid_height; j++) + { + for (int k = 0; k < this->grid_width; k++) + { + auto ws = Gtk::make_managed(this); + ws->output_id = output_data["id"].as_int(); + ws->set_can_target(false); + auto ws_width = this->get_scaled_width() / this->grid_width; + auto ws_height = this->workspace_switcher_target_height / this->grid_height; + ws->set_size_request(ws_width, ws_height); + ws->add_css_class("workspace"); + if (workspace_data[i]["workspace"]["x"].as_int() == k && + workspace_data[i]["workspace"]["y"].as_int() == j) + { + ws->add_css_class("active"); + this->current_ws_x = k; + this->current_ws_y = j; + } else + { + ws->add_css_class("inactive"); + } + ws->x_index = k; + ws->y_index = j; + grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); + + ws = Gtk::make_managed(this); + ws->output_id = output_data["id"].as_int(); + ws->set_size_request(this->get_scaled_width(), this->workspace_switcher_target_height); + ws->add_css_class("workspace"); + if (workspace_data[i]["workspace"]["x"].as_int() == k && + workspace_data[i]["workspace"]["y"].as_int() == j) + { + ws->add_css_class("active"); + this->current_ws_x = k; + this->current_ws_y = j; + } else + { + ws->add_css_class("inactive"); + } + ws->x_index = k; + ws->y_index = j; + auto popover_click_gesture = Gtk::GestureClick::create(); + popover_click_gesture->set_button(0); + popover_click_gesture->signal_released().connect(sigc::mem_fun(*ws, &WayfireWorkspaceBox::on_popover_grid_clicked)); + ws->add_controller(popover_click_gesture); + popover_grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); + if (workspace_switcher_render_views && j == this->grid_height - 1 && k == this->grid_width - 1) + { + ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) + { + if (data.serialize().find("error") != std::string::npos) + { + std::cerr << data.serialize() << std::endl; + std::cerr << "Error getting views list for workspace-switcher widget!" << std::endl; + return; + } + popover_render_views(data); + }); + } + } + } + } + }); + } +} + void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) { if (view_data["type"].as_string() != "toplevel") @@ -316,6 +518,55 @@ void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) } } +void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) +{ + if (view_data["type"].as_string() != "toplevel") + { + return; + } + + if (view_data["output-name"].as_string() != this->output_name) + { + return; + } + + auto v = Gtk::make_managed(); + v->add_css_class("view"); + v->add_css_class(view_data["app-id"].as_string()); + v->id = view_data["id"].as_int(); + v->output_id = view_data["output-id"].as_int(); + auto x = view_data["geometry"]["x"].as_int(); + auto y = view_data["geometry"]["y"].as_int(); + auto w = view_data["geometry"]["width"].as_int(); + auto h = view_data["geometry"]["height"].as_int(); + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *window = (WayfireWorkspaceWindow*)widget; + if (window->id == v->id) + { + return; + } + + double width = this->get_scaled_width(); + double height = workspace_switcher_target_height; + + //v->ws = ws; + v->x = x * (width / float(this->output_width)); + v->y = y * (height / float(this->output_height)); + v->w = (w / float(this->output_width)) * width; + v->h = (h / float(this->output_height)) * height; + std::pair workspace; + workspace = popover_get_workspace(v); + v->x_index = workspace.first; + v->y_index = workspace.second; + v->set_can_target(false); + // add to workspace box + overlay.add_overlay(*v); + windows.push_back(v); + return; + } +} + void WayfireWorkspaceSwitcher::remove_view(wf::json_t view_data) { for (auto w : this->windows) @@ -337,6 +588,24 @@ void WayfireWorkspaceSwitcher::remove_view(wf::json_t view_data) } } +void WayfireWorkspaceSwitcher::popover_remove_view(wf::json_t view_data) +{ + for (auto w : this->windows) + { + if (w->id == view_data["id"].as_int()) + { + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + overlay.remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + return; + } + } + } +} + void WayfireWorkspaceSwitcher::render_views(wf::json_t views_data) { for (size_t i = 0; i < views_data.size(); i++) @@ -345,7 +614,26 @@ void WayfireWorkspaceSwitcher::render_views(wf::json_t views_data) } } +void WayfireWorkspaceSwitcher::popover_render_views(wf::json_t views_data) +{ + for (size_t i = 0; i < views_data.size(); i++) + { + popover_add_view(views_data[i]); + } +} + void WayfireWorkspaceSwitcher::on_event(wf::json_t data) +{ + if (std::string(workspace_switcher_mode) == "classic") + { + switcher_on_event(data); + } else // "popover" + { + popover_on_event(data); + } +} + +void WayfireWorkspaceSwitcher::switcher_on_event(wf::json_t data) { if (data["event"].as_string() == "view-geometry-changed") { @@ -381,12 +669,44 @@ void WayfireWorkspaceSwitcher::on_event(wf::json_t data) } } +void WayfireWorkspaceSwitcher::popover_on_event(wf::json_t data) +{ + if (data["event"].as_string() == "view-geometry-changed") + { + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + overlay.remove_overlay(*w); + auto elem = std::remove(windows.begin(), windows.end(), w); + windows.erase(elem, windows.end()); + } + } + + popover_add_view(data["view"]); + } else if (data["event"].as_string() == "view-mapped") + { + popover_add_view(data["view"]); + } else if (data["event"].as_string() == "view-unmapped") + { + popover_remove_view(data["view"]); + } else if (data["event"].as_string() == "view-set-output") + { + popover_add_view(data["view"]); + } else if ((data["event"].as_string() == "output-layout-changed") || + (data["event"].as_string() == "wset-workspace-changed")) + { + get_wsets(); + } +} + WayfireWorkspaceSwitcher::WayfireWorkspaceSwitcher(WayfireOutput *output) { this->output_name = output->monitor->get_connector(); ipc_client = WayfireIPC::get_instance()->create_client(); - box.set_vexpand(false); - box.set_valign(Gtk::Align::CENTER); + switcher_box.set_vexpand(false); + switcher_box.set_valign(Gtk::Align::CENTER); } WayfireWorkspaceSwitcher::~WayfireWorkspaceSwitcher() diff --git a/src/panel/widgets/workspace-switcher.hpp b/src/panel/widgets/workspace-switcher.hpp index 5fad9a4bd..cbc78c6f7 100644 --- a/src/panel/widgets/workspace-switcher.hpp +++ b/src/panel/widgets/workspace-switcher.hpp @@ -23,27 +23,46 @@ class WayfireWorkspaceSwitcher : public WayfireWidget, public IIPCSubscriber { std::string output_name; void on_event(wf::json_t data) override; + void switcher_on_event(wf::json_t data); + void popover_on_event(wf::json_t data); void render_workspace(wf::json_t workspace_data, int j, int output_id, int output_width, int output_height); void process_workspaces(wf::json_t workspace_data); + void popover_process_workspaces(wf::json_t workspace_data); void render_views(wf::json_t views_data); + void popover_render_views(wf::json_t views_data); void add_view(wf::json_t view_data); + void popover_add_view(wf::json_t view_data); void remove_view(wf::json_t view_data); + void popover_remove_view(wf::json_t view_data); + void clear_switcher_box(); void clear_box(); void get_wsets(); bool on_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation); + bool on_popover_get_child_position(Gtk::Widget *widget, Gdk::Rectangle& allocation); public: Gtk::Box box; + Gtk::Grid grid; + Gtk::Box switcher_box; + Gtk::Popover *popover; + Gtk::Grid popover_grid; + Gtk::Overlay overlay; + double get_scaled_width(); + int output_width, output_height; void init(Gtk::Box *container) override; WayfireWorkspaceSwitcher(WayfireOutput *output); ~WayfireWorkspaceSwitcher(); int grid_width, grid_height; std::shared_ptr ipc_client; std::pair get_workspace(WayfireWorkspaceBox *ws, WayfireWorkspaceWindow *w); + std::pair popover_get_workspace(WayfireWorkspaceWindow *w); int current_ws_x, current_ws_y; std::vector windows; + void on_grid_clicked(int count, double x, double y); + WfOption workspace_switcher_mode{"panel/workspace_switcher_mode"}; WfOption workspace_switcher_target_height{"panel/workspace_switcher_target_height"}; + WfOption workspace_switcher_render_views{"panel/workspace_switcher_render_views"}; }; class WayfireWorkspaceBox : public Gtk::Overlay @@ -61,6 +80,7 @@ class WayfireWorkspaceBox : public Gtk::Overlay ~WayfireWorkspaceBox() override {} + void on_popover_grid_clicked(int count, double x, double y); void on_workspace_clicked(int count, double x, double y); bool on_workspace_scrolled(double x, double y); }; From 0f8593c5220c44d4d932a28067cf4c068764c604 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 10:58:15 -0700 Subject: [PATCH 3/9] Uncrustify --- src/panel/widgets/workspace-switcher.cpp | 60 ++++++++++++++---------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index 48a4b9b2d..a7b30dd29 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -24,22 +24,24 @@ void WayfireWorkspaceSwitcher::init(Gtk::Box *container) if (std::string(workspace_switcher_mode) == "classic") { switcher_box.append(box); - } else // "popover" - { + } else // "popover" + { switcher_box.append(grid); } + get_wsets(); }); workspace_switcher_mode.set_callback(mode_cb); workspace_switcher_render_views.set_callback([=] () { - get_wsets(); + get_wsets(); }); grid.add_css_class("workspace-switcher"); auto click_gesture = Gtk::GestureClick::create(); click_gesture->set_button(0); - click_gesture->signal_released().connect(sigc::mem_fun(*this, &WayfireWorkspaceSwitcher::on_grid_clicked)); + click_gesture->signal_released().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_grid_clicked)); grid.add_controller(click_gesture); container->append(switcher_box); @@ -60,8 +62,8 @@ void WayfireWorkspaceSwitcher::get_wsets() if (std::string(workspace_switcher_mode) == "classic") { process_workspaces(data); - } else // "popover" - { + } else // "popover" + { popover_process_workspaces(data); } }); @@ -81,10 +83,12 @@ void WayfireWorkspaceSwitcher::clear_box() { box.remove(*child); } + for (auto child : grid.get_children()) { grid.remove(*child); } + for (auto child : popover_grid.get_children()) { popover_grid.remove(*child); @@ -309,7 +313,7 @@ void WayfireWorkspaceSwitcher::render_workspace(wf::json_t workspace, int j, int ws->add_controller(click_gesture); ws->add_controller(scroll_controller); box.append(*ws); - if (workspace_switcher_render_views && j == this->grid_width - 1) + if (workspace_switcher_render_views && (j == this->grid_width - 1)) { ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) { @@ -395,7 +399,8 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d popover->set_parent(grid); popover->set_child(overlay); overlay.set_child(popover_grid); - overlay.signal_get_child_position().connect(sigc::mem_fun(*this, &WayfireWorkspaceSwitcher::on_popover_get_child_position), false); + overlay.signal_get_child_position().connect(sigc::mem_fun(*this, + &WayfireWorkspaceSwitcher::on_popover_get_child_position), false); for (int j = 0; j < this->grid_height; j++) { for (int k = 0; k < this->grid_width; k++) @@ -403,12 +408,12 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d auto ws = Gtk::make_managed(this); ws->output_id = output_data["id"].as_int(); ws->set_can_target(false); - auto ws_width = this->get_scaled_width() / this->grid_width; + auto ws_width = this->get_scaled_width() / this->grid_width; auto ws_height = this->workspace_switcher_target_height / this->grid_height; ws->set_size_request(ws_width, ws_height); ws->add_css_class("workspace"); - if (workspace_data[i]["workspace"]["x"].as_int() == k && - workspace_data[i]["workspace"]["y"].as_int() == j) + if ((workspace_data[i]["workspace"]["x"].as_int() == k) && + (workspace_data[i]["workspace"]["y"].as_int() == j)) { ws->add_css_class("active"); this->current_ws_x = k; @@ -417,16 +422,18 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d { ws->add_css_class("inactive"); } + ws->x_index = k; ws->y_index = j; grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); ws = Gtk::make_managed(this); ws->output_id = output_data["id"].as_int(); - ws->set_size_request(this->get_scaled_width(), this->workspace_switcher_target_height); + ws->set_size_request( + this->get_scaled_width(), this->workspace_switcher_target_height); ws->add_css_class("workspace"); - if (workspace_data[i]["workspace"]["x"].as_int() == k && - workspace_data[i]["workspace"]["y"].as_int() == j) + if ((workspace_data[i]["workspace"]["x"].as_int() == k) && + (workspace_data[i]["workspace"]["y"].as_int() == j)) { ws->add_css_class("active"); this->current_ws_x = k; @@ -435,23 +442,28 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d { ws->add_css_class("inactive"); } + ws->x_index = k; ws->y_index = j; auto popover_click_gesture = Gtk::GestureClick::create(); popover_click_gesture->set_button(0); - popover_click_gesture->signal_released().connect(sigc::mem_fun(*ws, &WayfireWorkspaceBox::on_popover_grid_clicked)); + popover_click_gesture->signal_released().connect(sigc::mem_fun(*ws, + &WayfireWorkspaceBox::on_popover_grid_clicked)); ws->add_controller(popover_click_gesture); popover_grid.attach(*ws, ws->x_index, ws->y_index, 1, 1); - if (workspace_switcher_render_views && j == this->grid_height - 1 && k == this->grid_width - 1) + if (workspace_switcher_render_views && (j == this->grid_height - 1) && + (k == this->grid_width - 1)) { ipc_client->send("{\"method\":\"window-rules/list-views\"}", [=] (wf::json_t data) { if (data.serialize().find("error") != std::string::npos) { std::cerr << data.serialize() << std::endl; - std::cerr << "Error getting views list for workspace-switcher widget!" << std::endl; + std::cerr << "Error getting views list for workspace-switcher widget!" << + std::endl; return; } + popover_render_views(data); }); } @@ -550,11 +562,11 @@ void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) double width = this->get_scaled_width(); double height = workspace_switcher_target_height; - //v->ws = ws; - v->x = x * (width / float(this->output_width)); - v->y = y * (height / float(this->output_height)); - v->w = (w / float(this->output_width)) * width; - v->h = (h / float(this->output_height)) * height; + // v->ws = ws; + v->x = x * (width / float(this->output_width)); + v->y = y * (height / float(this->output_height)); + v->w = (w / float(this->output_width)) * width; + v->h = (h / float(this->output_height)) * height; std::pair workspace; workspace = popover_get_workspace(v); v->x_index = workspace.first; @@ -627,8 +639,8 @@ void WayfireWorkspaceSwitcher::on_event(wf::json_t data) if (std::string(workspace_switcher_mode) == "classic") { switcher_on_event(data); - } else // "popover" - { + } else // "popover" + { popover_on_event(data); } } From 6b43c547bf79cfe822a11c5a146b5a4fb07464e1 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 11:02:54 -0700 Subject: [PATCH 4/9] Fixup css classes --- src/panel/widgets/workspace-switcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index a7b30dd29..f044728d1 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -37,7 +37,7 @@ void WayfireWorkspaceSwitcher::init(Gtk::Box *container) get_wsets(); }); - grid.add_css_class("workspace-switcher"); + switcher_box.add_css_class("workspace-switcher"); auto click_gesture = Gtk::GestureClick::create(); click_gesture->set_button(0); click_gesture->signal_released().connect(sigc::mem_fun(*this, @@ -399,6 +399,7 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d popover->set_parent(grid); popover->set_child(overlay); overlay.set_child(popover_grid); + overlay.add_css_class("workspace"); overlay.signal_get_child_position().connect(sigc::mem_fun(*this, &WayfireWorkspaceSwitcher::on_popover_get_child_position), false); for (int j = 0; j < this->grid_height; j++) From c7e5e82ca588be7961824840f87a868d934e443f Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 20:05:22 -0700 Subject: [PATCH 5/9] workspace-switcher: Set active and inactive classes for view widgets --- src/panel/widgets/workspace-switcher.cpp | 62 +++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index f044728d1..8620ce82a 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -9,6 +9,7 @@ void WayfireWorkspaceSwitcher::init(Gtk::Box *container) box.add_css_class("flat"); ipc_client->subscribe(this, {"view-mapped"}); + ipc_client->subscribe(this, {"view-focused"}); ipc_client->subscribe(this, {"view-unmapped"}); ipc_client->subscribe(this, {"view-set-output"}); ipc_client->subscribe(this, {"view-geometry-changed"}); @@ -461,7 +462,7 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d { std::cerr << data.serialize() << std::endl; std::cerr << "Error getting views list for workspace-switcher widget!" << - std::endl; + std::endl; return; } @@ -490,6 +491,14 @@ void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) auto v = Gtk::make_managed(); v->add_css_class("view"); v->add_css_class(view_data["app-id"].as_string()); + if (view_data["activated"].as_bool()) + { + v->add_css_class("active"); + } else + { + v->add_css_class("inactive"); + } + v->id = view_data["id"].as_int(); v->output_id = view_data["output-id"].as_int(); auto x = view_data["geometry"]["x"].as_int(); @@ -546,6 +555,14 @@ void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) auto v = Gtk::make_managed(); v->add_css_class("view"); v->add_css_class(view_data["app-id"].as_string()); + if (view_data["activated"].as_bool()) + { + v->add_css_class("active"); + } else + { + v->add_css_class("inactive"); + } + v->id = view_data["id"].as_int(); v->output_id = view_data["output-id"].as_int(); auto x = view_data["geometry"]["x"].as_int(); @@ -669,6 +686,29 @@ void WayfireWorkspaceSwitcher::switcher_on_event(wf::json_t data) } else if (data["event"].as_string() == "view-mapped") { add_view(data["view"]); + } else if ((data["event"].as_string() == "view-focused") && data["view"].is_object()) + { + if (data["view"]["type"].as_string() != "toplevel") + { + return; + } + + for (auto child : box.get_children()) + { + for (auto widget : child->get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + w->remove_css_class("inactive"); + w->add_css_class("active"); + } else + { + w->remove_css_class("active"); + w->add_css_class("inactive"); + } + } + } } else if (data["event"].as_string() == "view-unmapped") { remove_view(data["view"]); @@ -701,6 +741,26 @@ void WayfireWorkspaceSwitcher::popover_on_event(wf::json_t data) } else if (data["event"].as_string() == "view-mapped") { popover_add_view(data["view"]); + } else if ((data["event"].as_string() == "view-focused") && data["view"].is_object()) + { + if (data["view"]["type"].as_string() != "toplevel") + { + return; + } + + for (auto widget : overlay.get_children()) + { + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; + if (w->id == data["view"]["id"].as_int()) + { + w->remove_css_class("inactive"); + w->add_css_class("active"); + } else + { + w->remove_css_class("active"); + w->add_css_class("inactive"); + } + } } else if (data["event"].as_string() == "view-unmapped") { popover_remove_view(data["view"]); From eb474b55b79d3491a867ebf7761bec0692bfc288 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 20:07:48 -0700 Subject: [PATCH 6/9] workspace-switcher: Update default.css --- data/css/default.css | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/data/css/default.css b/data/css/default.css index 9c46dcb07..f8c504d99 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -75,18 +75,21 @@ .wf-panel .workspace-switcher { background-color: #222222FF; - padding-right: 1px; - padding-top: 1px; + padding-top: 0px; padding-bottom: 1px; + padding-right: 1px; + padding-left: 0px; } -.wf-panel .workspace-switcher .active { - background-color: #337755FF; +.wf-panel .workspace-switcher .workspace.active { + background-color: #005577FF; + margin-top: 1px; margin-left: 1px; } -.wf-panel .workspace-switcher .inactive { +.wf-panel .workspace-switcher .workspace.inactive { background-color: #0077AAFF; + margin-top: 1px; margin-left: 1px; } @@ -94,6 +97,16 @@ background-color: #0000AA77; border: 1px solid #222 } + +.wf-panel .workspace-switcher .workspace .view.active { + background-color: #FF00AA77; + border: 1px solid #222 +} + +.wf-panel .workspace-switcher .workspace .view.inactive { + background-color: #00FFAA77; + border: 1px solid #222 +} .excellent { color: #00ff00; } From 450b1153142720879fc44634192e60466d811796 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Wed, 18 Feb 2026 20:19:29 -0700 Subject: [PATCH 7/9] workspace-switcher: Fix popover styling bug --- src/panel/widgets/workspace-switcher.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index 8620ce82a..f0be2b582 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -750,6 +750,10 @@ void WayfireWorkspaceSwitcher::popover_on_event(wf::json_t data) for (auto widget : overlay.get_children()) { + if (widget->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; if (w->id == data["view"]["id"].as_int()) { From 4b5afac5707bf98f0b67b1370c20ff6f6940e7cb Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 19 Feb 2026 03:01:34 -0700 Subject: [PATCH 8/9] workspace-switcher: Fix up stacking orders of window widgets --- src/panel/widgets/workspace-switcher.cpp | 116 +++++++++++++++++++++-- src/panel/widgets/workspace-switcher.hpp | 2 + 2 files changed, 108 insertions(+), 10 deletions(-) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index f0be2b582..91b26e6bd 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -94,6 +94,8 @@ void WayfireWorkspaceSwitcher::clear_box() { popover_grid.remove(*child); } + + windows.clear(); } std::pair WayfireWorkspaceSwitcher::get_workspace(WayfireWorkspaceBox *ws, @@ -358,6 +360,15 @@ void WayfireWorkspaceSwitcher::process_workspaces(wf::json_t workspace_data) auto output_height = output_data["geometry"]["height"].as_int(); if (this->output_name == output_data["name"].as_string()) { + for (auto w : windows) + { + if (w->active) + { + this->active_view_id = w->id; + break; + } + } + clear_box(); for (int j = 0; j < this->grid_width; j++) { @@ -395,6 +406,15 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d this->output_height = output_data["geometry"]["height"].as_int(); if (this->output_name == output_data["name"].as_string()) { + for (auto w : windows) + { + if (w->active) + { + this->active_view_id = w->id; + break; + } + } + clear_box(); popover = Gtk::make_managed(); popover->set_parent(grid); @@ -491,15 +511,18 @@ void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) auto v = Gtk::make_managed(); v->add_css_class("view"); v->add_css_class(view_data["app-id"].as_string()); - if (view_data["activated"].as_bool()) + v->id = view_data["id"].as_int(); + if (view_data["activated"].as_bool() || (v->id == this->active_view_id)) { v->add_css_class("active"); + v->active = true; + this->active_view_id = v->id; } else { v->add_css_class("inactive"); + v->active = false; } - v->id = view_data["id"].as_int(); v->output_id = view_data["output-id"].as_int(); auto x = view_data["geometry"]["x"].as_int(); auto y = view_data["geometry"]["y"].as_int(); @@ -520,21 +543,22 @@ void WayfireWorkspaceSwitcher::add_view(wf::json_t view_data) double width = ws->get_scaled_width(); double height = workspace_switcher_target_height; - v->ws = ws; - v->x = x * (width / float(ws->output_width)); - v->y = y * (height / float(ws->output_height)); - v->w = (w / float(ws->output_width)) * width; - v->h = (h / float(ws->output_height)) * height; + v->x = x * (width / float(ws->output_width)); + v->y = y * (height / float(ws->output_height)); + v->w = (w / float(ws->output_width)) * width; + v->h = (h / float(ws->output_height)) * height; std::pair workspace; workspace = get_workspace(ws, v); v->x_index = workspace.first; v->y_index = workspace.second; if ((v->x_index == ws->x_index) && (v->y_index == ws->y_index)) { + v->ws = ws; v->set_can_target(false); // add to workspace box ws->add_overlay(*v); windows.push_back(v); + return; } } @@ -555,15 +579,18 @@ void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) auto v = Gtk::make_managed(); v->add_css_class("view"); v->add_css_class(view_data["app-id"].as_string()); - if (view_data["activated"].as_bool()) + v->id = view_data["id"].as_int(); + if (view_data["activated"].as_bool() || (v->id == this->active_view_id)) { v->add_css_class("active"); + v->active = true; + this->active_view_id = v->id; } else { v->add_css_class("inactive"); + v->active = false; } - v->id = view_data["id"].as_int(); v->output_id = view_data["output-id"].as_int(); auto x = view_data["geometry"]["x"].as_int(); auto y = view_data["geometry"]["y"].as_int(); @@ -580,7 +607,6 @@ void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) double width = this->get_scaled_width(); double height = workspace_switcher_target_height; - // v->ws = ws; v->x = x * (width / float(this->output_width)); v->y = y * (height / float(this->output_height)); v->w = (w / float(this->output_width)) * width; @@ -589,10 +615,13 @@ void WayfireWorkspaceSwitcher::popover_add_view(wf::json_t view_data) workspace = popover_get_workspace(v); v->x_index = workspace.first; v->y_index = workspace.second; + WayfireWorkspaceBox *ws = (WayfireWorkspaceBox*)&overlay; + v->ws = ws; v->set_can_target(false); // add to workspace box overlay.add_overlay(*v); windows.push_back(v); + return; } } @@ -642,6 +671,18 @@ void WayfireWorkspaceSwitcher::render_views(wf::json_t views_data) { add_view(views_data[i]); } + + for (auto w : windows) + { + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + } + } } void WayfireWorkspaceSwitcher::popover_render_views(wf::json_t views_data) @@ -650,6 +691,23 @@ void WayfireWorkspaceSwitcher::popover_render_views(wf::json_t views_data) { popover_add_view(views_data[i]); } + + for (auto w : windows) + { + if (w->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } + + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + } + } } void WayfireWorkspaceSwitcher::on_event(wf::json_t data) @@ -702,13 +760,29 @@ void WayfireWorkspaceSwitcher::switcher_on_event(wf::json_t data) { w->remove_css_class("inactive"); w->add_css_class("active"); + w->active = true; + this->active_view_id = w->id; } else { w->remove_css_class("active"); w->add_css_class("inactive"); + w->active = false; } } } + + for (auto w : windows) + { + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + this->active_view_id = w->id; + } + } } else if (data["event"].as_string() == "view-unmapped") { remove_view(data["view"]); @@ -754,15 +828,37 @@ void WayfireWorkspaceSwitcher::popover_on_event(wf::json_t data) { continue; } + WayfireWorkspaceWindow *w = (WayfireWorkspaceWindow*)widget; if (w->id == data["view"]["id"].as_int()) { w->remove_css_class("inactive"); w->add_css_class("active"); + w->active = true; + this->active_view_id = w->id; } else { w->remove_css_class("active"); w->add_css_class("inactive"); + w->active = false; + } + } + + for (auto w : windows) + { + if (w->gobj() == GTK_WIDGET(popover_grid.gobj())) + { + continue; + } + + if (w->active) + { + w->reference(); + w->ws->remove_overlay(*w); + w->ws->add_overlay(*w); + w->unreference(); + w->add_css_class("active"); + this->active_view_id = w->id; } } } else if (data["event"].as_string() == "view-unmapped") diff --git a/src/panel/widgets/workspace-switcher.hpp b/src/panel/widgets/workspace-switcher.hpp index cbc78c6f7..38cc94980 100644 --- a/src/panel/widgets/workspace-switcher.hpp +++ b/src/panel/widgets/workspace-switcher.hpp @@ -12,6 +12,7 @@ class WayfireWorkspaceWindow : public Gtk::Widget int x = 0, y = 0, w = 10, h = 10; int x_index, y_index; int id, output_id; + bool active; WayfireWorkspaceBox *ws; WayfireWorkspaceWindow() {} @@ -54,6 +55,7 @@ class WayfireWorkspaceSwitcher : public WayfireWidget, public IIPCSubscriber WayfireWorkspaceSwitcher(WayfireOutput *output); ~WayfireWorkspaceSwitcher(); int grid_width, grid_height; + int active_view_id; std::shared_ptr ipc_client; std::pair get_workspace(WayfireWorkspaceBox *ws, WayfireWorkspaceWindow *w); std::pair popover_get_workspace(WayfireWorkspaceWindow *w); From 1981869667fc23746af19cb594d70df9ebe39ce1 Mon Sep 17 00:00:00 2001 From: Scott Moreau Date: Thu, 19 Feb 2026 05:15:55 -0700 Subject: [PATCH 9/9] workspace-switcher: Fix potential issues with multiple outputs of different sizes --- src/panel/widgets/workspace-switcher.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/panel/widgets/workspace-switcher.cpp b/src/panel/widgets/workspace-switcher.cpp index 91b26e6bd..2f130b709 100644 --- a/src/panel/widgets/workspace-switcher.cpp +++ b/src/panel/widgets/workspace-switcher.cpp @@ -355,11 +355,11 @@ void WayfireWorkspaceSwitcher::process_workspaces(wf::json_t workspace_data) return; } - auto output_id = output_data["id"].as_int(); - auto output_width = output_data["geometry"]["width"].as_int(); - auto output_height = output_data["geometry"]["height"].as_int(); if (this->output_name == output_data["name"].as_string()) { + auto output_id = output_data["id"].as_int(); + auto output_width = output_data["geometry"]["width"].as_int(); + auto output_height = output_data["geometry"]["height"].as_int(); for (auto w : windows) { if (w->active) @@ -402,10 +402,10 @@ void WayfireWorkspaceSwitcher::popover_process_workspaces(wf::json_t workspace_d return; } - this->output_width = output_data["geometry"]["width"].as_int(); - this->output_height = output_data["geometry"]["height"].as_int(); if (this->output_name == output_data["name"].as_string()) { + this->output_width = output_data["geometry"]["width"].as_int(); + this->output_height = output_data["geometry"]["height"].as_int(); for (auto w : windows) { if (w->active)