Rebrand to Offscale and rework the panel header

Forked from omarchy.tailscale at the packaged commit (omarchy-dev
4.0.0.r1744.gf002044). The `tailscale` branch holds the pristine upstream
subtree history so upstream changes can still be merged in; `master` is
this fork.

- Rename the plugin to pastilhas.offscale: manifest id/name, moduleName
  and ipcTarget, and display strings. Deliberately NOT renamed: the
  lowercase "tailscale" CLI invocations, the TailscaleIPs/TailscaleIPv6
  JSON fields from `tailscale status --json`, and
  authorizeTailscaleOperator. Renaming those loads fine but silently
  breaks peer parsing.
- TailscaleIcon.qml -> OffscaleIcon.qml, now rendering offscale-logo.png
  instead of the 3x3 dot grid, keeping the crossed/warning states.
- Add OffscaleHero.qml: a copy of qs.Ui.PanelHero with a titleComponent
  slot, so the wordmark (offscale-text.png) can replace the title text.
  Otherwise byte-identical to the original, so it stays diffable.
- Drop the cycling hero phrases entirely - the array, the 2.8s timer and
  the metaOpacity fade.
- Show the local machine as a PeerRow of its own, so it gets the same
  layout and copy menu as tailnet peers. Adds `navigable` to PeerRow so
  that row stays out of the peers keyboard cursor.
- Enlarge the bar icon: BarIconButton pins its icon to opticalCanvas, so
  opticalSize/slotSize are overridden alongside iconSize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 17:04:41 +01:00
