From dc698e5df0ed9b69ec7f6773920a053cec910de9 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 14 Aug 2026 08:23:45 +0200 Subject: [PATCH] Suppress LocalSend's redundant tray item (#6852) LocalSend registers an Ayatana item with no ItemIsMenu and no Activate handler, so its primary click is a silent no-op and the menu offers only Open and Quit. Share > Receive already opens it, so drop the item the way Dropbox's is dropped when its dedicated widget owns the surface. Closes #6838 Co-authored-by: Claude Opus 5 --- shell/plugins/bar/widgets/Tray.qml | 6 +++--- shell/plugins/bar/widgets/TrayModel.js | 20 ++++++++++++-------- test/shell.d/tray-test.sh | 14 ++++++++------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/shell/plugins/bar/widgets/Tray.qml b/shell/plugins/bar/widgets/Tray.qml index 77657a30..d0d07f57 100644 --- a/shell/plugins/bar/widgets/Tray.qml +++ b/shell/plugins/bar/widgets/Tray.qml @@ -159,9 +159,9 @@ BarWidget { return "drawer" } - function ownedByDedicatedWidget(item) { + function ownedByOmarchy(item) { var layout = root.bar && root.bar.layoutConfig ? root.bar.layoutConfig : null - return TrayModel.ownedByDedicatedWidget(item, layout) + return TrayModel.ownedByOmarchy(item, layout) } function bucket(category) { @@ -170,7 +170,7 @@ BarWidget { for (var i = 0; i < values.length; i++) { var item = values[i] if (item.status === Status.Passive) continue - if (ownedByDedicatedWidget(item)) continue + if (ownedByOmarchy(item)) continue if (category === "all") { result.push(item) continue diff --git a/shell/plugins/bar/widgets/TrayModel.js b/shell/plugins/bar/widgets/TrayModel.js index a9da13c4..98a41da6 100644 --- a/shell/plugins/bar/widgets/TrayModel.js +++ b/shell/plugins/bar/widgets/TrayModel.js @@ -2,11 +2,11 @@ function text(value) { return String(value || "").toLowerCase() } -function isDropboxTrayItem(item) { +function itemNamed(item, name) { if (!item) return false - return text(item.id).indexOf("dropbox") !== -1 - || text(item.title).indexOf("dropbox") !== -1 - || text(item.tooltipTitle).indexOf("dropbox") !== -1 + return text(item.id).indexOf(name) !== -1 + || text(item.title).indexOf(name) !== -1 + || text(item.tooltipTitle).indexOf(name) !== -1 } function entryId(entry) { @@ -30,15 +30,19 @@ function layoutHasWidget(layout, id) { return false } -function ownedByDedicatedWidget(item, layout) { - return layoutHasWidget(layout, "omarchy.dropbox") && isDropboxTrayItem(item) +// LocalSend's item shows no state, offers only Open and Quit, and its primary +// click is a no-op, so Share > Receive is the whole surface. Hiding it by hand +// doesn't stick either: LocalSend picks a fresh tray id every launch. +function ownedByOmarchy(item, layout) { + return itemNamed(item, "localsend") + || (layoutHasWidget(layout, "omarchy.dropbox") && itemNamed(item, "dropbox")) } if (typeof module !== "undefined") { module.exports = { - isDropboxTrayItem: isDropboxTrayItem, + itemNamed: itemNamed, entryId: entryId, layoutHasWidget: layoutHasWidget, - ownedByDedicatedWidget: ownedByDedicatedWidget + ownedByOmarchy: ownedByOmarchy } } diff --git a/test/shell.d/tray-test.sh b/test/shell.d/tray-test.sh index d2b2a25e..5d10fc17 100644 --- a/test/shell.d/tray-test.sh +++ b/test/shell.d/tray-test.sh @@ -7,9 +7,10 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" run_node_test "tray model helpers" <<'JS' const tray = requireFromRoot('shell/plugins/bar/widgets/TrayModel.js') -assert(tray.isDropboxTrayItem({ id: 'dropbox-client' }), 'tray detects dropbox item ids') -assert(tray.isDropboxTrayItem({ title: 'Dropbox' }), 'tray detects dropbox item titles') -assert(!tray.isDropboxTrayItem({ id: 'nextcloud' }), 'tray ignores non-dropbox items') +assert(tray.itemNamed({ id: 'dropbox-client' }, 'dropbox'), 'tray matches item ids') +assert(tray.itemNamed({ title: 'Dropbox' }, 'dropbox'), 'tray matches item titles') +assert(tray.itemNamed({ tooltipTitle: 'LocalSend' }, 'localsend'), 'tray matches item tooltips') +assert(!tray.itemNamed({ id: 'nextcloud' }, 'dropbox'), 'tray ignores items named for something else') const layout = { left: [{ id: 'omarchy.menu' }], @@ -18,7 +19,8 @@ const layout = { } assert(tray.layoutHasWidget(layout, 'omarchy.dropbox'), 'tray finds dedicated dropbox widget in layout') -assert(tray.ownedByDedicatedWidget({ id: 'dropbox' }, layout), 'tray suppresses dropbox when dedicated widget is in bar') -assert(!tray.ownedByDedicatedWidget({ id: 'dropbox' }, { left: [], center: [], right: [] }), 'tray keeps dropbox when dedicated widget is absent') -assert(!tray.ownedByDedicatedWidget({ id: 'nextcloud' }, layout), 'tray keeps unrelated tray items') +assert(tray.ownedByOmarchy({ id: 'dropbox' }, layout), 'tray suppresses dropbox when dedicated widget is in bar') +assert(!tray.ownedByOmarchy({ id: 'dropbox' }, { left: [], center: [], right: [] }), 'tray keeps dropbox when dedicated widget is absent') +assert(tray.ownedByOmarchy({ id: 'qlBCprNUqU', title: 'localsend' }, { left: [], center: [], right: [] }), 'tray suppresses localsend regardless of layout') +assert(!tray.ownedByOmarchy({ id: 'nextcloud' }, layout), 'tray keeps unrelated tray items') JS