4.7 KiB
TODO: App indicator (system tray) improvements
Current state
modules/Tray.qml renders tray icons and handles left/right click, but:
- Right-click context menu is broken — calls
modelData.display(root.bar, mouse.x, mouse.y)which is not a valid QuickshellSystemTrayItemmethod and does nothing. - No tooltips —
SystemTrayItemexposestooltipTitleandtooltipDescriptionbut they are not shown. - Secondary activation — right-click currently tries to show a menu; for items without a menu,
secondaryActivate()should be called instead.
Quickshell API reference
// Key SystemTrayItem properties:
modelData.id // string — unique app identifier
modelData.icon // url — icon source
modelData.title // string — display name
modelData.tooltipTitle // string — tooltip heading
modelData.tooltipDescription // string — tooltip body
modelData.menu // QsMenuHandle | null — context menu (null if app has none)
// Methods:
modelData.activate() // primary action (left click)
modelData.secondaryActivate() // secondary action (middle click or right click when no menu)
// Menu traversal:
QsMenuOpener { id: opener; menu: someQsMenuHandle }
// opener.children → list of QsMenuEntry
// entry.text, entry.icon, entry.enabled, entry.isSeparator
// entry.hasChildren → true if entry is a submenu
// entry.triggered() → invoke the action
// entry as QsMenuHandle → pass back to QsMenuOpener for submenu
What needs to be done
1. Tooltips (easy)
Add a HoverHandler + flyout to each tray icon delegate.
Use FlyoutState the same way BarSection does it:
HoverHandler {
onHoveredChanged: {
const tip = [iconItem.modelData.tooltipTitle, iconItem.modelData.tooltipDescription]
.filter(s => s).join("\n") || iconItem.modelData.title;
if (hovered && tip) {
M.FlyoutState.text = tip;
M.FlyoutState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x;
M.FlyoutState.visible = true;
} else if (!hovered) {
M.FlyoutState.visible = false;
}
}
}
2. Context menus (harder)
The challenge: menus need their own window/panel to render in (can't render inside the bar's PanelWindow without clipping). Celestia-shell solves this with a dedicated popout PanelWindow per screen.
Approach A — Extend the Flyout system (recommended)
Extend FlyoutState to optionally carry a QsMenuHandle instead of just text. When a tray icon is right-clicked, set FlyoutState.menuHandle = modelData.menu. In Flyout.qml, detect whether to render the text tooltip or a menu:
// FlyoutState additions:
property var menuHandle: null // QsMenuHandle or null
property bool isMenu: false
// In Flyout.qml: show either the text box or the menu panel
Loader {
active: M.FlyoutState.isMenu && M.FlyoutState.menuHandle !== null
sourceComponent: TrayMenuPanel { handle: M.FlyoutState.menuHandle }
}
TrayMenuPanel would use QsMenuOpener to iterate the menu, render items as a Column of clickable rows, and handle submenus recursively (push/pop a StackView like celestia-shell does, or use a simple recursive Loader).
Approach B — Per-icon popup window
Each tray delegate instantiates a small PanelWindow on demand:
// Only created on right-click
Loader {
id: menuLoader
active: false
sourceComponent: TrayMenuWindow {
handle: iconItem.modelData.menu
screen: root.bar.screen
anchorX: ...
}
}
MouseArea {
onClicked: mouse => {
if (mouse.button === Qt.RightButton && iconItem.modelData.menu)
menuLoader.active = true;
}
}
Simpler isolation but harder to manage dismiss/close (clicking outside).
3. Right-click fallback
If modelData.menu === null, right-click should call modelData.secondaryActivate() rather than trying to open a menu.
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
modelData.activate();
} else if (mouse.button === Qt.RightButton) {
if (modelData.menu)
// open menu (Approach A or B above)
else
modelData.secondaryActivate();
}
}
Reference
Celestia-shell implements a full menu renderer at:
modules/bar/popouts/TrayMenu.qml—StackView-based menu with submenu push/pop, separator support, icon+label rows, back button for submenusmodules/bar/components/TrayItem.qml— per-item delegate- The menu is shown via
popouts.currentName = "traymenu{index}"in the popout panel system
The key QML types needed: QsMenuOpener, QsMenuHandle, QsMenuEntry — all from import Quickshell.