Show the current tailnet next to the bar icon

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 22:25:11 +01:00
co-authored by Claude Opus 5
parent ca341c0e71
commit 91cf1130e0
+49 -3
View File
@@ -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