Tailscale Widget Plugin for Dank Bar #1

Closed
opened 2026-05-16 01:22:32 +00:00 by jtmorris · 1 comment
Owner

Problem Statement

Users of Dank Material Shell want a lightweight, native-feeling status widget on the Dank Bar that shows Tailscale connectivity at a glance and provides quick controls (toggle connection, switch exit nodes, copy addresses) without leaving the shell or opening a separate GUI.

Solution

A DMS widget plugin (tailscale) that renders a compact pill in the Dank Bar using the standard PluginComponent + DankIcon pattern. The icon reflects connected/disconnected state. Right-click toggles Tailscale via the official CLI. Left-click opens a PopoutComponent flyout containing current IP, active exit node, and a peer list with one-click exit-node selection and copy-to-clipboard actions. All interaction is driven by tailscale status --json, tailscale up/down, and tailscale set --exit-node=....

User Stories

  1. As a DMS user, I want to see a Tailscale icon in my Dank Bar so that I know at a glance whether I am connected to my tailnet.
  2. As a DMS user, I want the icon to change when Tailscale is connected vs. disconnected so that I can visually confirm status without clicking.
  3. As a DMS user, I want to right-click the widget to toggle my Tailscale connection so that I can quickly go online or offline.
  4. As a DMS user, I want to left-click the widget to open a flyout with my current Tailscale IP and active exit node so that I have the most important information in one place.
  5. As a DMS user, I want the flyout to list all devices on my tailnet with their Tailscale IPs so that I can quickly find a peer.
  6. As a DMS user, I want to click any device name or IP in the flyout to copy it to the clipboard so that I can easily share or paste addresses.
  7. As a DMS user, I want any peer that offers an exit node to show a “Use as exit node” button so that I can route my traffic through that node with one click.
  8. As a DMS user, I want the widget to automatically refresh status every few seconds so that the displayed information stays current.
  9. As a DMS user, I want errors from Tailscale commands to appear as toast notifications so that I am informed when something goes wrong.
  10. As a DMS user, I want the plugin to work with the standard DMS plugin loading mechanism (plugin.json + QML component) so that I can enable it like any other widget.

Implementation Decisions

  • The plugin will be implemented as a single PluginComponent widget (type: "widget").
  • Iconography will reuse existing Material symbols (vpn_key / vpn_key_off) for connected/disconnected states; no custom assets required for v1.
  • All Tailscale interaction will be performed via Process + StdioCollector calling the tailscale CLI (no direct Tailscale API or daemon socket).
  • Status polling will be driven by a Timer inside the QML component (interval exposed only via code, not user settings).
  • The flyout will be implemented with popoutContent + PopoutComponent following the DMS widget-with-popout pattern.
  • Click-to-copy will use Quickshell.execDetached(["sh","-c","echo -n '…' | wl-copy"]) and ToastService.
  • Exit-node selection will call tailscale set --exit-node=<hostname> (and tailscale set --exit-node= to clear).
  • The plugin will request the three permissions: settings_read, settings_write, process.
  • No PluginSettings component or persistent user preferences will be provided in v1.
  • The widget will support both horizontal and vertical bar orientations using the standard horizontalBarPill / verticalBarPill properties.

Testing Decisions

  • Good tests exercise external behavior only: icon state changes when tailscale status reports Running vs. Stopped, toggle commands are issued on right-click, flyout content matches parsed JSON, copy actions invoke the expected clipboard command.
  • The primary testable module is the status-parsing and command-generation logic inside TailscaleWidget.qml.
  • Prior art: the existing dms-tailscale plugin in the DMS plugin library uses the same Process + StdioCollector + Timer pattern; tests should mirror that style.

Out of Scope

  • Custom Tailscale logo SVG or branded assets.
  • Settings UI, refresh-interval configuration, or preferred-exit-node list.
  • Traffic graphs, ping, or file-send/receive features (those belong in a full GUI like KTailCtl).
  • Multi-user or multi-profile Tailscale support.
  • Any changes to Dank Material Shell core or Quickshell itself.

Further Notes

The implementation deliberately stays close to the lightweight dms-tailscale reference while adding the exit-node and copy-to-clipboard features requested. It follows the exact DMS plugin development patterns documented at https://danklinux.com/docs/dankmaterialshell/plugin-development (v1.4).