co-authored by Claude Opus 5
parent ba90129612
commit 0912b2b50d
9 changed files with 275 additions and 102 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ function hasFileSharing(self) {
return false return false
} }
// Tailscale grades every peer itself — offline, wrong owner, an OS without // Offscale grades every peer itself — offline, wrong owner, an OS without
// Taildrop, no peer API — so take its word when the status carries one, and // Taildrop, no peer API — so take its word when the status carries one, and
// fall back to same-owner for daemons too old to say. // fall back to same-owner for daemons too old to say.
function isTaildropTarget(peer, selfUserId) { function isTaildropTarget(peer, selfUserId) {
+132
View File
@@ -0,0 +1,132 @@
import QtQuick
import qs.Commons
import qs.Ui
// Local copy of qs.Ui.PanelHero with one addition: `titleComponent`.
//
// The stock hero renders `title` as a plain Text and offers no slot to put
// anything else there, so showing the Offscale wordmark instead of a string
// needed either editing a shared /usr/share component (never) or owning a
// copy here. Everything else is unchanged from the original, so diffing
// against /usr/share/omarchy/shell/Ui/PanelHero.qml shows exactly what we did.
Item {
id: root
property Component iconComponent: null
property string title: ""
// When set, replaces the title Text. `title` is still used as the accessible
// / fallback string, so callers should keep setting it.
property Component titleComponent: null
property string meta: ""
property string detail: ""
property color foreground: Color.foreground
property string fontFamily: Style.font.family
property real iconSize: Style.font.display
property real iconOpacity: 1.0
property alias metaOpacity: metaText.opacity
// Optional control pinned to the trailing edge of the hero — a ToggleSwitch,
// a small button. The hero centers it against the labels and reserves the
// space itself, so callers never do the geometry.
property Component trailingControl: null
readonly property color dim: Qt.darker(foreground, 1.4)
readonly property real trailingInset: trailingLoader.item && trailingLoader.item.visible ? trailingLoader.width + Style.space(12) : 0
width: parent ? parent.width : implicitWidth
implicitHeight: Math.max(iconLoader.implicitHeight, heroLabels.implicitHeight, trailingLoader.implicitHeight)
Loader {
id: iconLoader
sourceComponent: root.iconComponent
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
opacity: root.iconOpacity
}
Column {
id: heroLabels
anchors.left: iconLoader.right
anchors.leftMargin: Style.space(14)
anchors.right: parent.right
anchors.rightMargin: root.trailingInset
anchors.verticalCenter: parent.verticalCenter
// Stock PanelHero uses space(2) = 3px, which is fine for two lines of text
// but too tight under an image wordmark. space(5) = 8px gives the machine
// name room to read as its own line.
spacing: Style.space(5)
Row {
id: titleRow
visible: root.title !== "" || root.titleComponent !== null || detailPill.visible
width: parent.width
// Either the custom title component or the stock Text — never both.
Loader {
id: titleSlot
visible: root.titleComponent !== null
active: root.titleComponent !== null
sourceComponent: root.titleComponent
anchors.verticalCenter: parent.verticalCenter
}
Text {
id: titleText
visible: root.titleComponent === null && root.title !== ""
text: root.title
width: visible ? Math.min(implicitWidth, Math.max(0, parent.width - (detailPill.visible ? detailPill.implicitWidth + Style.space(8) : 0))) : 0
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.title
font.bold: true
elide: Text.ElideRight
}
Item {
width: Math.max(0, parent.width - titleSlot.width - titleText.width - detailPill.implicitWidth)
height: 1
}
BorderSurface {
id: detailPill
visible: root.detail !== ""
implicitWidth: detailText.implicitWidth + Style.space(10)
implicitHeight: detailText.implicitHeight + Style.space(4)
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
radius: Style.cornerRadius
Text {
id: detailText
anchors.centerIn: parent
text: root.detail
color: root.dim
font.family: root.fontFamily
font.pixelSize: Style.font.body
font.bold: true
}
}
}
Text {
id: metaText
width: parent.width
text: root.meta.toUpperCase()
visible: text !== ""
color: root.dim
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
font.letterSpacing: 1.2
elide: Text.ElideRight
}
}
Loader {
id: trailingLoader
sourceComponent: root.trailingControl
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
+25 -23
View File
@@ -16,23 +16,31 @@ Item {
implicitWidth: iconSize implicitWidth: iconSize
implicitHeight: iconSize implicitHeight: iconSize
readonly property real dotSize: Math.max(2, root.iconSize * 0.24) // The Offscale mark, replacing the stock 3x3 Tailscale dot grid.
readonly property real mid: (root.iconSize - dotSize) / 2 //
readonly property real end: root.iconSize - dotSize // offscale-logo.png lives beside this file rather than being referenced from
// ~/projects, so the plugin stays self-contained and keeps working if that
// Native rendering of the Tailscale mark from the SVG: a 3×3 dot grid // tree moves. It is the source PNG trimmed of its transparent margin, padded
// with the inactive dots faded. This avoids Qt SVG/effect rendering quirks // back to square so it centres, and scaled 1254 -> 256 (1.1 MB -> 77 KB).
// in tiny bar slots while keeping the official silhouette. //
Dot { x: 0; y: 0; opacity: 0.24 } // sourceSize caps the decode at 256 regardless of how large iconSize gets, so
Dot { x: root.mid; y: 0; opacity: 0.24 } // the bar slot never decodes more pixels than it needs; mipmap keeps the
Dot { x: root.end; y: 0; opacity: 0.24 } // downscale to ~27px clean instead of aliased.
Dot { x: 0; y: root.mid; opacity: 1.0 } Image {
Dot { x: root.mid; y: root.mid; opacity: 1.0 } id: logo
Dot { x: root.end; y: root.mid; opacity: 1.0 } anchors.fill: parent
Dot { x: 0; y: root.end; opacity: 0.24 } source: Qt.resolvedUrl("offscale-logo.png")
Dot { x: root.mid; y: root.end; opacity: 1.0 } sourceSize.width: 256
Dot { x: root.end; y: root.end; opacity: 0.24 } sourceSize.height: 256
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
// Dim the mark when Tailscale is down, matching how the stock icon faded
// its inactive dots.
opacity: root.crossed ? 0.45 : 1.0
}
// Diagonal strike when disconnected.
Rectangle { Rectangle {
visible: root.crossed visible: root.crossed
anchors.centerIn: parent anchors.centerIn: parent
@@ -43,6 +51,7 @@ Item {
rotation: -45 rotation: -45
} }
// "!" badge when a login is needed.
BorderSurface { BorderSurface {
visible: root.warning visible: root.warning
width: Math.max(7, parent.width * 0.42) width: Math.max(7, parent.width * 0.42)
@@ -62,11 +71,4 @@ Item {
font.bold: true font.bold: true
} }
} }
component Dot: Rectangle {
width: root.dotSize
height: root.dotSize
radius: width / 2
color: root.color
}
} }
+90 -54
View File
@@ -8,8 +8,8 @@ import qs.Ui
Panel { Panel {
id: root id: root
moduleName: "omarchy.tailscale" moduleName: "pastilhas.offscale"
ipcTarget: "omarchy.tailscale" ipcTarget: "pastilhas.offscale"
manageIpc: false manageIpc: false
property string focusSection: "header" property string focusSection: "header"
@@ -22,20 +22,6 @@ Panel {
property bool copyMenuOpen: false property bool copyMenuOpen: false
property bool mullvadPickerOpen: false property bool mullvadPickerOpen: false
property string mullvadQuery: "" property string mullvadQuery: ""
property int phraseIndex: 0
readonly property var activePhrases: [
"Encrypting connections",
"Sending secrets",
"Guarding wires",
"Braiding packets",
"Polishing tunnels",
"Hiding routes",
"Sealing ports",
"Sorting tailnets",
"Shuffling keys",
"Watching machines"
]
readonly property string heroPhraseText: activePhrases[phraseIndex % activePhrases.length]
readonly property color foreground: bar ? bar.foreground : Color.foreground readonly property color foreground: bar ? bar.foreground : Color.foreground
readonly property color urgent: bar ? bar.urgent : Color.urgent readonly property color urgent: bar ? bar.urgent : Color.urgent
@@ -52,7 +38,7 @@ Panel {
// "header" stays navigable, but an absent CLI leaves nothing to highlight. // "header" stays navigable, but an absent CLI leaves nothing to highlight.
readonly property bool headerHasCursor: cursorActive && focusSection === "header" && tailscale.installed readonly property bool headerHasCursor: cursorActive && focusSection === "header" && tailscale.installed
readonly property color iconColor: tailscale.active ? foreground : dim readonly property color iconColor: tailscale.active ? foreground : dim
readonly property string toggleHint: tailscale.active ? "Turn Tailscale off" : (tailscale.needsLogin ? "Authorize this device" : "Turn Tailscale on") readonly property string toggleHint: tailscale.active ? "Turn Offscale off" : (tailscale.needsLogin ? "Authorize this device" : "Turn Offscale on")
readonly property color barIconColor: tailscale.active ? barForeground : Qt.darker(barForeground, 1.55) readonly property color barIconColor: tailscale.active ? barForeground : Qt.darker(barForeground, 1.55)
readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent" readonly property color hoverFill: bar ? Style.hoverFillFor(bar.foreground, Color.accent) : "transparent"
readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent" readonly property color selectedFill: bar ? Style.selectedFillFor(bar.foreground, Color.accent) : "transparent"
@@ -240,7 +226,7 @@ Panel {
function activateCursor() { function activateCursor() {
ensureCursor() ensureCursor()
if (focusSection === "header") { if (focusSection === "header") {
tailscale.toggleTailscale() tailscale.toggleOffscale()
} else if (focusSection === "auth") { } else if (focusSection === "auth") {
tailscale.authorizeTailscaleOperator() tailscale.authorizeTailscaleOperator()
} else if (focusSection === "accounts") { } else if (focusSection === "accounts") {
@@ -373,7 +359,7 @@ Panel {
function refresh(): string { tailscale.refresh(); return "ok" } function refresh(): string { tailscale.refresh(); return "ok" }
function up(): string { tailscale.loginOrUp(); return "ok" } function up(): string { tailscale.loginOrUp(); return "ok" }
function down(): string { tailscale.down(); return "ok" } function down(): string { tailscale.down(); return "ok" }
function toggleTailscale(): string { tailscale.toggleTailscale(); return "ok" } function toggleOffscale(): string { tailscale.toggleOffscale(); return "ok" }
function status(): string { return tailscale.statusText } function status(): string { return tailscale.statusText }
} }
@@ -381,11 +367,20 @@ Panel {
id: button id: button
anchors.fill: parent anchors.fill: parent
bar: root.bar bar: root.bar
// BarIconButton pins its icon Loader to an `opticalCanvas` sized by
// Style.bar.iconCanvas (16 * fontScale ~= 27px here). Growing iconSize
// alone can't get past that box, so widen the canvas first. slotSize is
// raised to match so the widget still reserves room for it in the bar.
opticalSize: Style.space(21)
slotSize: Style.space(23)
iconComponent: Component { iconComponent: Component {
Item { Item {
TailscaleIcon { OffscaleIcon {
anchors.centerIn: parent anchors.centerIn: parent
iconSize: Style.space(11) // Stock was space(11) = 18px at base-size 20 — noticeably smaller
// than the neighbouring widgets. space(19) ~= 32px, inside the
// widened canvas above. centerIn means it grows from the centre.
iconSize: Style.space(19)
color: root.barIconColor color: root.barIconColor
badgeColor: root.urgent badgeColor: root.urgent
crossed: !tailscale.active && !tailscale.needsLogin crossed: !tailscale.active && !tailscale.needsLogin
@@ -394,7 +389,7 @@ Panel {
} }
} }
onPressed: function(buttonCode) { onPressed: function(buttonCode) {
if (buttonCode === Qt.RightButton) tailscale.toggleTailscale() if (buttonCode === Qt.RightButton) tailscale.toggleOffscale()
else if (buttonCode === Qt.MiddleButton) tailscale.refresh() else if (buttonCode === Qt.MiddleButton) tailscale.refresh()
else root.toggle() else root.toggle()
} }
@@ -422,7 +417,7 @@ Panel {
onCloseRequested: root.close() onCloseRequested: root.close()
onTabRequested: function(direction) { root.switchPanel(direction) } onTabRequested: function(direction) { root.switchPanel(direction) }
onTextKey: function(t) { onTextKey: function(t) {
if (t === "t" || t === "T") tailscale.toggleTailscale() if (t === "t" || t === "T") tailscale.toggleOffscale()
else if (t === "c" || t === "C") tailscale.copyPeerIp(root.selectedPeer()) else if (t === "c" || t === "C") tailscale.copyPeerIp(root.selectedPeer())
else if (t === "n" || t === "N") tailscale.copyPeerName(root.selectedPeer()) else if (t === "n" || t === "N") tailscale.copyPeerName(root.selectedPeer())
else if (t === "d" || t === "D") tailscale.copyPeerDnsName(root.selectedPeer()) else if (t === "d" || t === "D") tailscale.copyPeerDnsName(root.selectedPeer())
@@ -454,17 +449,44 @@ Panel {
readonly property bool ringVisible: root.headerHasCursor readonly property bool ringVisible: root.headerHasCursor
function focusHero() { root.setHeaderCursor() } function focusHero() { root.setHeaderCursor() }
PanelHero { OffscaleHero {
id: hero id: hero
width: parent.width width: parent.width
title: tailscale.installed ? (tailscale.selfName || "Tailscale") : "Tailscale" // The wordmark replaces the title text; `title` is kept as the
meta: tailscale.active ? root.heroPhraseText : "Tailscale is disconnected" // fallback string. The machine name moves down into `meta`, where
// the cycling phrases used to be.
title: tailscale.installed ? (tailscale.selfName || "Offscale") : "Offscale"
titleComponent: Component {
Image {
source: Qt.resolvedUrl("offscale-text.png")
// Asset is 773x128 (the source trimmed and scaled); cap the
// decode there and let the width follow the aspect ratio.
sourceSize.height: 128
fillMode: Image.PreserveAspectFit
smooth: true
mipmap: true
// Sized against the title font so it tracks base-size.
// 1.25 read a touch heavy next to the icon; 1.05 sits closer
// to the weight of the text it replaced.
height: Style.font.title * 1.05
width: height * (773 / 128)
}
}
// No meta line: the header is the icon and the wordmark, centred
// against each other. metaText hides itself when empty, so the
// label Column collapses to the single title row and its
// verticalCenter lines up with the icon's.
//
// Losing this line also drops the "Offscale is disconnected"
// text, but that state is still carried by the icon (crossed and
// faded to 45%) and by the toggle switch on the trailing edge.
meta: ""
foreground: root.foreground foreground: root.foreground
fontFamily: root.fontFamily fontFamily: root.fontFamily
iconOpacity: tailscale.active ? 1.0 : 0.5 iconOpacity: tailscale.active ? 1.0 : 0.5
// Status only — the switch owns toggling, mouse and keyboard alike. // Status only — the switch owns toggling, mouse and keyboard alike.
iconComponent: Component { iconComponent: Component {
TailscaleIcon { OffscaleIcon {
iconSize: Style.font.display iconSize: Style.font.display
color: root.iconColor color: root.iconColor
badgeColor: root.urgent badgeColor: root.urgent
@@ -485,7 +507,7 @@ Panel {
hasCursor: header.ringVisible hasCursor: header.ringVisible
foreground: hero.foreground foreground: hero.foreground
onHovered: function(on) { if (on) header.focusHero() } onHovered: function(on) { if (on) header.focusHero() }
onToggled: tailscale.toggleTailscale() onToggled: tailscale.toggleOffscale()
PanelToolTip { PanelToolTip {
visible: powerSwitch.containsMouse visible: powerSwitch.containsMouse
@@ -497,6 +519,36 @@ Panel {
} }
} }
// The local machine, rendered with the same PeerRow used for the
// tailnet machines below — so it gets the identical layout, OS icon
// and copy menu (name / DNS / IP) for free. No section header: the
// row is the machine.
//
// The service exposes self as flat strings rather than a peer object,
// so the peer shape is assembled here. IPv6 is not among them, so
// that copy option simply doesn't appear. `navigable: false` keeps it
// out of the peers keyboard cursor, and canSendFiles() is false for
// self, so the Taildrop button hides itself.
PanelSeparator {
visible: tailscale.installed
foreground: root.foreground
}
PeerRow {
visible: tailscale.installed
width: parent.width
navigable: false
rowIndex: -1
peer: ({
DisplayName: tailscale.selfName,
HostName: tailscale.selfName,
DNSName: tailscale.selfDnsName,
TailscaleIPs: tailscale.selfIp !== "" ? [tailscale.selfIp] : [],
TailscaleIPv6: [],
OS: "linux"
})
}
Text { Text {
visible: tailscale.actionStatus !== "" || tailscale.lastError !== "" visible: tailscale.actionStatus !== "" || tailscale.lastError !== ""
width: parent.width width: parent.width
@@ -519,7 +571,7 @@ Panel {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.margins: Style.space(12) anchors.margins: Style.space(12)
text: "Tailscale CLI is not installed or not on PATH." text: "Offscale CLI is not installed or not on PATH."
color: root.dim color: root.dim
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.body font.pixelSize: Style.font.body
@@ -713,28 +765,7 @@ Panel {
} }
} }
Timer {
id: phraseTimer
interval: 2800
running: root.opened && tailscale.active
repeat: true
onTriggered: phraseSwap.restart()
}
SequentialAnimation {
id: phraseSwap
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 0.0; duration: 180; easing.type: Easing.OutQuad
}
ScriptAction {
script: root.phraseIndex = (root.phraseIndex + 1) % root.activePhrases.length
}
PropertyAnimation {
target: hero; property: "metaOpacity"
to: 1.0; duration: 260; easing.type: Easing.InQuad
}
}
component AuthRow: CursorSurface { component AuthRow: CursorSurface {
id: authRow id: authRow
@@ -776,7 +807,7 @@ Panel {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: "Authorize Tailscale operator" text: "Authorize Offscale operator"
color: root.foreground color: root.foreground
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.body font.pixelSize: Style.font.body
@@ -785,7 +816,7 @@ Panel {
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: "Allow this user to operate this Tailscale profile" text: "Allow this user to operate this Offscale profile"
color: root.dim color: root.dim
font.family: root.fontFamily font.family: root.fontFamily
font.pixelSize: Style.font.caption font.pixelSize: Style.font.caption
@@ -882,7 +913,12 @@ Panel {
} }
property int copyIndex: 0 property int copyIndex: 0
hasCursor: root.cursorActive && root.focusSection === "peers" && root.peerIndex === rowIndex // Peers live in the keyboard-navigable "peers" section. The local machine
// row reuses this component purely for its copy menu, and must not claim
// the peers cursor or renumber it on hover — hence navigable: false there.
property bool navigable: true
hasCursor: navigable && root.cursorActive && root.focusSection === "peers" && root.peerIndex === rowIndex
foreground: root.foreground foreground: root.foreground
implicitHeight: Math.max(peerContent.implicitHeight, copyButton.implicitHeight) + Style.spacing.rowPaddingX implicitHeight: Math.max(peerContent.implicitHeight, copyButton.implicitHeight) + Style.spacing.rowPaddingX
@@ -921,7 +957,7 @@ Panel {
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.ArrowCursor cursorShape: Qt.ArrowCursor
onContainsMouseChanged: if (containsMouse) root.setPeerCursor(peerRow.rowIndex) onContainsMouseChanged: if (containsMouse && peerRow.navigable) root.setPeerCursor(peerRow.rowIndex)
} }
RowLayout { RowLayout {
+9 -9
View File
@@ -1,15 +1,15 @@
# Tailscale Omarchy Widget # Offscale Omarchy Widget
Native Omarchy bar widget for Tailscale. Native Omarchy bar widget for Offscale.
## Features ## Features
- Shows Tailscale connection state in the bar - Shows Offscale connection state in the bar
- Left click opens a keyboard-friendly panel - Left click opens a keyboard-friendly panel
- Right click toggles Tailscale on/off - Right click toggles Offscale on/off
- Switch between available Tailscale connections when multiple are available - Switch between available Offscale connections when multiple are available
- Browse machines from `tailscale status --json` - Browse machines from `tailscale status --json`
- Copy a machine's Tailscale IP, host name, or DNS name - Copy a machine's Offscale IP, host name, or DNS name
- Send files to a machine with Taildrop, when the tailnet allows file sharing - Send files to a machine with Taildrop, when the tailnet allows file sharing
## Keyboard shortcuts ## Keyboard shortcuts
@@ -22,7 +22,7 @@ Inside the panel:
- `n`: copy selected peer name - `n`: copy selected peer name
- `d`: copy selected peer DNS name - `d`: copy selected peer DNS name
- `s`: send files to selected peer - `s`: send files to selected peer
- `t`: toggle Tailscale - `t`: toggle Offscale
- `r`: refresh status - `r`: refresh status
- `esc`: close - `esc`: close
@@ -37,12 +37,12 @@ Inside the panel:
Incoming Taildrop files are saved to `~/Downloads` by the Incoming Taildrop files are saved to `~/Downloads` by the
`omarchy-tailscale-receive` service, which announces each one with a `omarchy-tailscale-receive` service, which announces each one with a
notification (an image preview when the file is an image, and a click to open notification (an image preview when the file is an image, and a click to open
it). The Tailscale service install enables it; `omarchy tailscale receive` it). The Offscale service install enables it; `omarchy tailscale receive`
runs the same loop by hand. runs the same loop by hand.
## Icon ## Icon
Renders the Tailscale mark natively as a theme-colored 3×3 dot grid, matching the official SVG silhouette while avoiding tiny-SVG rendering quirks in the bar. Renders the Offscale mark natively as a theme-colored 3×3 dot grid, matching the official SVG silhouette while avoiding tiny-SVG rendering quirks in the bar.
## Add to the bar ## Add to the bar
+9 -9
View File
@@ -280,7 +280,7 @@ Item {
exitNodes = running ? tailnetExitNodes.concat(mullvadRegions) : [] exitNodes = running ? tailnetExitNodes.concat(mullvadRegions) : []
} }
function toggleTailscale() { function toggleOffscale() {
if (!installed) return if (!installed) return
if (active) down() if (active) down()
else loginOrUp() else loginOrUp()
@@ -304,7 +304,7 @@ Item {
} }
_loginOutput = "" _loginOutput = ""
_loginError = "" _loginError = ""
if (needsLogin) actionStatus = "Starting Tailscale login…" if (needsLogin) actionStatus = "Starting Offscale login…"
else _desired = 1 else _desired = 1
_loginInProgress = needsLogin _loginInProgress = needsLogin
_loginUrlOpened = false _loginUrlOpened = false
@@ -349,7 +349,7 @@ Item {
if (!installed || operatorProcess.running || userName === "") return if (!installed || operatorProcess.running || userName === "") return
_operatorOutput = "" _operatorOutput = ""
_operatorError = "" _operatorError = ""
actionStatus = "Authorizing Tailscale operator..." actionStatus = "Authorizing Offscale operator..."
operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName] operatorProcess.command = ["pkexec", "tailscale", "set", "--operator=" + userName]
operatorProcess.running = true operatorProcess.running = true
} }
@@ -449,7 +449,7 @@ Item {
if (!root._loginInProgress || root._loginUrlOpened) return if (!root._loginInProgress || root._loginUrlOpened) return
if (!root.openAuthUrlFrom(root.authUrl, true)) { if (!root.openAuthUrlFrom(root.authUrl, true)) {
root._loginInProgress = false root._loginInProgress = false
root.actionStatus = "Tailscale login link not available yet" root.actionStatus = "Offscale login link not available yet"
} }
} }
} }
@@ -500,9 +500,9 @@ Item {
root.parseAccounts("") root.parseAccounts("")
if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) { if (/profiles access denied/i.test(stderr) || /profiles access denied/i.test(stdout)) {
root.accountsAccessDenied = true root.accountsAccessDenied = true
root.lastError = "Authorize Tailscale operator to show connections" root.lastError = "Authorize Offscale operator to show connections"
} else { } else {
root.lastError = elideStatus(stderr || stdout || "Could not list Tailscale connections") root.lastError = elideStatus(stderr || stdout || "Could not list Offscale connections")
} }
} }
} }
@@ -532,7 +532,7 @@ Item {
var stderr = String(actionStderr.text || root._actionError || "") var stderr = String(actionStderr.text || root._actionError || "")
if (exitCode !== 0) { if (exitCode !== 0) {
root._desired = -1 root._desired = -1
root.lastError = elideStatus(stderr || stdout || "Tailscale command failed") root.lastError = elideStatus(stderr || stdout || "Offscale command failed")
root.actionStatus = root.lastError root.actionStatus = root.lastError
actionStatusTimer.restart() actionStatusTimer.restart()
} else { } else {
@@ -621,13 +621,13 @@ Item {
var stdout = String(operatorStdout.text || root._operatorOutput || "") var stdout = String(operatorStdout.text || root._operatorOutput || "")
var stderr = String(operatorStderr.text || root._operatorError || "") var stderr = String(operatorStderr.text || root._operatorError || "")
if (exitCode !== 0) { if (exitCode !== 0) {
root.lastError = elideStatus(stderr || stdout || "Tailscale authorization failed") root.lastError = elideStatus(stderr || stdout || "Offscale authorization failed")
root.actionStatus = root.lastError root.actionStatus = root.lastError
actionStatusTimer.restart() actionStatusTimer.restart()
} else { } else {
root.accountsAccessDenied = false root.accountsAccessDenied = false
root.lastError = "" root.lastError = ""
root.actionStatus = "Tailscale operator authorized" root.actionStatus = "Offscale operator authorized"
actionStatusTimer.restart() actionStatusTimer.restart()
root._lastAccountsRefreshMs = 0 root._lastAccountsRefreshMs = 0
} }
+9 -6
View File
@@ -1,11 +1,11 @@
{ {
"schemaVersion": 1, "schemaVersion": 1,
"id": "omarchy.tailscale", "id": "pastilhas.offscale",
"name": "Tailscale", "name": "Offscale",
"version": "1.0.0", "version": "1.0.0",
"author": "Omarchy", "author": "pastilhas",
"license": "MIT", "license": "MIT",
"description": "Tailscale status, connection switching, machine browsing, and quick copy actions in the Omarchy bar.", "description": "Offscale status, connection switching, machine browsing, and quick copy actions in the Omarchy bar.",
"kinds": [ "kinds": [
"bar-widget" "bar-widget"
], ],
@@ -13,8 +13,8 @@
"barWidget": "Panel.qml" "barWidget": "Panel.qml"
}, },
"barWidget": { "barWidget": {
"displayName": "Tailscale", "displayName": "Offscale",
"description": "Toggle Tailscale, switch connections, browse machines, and copy machine IPs or names.", "description": "Toggle Offscale, switch connections, browse machines, and copy machine IPs or names.",
"category": "Network", "category": "Network",
"allowMultiple": false, "allowMultiple": false,
"defaultSection": "right", "defaultSection": "right",
@@ -32,5 +32,8 @@
"defaultValue": 30 "defaultValue": 30
} }
] ]
},
"omarchy": {
"clonedFrom": "omarchy.tailscale"
} }
} }
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB