feat(widgets): add show all outputs to taskbar widget

This commit is contained in:
Ly-sec
2026-05-10 13:54:45 +02:00
parent cd468caa93
commit 32c6be928a
5 changed files with 193 additions and 51 deletions
+4
View File
@@ -992,6 +992,10 @@
"label": "Group by Workspace",
"description": "Group taskbar icons into workspace capsules when compositor data is available"
},
"show_all_outputs": {
"label": "Show All Monitors",
"description": "List windows from every display on this taskbar. Grouped capsules add a short monitor index (tag·1, tag·2, …) when multiple outputs are present."
},
"height": {
"label": "Height",
"description": "Widget height in pixels"
+2 -1
View File
@@ -333,7 +333,8 @@ std::unique_ptr<Widget> WidgetFactory::create(const std::string& name, wl_output
if (type == "taskbar") {
const bool groupByWorkspace = wc != nullptr ? wc->getBool("group_by_workspace", false) : false;
auto widget = std::make_unique<TaskbarWidget>(m_platform, output, groupByWorkspace, barPosition);
const bool showAllOutputs = wc != nullptr ? wc->getBool("show_all_outputs", false) : false;
auto widget = std::make_unique<TaskbarWidget>(m_platform, output, groupByWorkspace, showAllOutputs, barPosition);
widget->setContentScale(contentScale);
return widget;
}
+178 -49
View File
@@ -33,8 +33,8 @@
#include <wayland-client-protocol.h>
TaskbarWidget::TaskbarWidget(CompositorPlatform& platform, wl_output* output, bool groupByWorkspace,
std::string barPosition)
: m_platform(platform), m_output(output), m_groupByWorkspace(groupByWorkspace),
bool showAllOutputs, std::string barPosition)
: m_platform(platform), m_output(output), m_groupByWorkspace(groupByWorkspace), m_showAllOutputs(showAllOutputs),
m_barPosition(std::move(barPosition)) {
buildDesktopIconIndex();
}
@@ -177,25 +177,26 @@ void TaskbarWidget::buildTaskButtons(Renderer& renderer) {
}
const std::optional<Workspace> clickWorkspace =
taskWorkspace != nullptr ? std::optional<Workspace>(taskWorkspace->workspace) : std::nullopt;
wl_output* const taskWsHost = taskWorkspace != nullptr ? workspaceHostOutput(*taskWorkspace) : m_output;
if (task.firstHandle != nullptr || clickWorkspace.has_value()) {
auto* areaPtr = area.get();
area->setOnClick(
[this, task, areaPtr, handle = task.firstHandle, clickWorkspace](const InputArea::PointerData& data) {
if (data.button == BTN_LEFT) {
if (handle != nullptr) {
m_platform.activateToplevel(handle);
return;
}
if (clickWorkspace.has_value()) {
m_platform.activateWorkspace(m_output, *clickWorkspace);
}
return;
}
if (data.button == BTN_RIGHT && areaPtr != nullptr && handle != nullptr) {
openTaskContextMenu(task, *areaPtr);
}
});
area->setOnClick([this, task, areaPtr, handle = task.firstHandle, clickWorkspace,
taskWsHost](const InputArea::PointerData& data) {
if (data.button == BTN_LEFT) {
if (handle != nullptr) {
m_platform.activateToplevel(handle);
return;
}
if (clickWorkspace.has_value()) {
m_platform.activateWorkspace(taskWsHost, *clickWorkspace);
}
return;
}
if (data.button == BTN_RIGHT && areaPtr != nullptr && handle != nullptr) {
openTaskContextMenu(task, *areaPtr);
}
});
} else {
area->setEnabled(false);
}
@@ -279,9 +280,10 @@ void TaskbarWidget::buildTaskButtons(Renderer& renderer) {
switcher->setAcceptedButtons(InputArea::buttonMask(BTN_LEFT));
switcher->setOnAxisHandler(workspaceAxisHandler);
auto wsCopy = ws.workspace;
switcher->setOnClick([this, wsCopy](const InputArea::PointerData& data) {
wl_output* const wsHost = workspaceHostOutput(ws);
switcher->setOnClick([this, wsCopy, wsHost](const InputArea::PointerData& data) {
if (data.button == BTN_LEFT) {
m_platform.activateWorkspace(m_output, wsCopy);
m_platform.activateWorkspace(wsHost, wsCopy);
}
});
groupPtr->addChild(std::move(switcher));
@@ -306,9 +308,10 @@ void TaskbarWidget::buildTaskButtons(Renderer& renderer) {
badgeHit->setAcceptedButtons(InputArea::buttonMask(BTN_LEFT));
badgeHit->setOnAxisHandler(workspaceAxisHandler);
auto wsForBadge = ws.workspace;
badgeHit->setOnClick([this, wsForBadge](const InputArea::PointerData& data) {
wl_output* const badgeHost = workspaceHostOutput(ws);
badgeHit->setOnClick([this, wsForBadge, badgeHost](const InputArea::PointerData& data) {
if (data.button == BTN_LEFT) {
m_platform.activateWorkspace(m_output, wsForBadge);
m_platform.activateWorkspace(badgeHost, wsForBadge);
}
});
@@ -351,7 +354,8 @@ void TaskbarWidget::updateModels() {
const auto active = m_platform.activeToplevel();
const auto* activeHandle = active.has_value() ? active->handle : nullptr;
const auto running = m_platform.runningAppIds(m_output);
wl_output* const topFilter = toplevelOutputFilter();
const auto running = m_platform.runningAppIds(topFilter);
const auto assignmentMode = m_platform.taskbarAssignmentMode();
const auto resolvedRunning = app_identity::resolveRunningApps(running, desktopEntries());
std::vector<WorkspaceModel> nextWorkspaces;
@@ -359,18 +363,73 @@ void TaskbarWidget::updateModels() {
std::vector<WorkspaceWindowAssignment> workspaceAssignments;
if (m_groupByWorkspace) {
const auto workspaces = m_platform.workspaces(m_output);
const auto displayKeys = m_platform.workspaceDisplayKeys(m_output);
nextWorkspaces.reserve(workspaces.size());
for (std::size_t i = 0; i < workspaces.size(); ++i) {
WorkspaceModel item{};
item.workspace = workspaces[i];
item.key = i < displayKeys.size() && !displayKeys[i].empty() ? displayKeys[i] : workspaceLabel(item.workspace, i);
item.label = item.key;
nextWorkspaces.push_back(std::move(item));
nextWorkspaces.reserve(32);
std::unordered_map<wl_output*, int> monitorOrdinal;
int nextOrdinal = 1;
for (const auto& wo : m_platform.outputs()) {
if (wo.output == nullptr) {
continue;
}
if (!m_showAllOutputs && wo.output != m_output) {
continue;
}
monitorOrdinal.emplace(wo.output, nextOrdinal++);
}
runningByWorkspace = m_platform.appIdsByWorkspace(m_output);
workspaceAssignments = m_platform.workspaceWindowAssignments(m_output);
for (const auto& wo : m_platform.outputs()) {
if (wo.output == nullptr) {
continue;
}
if (!m_showAllOutputs && wo.output != m_output) {
continue;
}
const auto workspaces = m_platform.workspaces(wo.output);
const auto displayKeys = m_platform.workspaceDisplayKeys(wo.output);
const std::string keyPrefix = workspaceKeyPrefixForOutput(wo.output);
nextWorkspaces.reserve(nextWorkspaces.size() + workspaces.size());
for (std::size_t i = 0; i < workspaces.size(); ++i) {
WorkspaceModel item{};
item.workspace = workspaces[i];
item.hostOutput = wo.output;
const std::string baseKey =
i < displayKeys.size() && !displayKeys[i].empty() ? displayKeys[i] : workspaceLabel(item.workspace, i);
item.key = keyPrefix + baseKey;
if (useMultiOutputWorkspaceKeys()) {
const auto ordIt = monitorOrdinal.find(wo.output);
if (ordIt != monitorOrdinal.end()) {
item.label = baseKey + "\u00B7" + std::to_string(ordIt->second);
} else {
item.label = baseKey;
}
} else {
item.label = baseKey;
}
nextWorkspaces.push_back(std::move(item));
}
}
if (m_showAllOutputs) {
for (const auto& wo : m_platform.outputs()) {
if (wo.output == nullptr) {
continue;
}
const std::string prefix = workspaceKeyPrefixForOutput(wo.output);
const auto perApps = m_platform.appIdsByWorkspace(wo.output);
for (const auto& [wsKey, apps] : perApps) {
auto& bucket = runningByWorkspace[prefix + wsKey];
bucket.insert(bucket.end(), apps.begin(), apps.end());
}
auto perAssign = m_platform.workspaceWindowAssignments(wo.output);
for (auto& row : perAssign) {
row.workspaceKey = prefix + row.workspaceKey;
workspaceAssignments.push_back(std::move(row));
}
}
} else {
runningByWorkspace = m_platform.appIdsByWorkspace(m_output);
workspaceAssignments = m_platform.workspaceWindowAssignments(m_output);
}
std::unordered_map<std::string, std::size_t> workspaceKeyToOrder;
for (std::size_t i = 0; i < nextWorkspaces.size(); ++i) {
workspaceKeyToOrder[nextWorkspaces[i].key] = i;
@@ -403,7 +462,7 @@ void TaskbarWidget::updateModels() {
const std::string nameLower = !run.entry.nameLower.empty() ? run.entry.nameLower : run.runningLower;
const std::string appId = !run.entry.id.empty() ? run.entry.id : run.runningAppId;
const auto windows = m_platform.windowsForApp(idLower, startupLower, m_output);
const auto windows = m_platform.windowsForApp(idLower, startupLower, topFilter);
for (const auto& window : windows) {
const auto handleKey = reinterpret_cast<std::uintptr_t>(window.handle);
if (!processedHandles.insert(handleKey).second) {
@@ -457,7 +516,25 @@ void TaskbarWidget::updateModels() {
candidates.push_back(std::move(candidate));
}
const auto assignedByHandle = m_platform.assignTaskbarWindows(candidates, m_output);
std::unordered_map<std::uintptr_t, WorkspaceWindow> assignedByHandle;
if (m_showAllOutputs) {
for (const auto& wo : m_platform.outputs()) {
if (wo.output == nullptr) {
continue;
}
const auto part = m_platform.assignTaskbarWindows(candidates, wo.output);
const std::string prefix = workspaceKeyPrefixForOutput(wo.output);
for (const auto& [handleKey, assigned] : part) {
WorkspaceWindow copy = assigned;
if (!prefix.empty()) {
copy.workspaceKey = prefix + copy.workspaceKey;
}
assignedByHandle[handleKey] = std::move(copy);
}
}
} else {
assignedByHandle = m_platform.assignTaskbarWindows(candidates, m_output);
}
std::vector<bool> representedAssignments(workspaceAssignments.size(), false);
for (auto& task : nextTasks) {
@@ -812,7 +889,7 @@ void TaskbarWidget::updateModels() {
for (const auto& appId : *list) {
const std::string appLower = toLower(appId);
const std::string startupWmClassLower = toLower(appId);
const auto windows = m_platform.windowsForApp(appLower, startupWmClassLower, m_output);
const auto windows = m_platform.windowsForApp(appLower, startupWmClassLower, topFilter);
if (windows.empty()) {
continue;
}
@@ -832,20 +909,37 @@ void TaskbarWidget::updateModels() {
if (m_groupByWorkspace && !nextWorkspaces.empty() &&
assignmentMode != TaskbarAssignmentMode::WorkspaceOccurrenceTitle) {
std::string activeWorkspaceKey;
for (const auto& workspace : nextWorkspaces) {
if (workspace.workspace.active) {
activeWorkspaceKey = workspace.key;
wl_output* activeOut = m_platform.activeToplevelOutput();
if (activeOut != nullptr) {
for (auto& task : nextTasks) {
if (!task.active) {
continue;
}
for (const auto& wsm : nextWorkspaces) {
if (workspaceHostOutput(wsm) != activeOut || !wsm.workspace.active) {
continue;
}
task.workspaceKey = wsm.key;
break;
}
break;
}
}
if (!activeWorkspaceKey.empty()) {
for (auto& task : nextTasks) {
if (task.active) {
task.workspaceKey = activeWorkspaceKey;
} else {
std::string activeWorkspaceKey;
for (const auto& workspace : nextWorkspaces) {
if (workspace.workspace.active) {
activeWorkspaceKey = workspace.key;
break;
}
}
if (!activeWorkspaceKey.empty()) {
for (auto& task : nextTasks) {
if (task.active) {
task.workspaceKey = activeWorkspaceKey;
break;
}
}
}
}
}
@@ -937,7 +1031,7 @@ void TaskbarWidget::openTaskContextMenu(const TaskModel& task, InputArea& area)
return;
}
const auto windows = m_platform.windowsForApp(task.idLower, task.startupWmClassLower, m_output);
const auto windows = m_platform.windowsForApp(task.idLower, task.startupWmClassLower, toplevelOutputFilter());
m_contextMenuHandles.clear();
m_contextMenuHandles.reserve(windows.size());
for (const auto& window : windows) {
@@ -1121,7 +1215,8 @@ bool TaskbarWidget::modelsEqual(const std::vector<TaskModel>& tasks,
const auto& a = workspaces[i].workspace;
const auto& b = m_workspaces[i].workspace;
if (a.id != b.id || a.name != b.name || a.active != b.active || a.urgent != b.urgent || a.occupied != b.occupied ||
workspaces[i].key != m_workspaces[i].key || workspaces[i].label != m_workspaces[i].label) {
workspaces[i].key != m_workspaces[i].key || workspaces[i].label != m_workspaces[i].label ||
workspaces[i].hostOutput != m_workspaces[i].hostOutput) {
return false;
}
}
@@ -1200,5 +1295,39 @@ void TaskbarWidget::activateAdjacentWorkspace(int direction) {
targetIndex = current - 1;
}
m_platform.activateWorkspace(m_output, m_workspaces[targetIndex].workspace);
const auto& targetWs = m_workspaces[targetIndex];
m_platform.activateWorkspace(workspaceHostOutput(targetWs), targetWs.workspace);
}
wl_output* TaskbarWidget::toplevelOutputFilter() const noexcept { return m_showAllOutputs ? nullptr : m_output; }
bool TaskbarWidget::useMultiOutputWorkspaceKeys() const noexcept {
if (!m_showAllOutputs) {
return false;
}
std::size_t n = 0;
for (const auto& wo : m_platform.outputs()) {
if (wo.output != nullptr) {
++n;
}
}
return n > 1;
}
std::string TaskbarWidget::workspaceKeyPrefixForOutput(wl_output* out) const {
if (!useMultiOutputWorkspaceKeys()) {
return {};
}
std::string connector;
if (const auto* info = m_platform.findOutputByWl(out); info != nullptr) {
connector = info->connectorName;
}
if (!connector.empty()) {
return connector + '\x1e';
}
return "display\x1e";
}
wl_output* TaskbarWidget::workspaceHostOutput(const WorkspaceModel& model) const noexcept {
return model.hostOutput != nullptr ? model.hostOutput : m_output;
}
+8 -1
View File
@@ -20,7 +20,8 @@ struct PointerEvent;
class TaskbarWidget : public Widget {
public:
TaskbarWidget(CompositorPlatform& platform, wl_output* output, bool groupByWorkspace, std::string barPosition);
TaskbarWidget(CompositorPlatform& platform, wl_output* output, bool groupByWorkspace, bool showAllOutputs,
std::string barPosition);
~TaskbarWidget() override;
void create() override;
@@ -48,6 +49,7 @@ private:
Workspace workspace;
std::string key;
std::string label;
wl_output* hostOutput = nullptr;
};
struct PendingWorkspaceTransition {
@@ -71,10 +73,15 @@ private:
void openTaskContextMenu(const TaskModel& task, InputArea& area);
void activateAdjacentWorkspace(int direction);
[[nodiscard]] bool activeWorkspaceIndex(std::size_t& index) const;
[[nodiscard]] wl_output* toplevelOutputFilter() const noexcept;
[[nodiscard]] bool useMultiOutputWorkspaceKeys() const noexcept;
[[nodiscard]] std::string workspaceKeyPrefixForOutput(wl_output* out) const;
[[nodiscard]] wl_output* workspaceHostOutput(const WorkspaceModel& model) const noexcept;
CompositorPlatform& m_platform;
wl_output* m_output = nullptr;
bool m_groupByWorkspace = false;
bool m_showAllOutputs = false;
std::string m_barPosition;
bool m_rebuildPending = true;
bool m_vertical = false;
@@ -388,6 +388,7 @@ namespace settings {
add(boolSpec("show_label", true));
} else if (type == "taskbar") {
add(boolSpec("group_by_workspace", false));
add(boolSpec("show_all_outputs", false));
} else if (type == "tray") {
add(stringListSpec("hidden"));
add(stringListSpec("pinned"));