# TODO: App indicator (system tray) improvements ## Current state `modules/Tray.qml` renders tray icons and handles left/right click, but: 1. **Right-click context menu is broken** — calls `modelData.display(root.bar, mouse.x, mouse.y)` which is not a valid Quickshell `SystemTrayItem` method and does nothing. 2. **No tooltips** — `SystemTrayItem` exposes `tooltipTitle` and `tooltipDescription` but they are not shown. 3. **Secondary activation** — right-click currently tries to show a menu; for items without a menu, `secondaryActivate()` should be called instead. --- ## Quickshell API reference ```qml // 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: ```qml 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: ```qml // 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: ```qml // 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. ```qml 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 submenus - `modules/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`.