Files
dotfiles/dotfiles/.config/quickshell/modules/bar/PinnedTray.qml
thomas 4bd3d327c6 Feature: Seafile starts on login and is pinned in the tray
Launches seafile-applet from startup.lua and adds it to PinnedTray between Bitwarden and Sunshine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:49:04 +01:00

111 lines
3.7 KiB
QML

import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import "../../config"
RowLayout {
id: root
spacing: Appearance.spacing * 1.
readonly property var pinned: [
{
name: "Bitwarden",
icon: Qt.resolvedUrl("../../assets/bitwarden.png"),
binary: "bitwarden",
launch: ["bitwarden"],
secondary: ["pkill", "bitwarden"]
},
{
name: "Seafile",
icon: "seafile",
binary: "seafile-applet",
launch: ["seafile-applet"],
secondary: ["pkill", "seafile-applet"]
},
{
name: "Sunshine",
icon: Qt.resolvedUrl("../../assets/sunshine.svg"),
binary: "sunshine",
launch: ["systemctl", "--user", "start", "app-dev.lizardbyte.app.Sunshine.service"],
secondary: ["xdg-open", "https://xol.twhomelab.local:47990"],
},
]
function findTrayItem(name) {
const needle = name.toLowerCase()
for (const item of SystemTray.items.values) {
if (item.id.toLowerCase().includes(needle) || item.title.toLowerCase().includes(needle))
return item
}
return null
}
Repeater {
model: root.pinned
delegate: Item {
id: slot
required property var modelData
readonly property var trayItem: root.findTrayItem(modelData.name)
readonly property bool running: trayItem !== null
property bool installed: false // Application installation check
visible: slot.installed
implicitWidth: Appearance.iconSize
implicitHeight: Appearance.iconSize
Process {
running: true
command: ["which", slot.modelData.binary]
onExited: (exitCode) => slot.installed = exitCode === 0
}
IconImage {
anchors.fill: parent
// modelData.icon is a QUrl when it comes from
// Qt.resolvedUrl() (Bitwarden/Sunshine's local assets), not
// a QString — coerce to string before using String methods
// on it, or this throws instead of ever matching.
source: slot.running
? slot.trayItem.icon
: (String(slot.modelData.icon).includes("://")
? slot.modelData.icon
: Quickshell.iconPath(slot.modelData.icon))
opacity: slot.running ? 1.0 : 0.5
}
Process { id: launcher }
Process { id: secondaryProc }
MouseArea {
anchors.fill: parent
anchors.margins: -4
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
if (slot.modelData.secondary) {
if (!secondaryProc.running) {
secondaryProc.command = slot.modelData.secondary
secondaryProc.running = true
}
} else if (slot.running) {
slot.trayItem.secondaryActivate()
}
} else if (slot.running) {
slot.trayItem.activate()
} else if (!launcher.running) {
launcher.command = slot.modelData.launch
launcher.running = true
}
}
}
}
}
}