# Tailscale Widget Plugin for Dank Material Shell A lightweight widget plugin that shows Tailscale connectivity status on the Dank Bar with quick controls for toggling connection, switching exit nodes, and copying peer addresses. ![Tailscale Widget v0.2.2](resources/dms_tailscalectl_v0.1.0.png) ## Features - **Status icon** in the bar — `vpn_key` when connected, `vpn_key_off` when disconnected - **Right-click** to toggle Tailscale on/off - **Left-click** to open a popout showing: - Your current Tailscale IP (when connected) - Public IP derived from Tailscale endpoint addresses (Self, or active exit-node peer when one is selected) - Active exit node (with clear button) - Peer list with hostnames and IPs (when connected) - A clear "Not connected" empty state when disconnected (no stale peer list) - **Click-to-copy** any hostname or IP to clipboard - **Exit node selection** — click `↗` on any exit-node-capable peer to route through it - **On-demand status** — polls Tailscale for ground truth on load, explicit actions, and post-mutation verification (defensive poll-act-poll for toggles; no always-on timer) - **Single-flight actions** — concurrent status/toggle/exit/copy chains are rejected while an operation is in flight - **Toast notifications** for all errors ## Requirements - Dank Material Shell installed and running - `tailscale` CLI available on `PATH` - A clipboard tool (`dms` or `wl-copy`) ## Installation 1. Clone or copy this repository so that the `tailscalectl/` directory is available: ``` git clone ``` 2. Place the `tailscalectl/` directory into your DMS plugins directory: ``` mv tailscalectl ~/.config/dankmaterialshell/plugins/ ``` The directory must contain: ``` tailscalectl/ ├── plugin.json ├── TailscaleWidget.qml ├── lib.js └── i18n/ ``` 3. Reload the plugin: ``` dms ipc call plugins reload tailscalectl ``` 4. The Tailscale icon should appear on your Dank Bar. If connected, it shows a filled key icon; if disconnected, an outlined key icon. ## Usage | Action | How | |---|---| | Toggle connection | Right-click the bar icon | | Open popout | Left-click the bar icon | | Copy hostname/IP | Click the text in the peer list | | Set exit node | Click `↗` next to an exit-node peer | | Clear exit node | Click `×` next to "Exit node: ..." or click `↗` on current node | ## Plugin Manifest ```json { "id": "tailscalectl", "name": "Tailscale", "description": "Tailscale status and controls on the Dank Bar", "author": "John Morris", "icon": "vpn_key", "type": "widget", "capabilities": ["dankbar-widget"], "component": "./TailscaleWidget.qml", "permissions": ["process"], "requires": ["tailscale"], "version": "0.2.2" } ``` ## Implementation notes - Uses `Proc` singleton (from `qs.Common`) for all external `tailscale` commands (one-shot stdout capture + auto cleanup). - Fully I18n-ready via `I18n.tr(...)` (source keys in American English only today; see `tailscalectl/i18n/` for scaffolding). - Follows current `dms-plugin-dev` + DMS 1.4 plugin best practices (capabilities, requires, no raw Process for one-shots, etc.). - Toggle uses intentional defensive poll-act-poll; a failed status poll aborts the pending toggle (does not invent `up`/`down`). - When `BackendState` is not `Running`, peer list / exit node / self IP are cleared so the UI never shows a stale connected-looking peer list. - Public IP is derived from `Self.Addrs` (or the active exit-node peer's `Addrs` when an exit node is selected). No external HTTP probe. - Status row uses `RowLayout` with a real `Layout.fillWidth` spacer (not a no-op on plain `Row`). - Peer `ListView` uses `Flickable.StopAtBounds` (no desktop rubber-band overshoot). ## Testing ```bash node --test test/lib.test.js ``` ## License See [LICENSE](LICENSE).