From 91cf1130e0d738c0e4635947fe4787045d496577 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Sat, 15 Aug 2026 22:25:11 +0100 Subject: [PATCH] Show the current tailnet next to the bar icon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a label to the left of the mark naming the tailnet the selected profile belongs to: the second-from-last label of the domain, so offscale.pastilhas.dev reads "pastilhas" and headscale.pertento.ai reads "pertento". Taking the second-level domain rather than a fixed index keeps the distinctive part if a tailnet ever has four labels. Read from the accounts list the service already polls, so no extra process runs for it. BarIconButton is icon-only by design (labelVisible: false, fixedWidth: slotSize, and its `text` feeds a glyph rather than a label), so the label could not live inside it — the bar widget is now a Row of label + button, and its footprint comes from the row. Coloured with the brand yellow, dimmed by opacity rather than by darkening when Tailscale is off: Qt.darker on yellow shifts the hue to olive instead of quietening it. Co-Authored-By: Claude Opus 5 --- Panel.qml | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Panel.qml b/Panel.qml index e377d83..f569a21 100644 --- a/Panel.qml +++ b/Panel.qml @@ -329,8 +329,31 @@ Panel { headerIndex = 0 } - implicitWidth: button.implicitWidth - implicitHeight: button.implicitHeight + implicitWidth: barContent.implicitWidth + implicitHeight: barContent.implicitHeight + + // Short name of the tailnet the selected profile belongs to, shown in the bar + // beside the icon. Takes the second-from-last label of the domain, which is + // the distinctive part: + // + // headscale.pertento.ai -> pertento + // offscale.pastilhas.dev -> pastilhas + // + // Reads from the accounts list the service already polls, so no extra process + // runs for this. + readonly property string tailnetName: { + var list = tailscale.accounts + for (var i = 0; i < list.length; i++) { + if (list[i].selected !== true) continue + var parts = String(list[i].tailnet || "").split(".") + var clean = [] + for (var j = 0; j < parts.length; j++) if (parts[j] !== "") clean.push(parts[j]) + if (clean.length >= 2) return clean[clean.length - 2] + if (clean.length === 1) return clean[0] + return "" + } + return "" + } onOpenedChanged: if (opened) { cursorActive = false @@ -372,9 +395,31 @@ Panel { function status(): string { return tailscale.statusText } } + // Tailnet label to the left of the mark. BarIconButton is icon-only by design + // (labelVisible: false, fixedWidth: slotSize, and its `text` feeds a glyph + // rather than a label), so the name sits beside it in a Row instead. + Row { + id: barContent + anchors.fill: parent + spacing: Style.space(4) + + Text { + id: tailnetLabel + visible: text !== "" + text: root.tailnetName + // Brand yellow. Dimmed by opacity rather than by darkening the colour + // when Tailscale is off — Qt.darker on yellow slides the hue to olive + // instead of just quietening it, the same trap as the IP/DNS subtitle. + color: root.accent + opacity: tailscale.active ? 1.0 : 0.5 + font.family: root.fontFamily + font.pixelSize: Style.bar.iconFont + anchors.verticalCenter: parent.verticalCenter + } + BarIconButton { id: button - anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter bar: root.bar // BarIconButton pins its icon Loader to an `opticalCanvas` sized by // Style.bar.iconCanvas (16 * fontScale ~= 27px here). Growing iconSize @@ -403,6 +448,7 @@ Panel { else root.toggle() } } + } KeyboardPanel { id: panel