## Problem Statement Users of Dank Material Shell want a lightweight, native-feeling status widget on the Dank Bar that shows Tailscale connectivity at a glance and provides quick controls (toggle connection, switch exit nodes, copy addresses) without leaving the shell or opening a separate GUI. ## Solution A DMS widget plugin (`tailscale`) that renders a compact pill in the Dank Bar using the standard `PluginComponent` + `DankIcon` pattern. The icon reflects connected/disconnected state. Right-click toggles Tailscale via the official CLI. Left-click opens a `PopoutComponent` flyout containing current IP, active exit node, and a peer list with one-click exit-node selection and copy-to-clipboard actions. All interaction is driven by `tailscale status --json`, `tailscale up/down`, and `tailscale set --exit-node=...`. ## User Stories 1. As a DMS user, I want to see a Tailscale icon in my Dank Bar so that I know at a glance whether I am connected to my tailnet. 2. As a DMS user, I want the icon to change when Tailscale is connected vs. disconnected so that I can visually confirm status without clicking. 3. As a DMS user, I want to right-click the widget to toggle my Tailscale connection so that I can quickly go online or offline. 4. As a DMS user, I want to left-click the widget to open a flyout with my current Tailscale IP and active exit node so that I have the most important information in one place. 5. As a DMS user, I want the flyout to list all devices on my tailnet with their Tailscale IPs so that I can quickly find a peer. 6. As a DMS user, I want to click any device name or IP in the flyout to copy it to the clipboard so that I can easily share or paste addresses. 7. As a DMS user, I want any peer that offers an exit node to show a “Use as exit node” button so that I can route my traffic through that node with one click. 8. As a DMS user, I want the widget to automatically refresh status every few seconds so that the displayed information stays current. 9. As a DMS user, I want errors from Tailscale commands to appear as toast notifications so that I am informed when something goes wrong. 10. As a DMS user, I want the plugin to work with the standard DMS plugin loading mechanism (plugin.json + QML component) so that I can enable it like any other widget. ## Implementation Decisions - The plugin will be implemented as a single `PluginComponent` widget (type: "widget"). - Iconography will reuse existing Material symbols (`vpn_key` / `vpn_key_off`) for connected/disconnected states; no custom assets required for v1. - All Tailscale interaction will be performed via `Process` + `StdioCollector` calling the `tailscale` CLI (no direct Tailscale API or daemon socket). - Status polling will be driven by a `Timer` inside the QML component (interval exposed only via code, not user settings). - The flyout will be implemented with `popoutContent` + `PopoutComponent` following the DMS widget-with-popout pattern. - Click-to-copy will use `Quickshell.execDetached(["sh","-c","echo -n '…' | wl-copy"])` and `ToastService`. - Exit-node selection will call `tailscale set --exit-node=<hostname>` (and `tailscale set --exit-node=` to clear). - The plugin will request the three permissions: `settings_read`, `settings_write`, `process`. - No `PluginSettings` component or persistent user preferences will be provided in v1. - The widget will support both horizontal and vertical bar orientations using the standard `horizontalBarPill` / `verticalBarPill` properties. ## Testing Decisions - Good tests exercise external behavior only: icon state changes when `tailscale status` reports Running vs. Stopped, toggle commands are issued on right-click, flyout content matches parsed JSON, copy actions invoke the expected clipboard command. - The primary testable module is the status-parsing and command-generation logic inside `TailscaleWidget.qml`. - Prior art: the existing `dms-tailscale` plugin in the DMS plugin library uses the same `Process` + `StdioCollector` + `Timer` pattern; tests should mirror that style. ## Out of Scope - Custom Tailscale logo SVG or branded assets. - Settings UI, refresh-interval configuration, or preferred-exit-node list. - Traffic graphs, ping, or file-send/receive features (those belong in a full GUI like KTailCtl). - Multi-user or multi-profile Tailscale support. - Any changes to Dank Material Shell core or Quickshell itself. ## Further Notes The implementation deliberately stays close to the lightweight `dms-tailscale` reference while adding the exit-node and copy-to-clipboard features requested. It follows the exact DMS plugin development patterns documented at https://danklinux.com/docs/dankmaterialshell/plugin-development (v1.4).
Collaborator

HITL Verification Complete

All 8 user stories verified on a real tailnet. Screenshot attached:

Tailscale Widget v0.1.0

Verification Checklist

  • Story 1: Tailscale icon visible on bar
  • Story 2: Icon changes connected/disconnected state
  • Story 3: Right-click toggles connection
  • Story 4: Left-click popout shows IP and active exit node
  • Story 5: Peer list with hostnames and IPs
  • Story 6: Click-to-copy works for hostnames and IPs
  • Story 7: Exit node selection via ↗ button works
  • Story 8: Auto-refresh every 5 seconds
  • Story 9: Toast notifications on errors
  • Story 10: Plugin loads via standard DMS mechanism

Bug Fixed During HITL

  • CurrentExitNode was null in JSON output; fixed by scanning Peer map for ExitNode: true instead.

Test Results

All 10 unit tests pass (node --test test/lib.test.js).

## HITL Verification Complete All 8 user stories verified on a real tailnet. Screenshot attached: ![Tailscale Widget v0.1.0](https://forgejo.jtmorris.net/jtmorris/dms_tailscalectl/raw/branch/master/resources/dms_tailscalectl_v0.1.0.png) ### Verification Checklist - [x] Story 1: Tailscale icon visible on bar - [x] Story 2: Icon changes connected/disconnected state - [x] Story 3: Right-click toggles connection - [x] Story 4: Left-click popout shows IP and active exit node - [x] Story 5: Peer list with hostnames and IPs - [x] Story 6: Click-to-copy works for hostnames and IPs - [x] Story 7: Exit node selection via ↗ button works - [x] Story 8: Auto-refresh every 5 seconds - [x] Story 9: Toast notifications on errors - [x] Story 10: Plugin loads via standard DMS mechanism ### Bug Fixed During HITL - CurrentExitNode was null in JSON output; fixed by scanning Peer map for ExitNode: true instead. ### Test Results All 10 unit tests pass (node --test test/lib.test.js).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jtmorris/dms_tailscalectl#1
No description provided.