AirDrop for your tailnet. Drop a file on a window, send your clipboard with a keyboard shortcut, and have text copied on one machine land in the clipboard of another — over Tailscale Taildrop, on COSMIC / Pop!_OS.
Taildrop already moves files between your own machines, encrypted and without a cloud round-trip — but it is a CLI, so in practice nobody uses it for the two-second "send me that photo" job. tailtoss is the missing front end: a drop target, a tray icon and a keyboard shortcut.
It adds no transport, relay, discovery or queueing of its own. Tailscale does all the networking; tailtoss is a friendly shell around three commands.
flowchart LR
subgraph you["your machine"]
UI["window · tray · CLI"] --> TT["tailtoss"]
TT -->|"tailscale file cp"| TS1["tailscale"]
end
TS1 ==>|"WireGuard: direct, or DERP relay"| TS2
subgraph them["your other machine"]
TS2["tailscale"] -->|"tailscale file get --loop"| RX["tailtoss recv"]
RX --> DL["~/Downloads/Taildrop"]
RX -->|"*.clip.txt"| CB["clipboard"]
end
| What you do | What runs underneath |
|---|---|
| Send | tailscale file cp <path> <target>: |
| Receive | tailscale file get --loop --conflict=rename <dir> |
| List devices | tailscale status --json |
Three pieces, deliberately kept apart so the tray can never take down the rest:
tailtoss-ui— the window. The only part that links libcosmic.tailtoss daemon— the tray icon and the clipboard watcher.tailtoss recv— a systemd user service holdingtailscale file get --loopopen.
They share no IPC: each shells out to tailscale itself (status --json costs ~9 ms),
reads the same TOML config, and appends to one JSONL history. So a shortcut-bound
tailtoss send-clipboard works with no window open and no daemon running.
Clipboard sync rides on a naming convention: a file arriving as *.clip.txt is copied
into your clipboard and deleted, instead of being kept as a file. That is all it takes for
two machines running tailtoss to share a clipboard in both directions.
It fails fast. tailscale file cp to an offline peer does not error — it blocks
forever, and Taildrop has no store-and-forward, so waiting is pointless. tailtoss refuses
locally in ~9 ms instead, backed by a stall timer and a size-scaled total timeout for a
peer that drops mid-transfer.
- Tailscale, installed and logged in (
tailscale up). - A Wayland session. There is no X11 support, by design.
wl-clipboardrecommended; there is an in-process fallback.- Rust (stable) to build.
No root needed, as long as you are the Tailscale operator
(tailscale set --operator=$USER, usually already done at install).
git clone https://github.com/oiver12/tailtoss ~/projects/tailtoss
cd ~/projects/tailtoss
./install.sh
tailtoss doctor # check the whole environmentEverything lands under $HOME: two binaries in ~/.local/bin, an icon, a launcher entry,
the two systemd user units, and a commented ~/.config/tailtoss/config.toml that is never
overwritten. Options: --debug, --no-build, --service, --no-daemon, --no-recv,
--shortcuts. Uninstall with just uninstall.
The window (tailtoss ui) — drop files on the zone, click it to pick some, or press
Ctrl+V to send whatever is on the clipboard. Below it: your devices with online dots,
and recent transfers.
The tray — quick clipboard sends and a target picker, without opening the window.
Keyboard shortcuts — Wayland does not let an app grab global hotkeys, so COSMIC owns
the binding and runs a command. Bind tailtoss send-clipboard, send-image, send-last
or ui in Settings → Keyboard → Shortcuts → Custom, or run just shortcuts for a
config snippet. More →
The CLI
tailtoss send-file <paths...> [--to <device>] [--name <n>]
tailtoss send-clipboard | send-image | send-last [--to <device>]
tailtoss devices [--json] tailtoss set-default <device>
tailtoss ui tailtoss doctor
tailtoss daemon tailtoss recv
tailtoss clip-debug [--image] # what is actually on the clipboard
--to accepts a hostname, a MagicDNS name or a 100.x address, case-insensitively, and
every failure has its own exit code.
- Configuration — every config key, the clipboard marker convention, keyboard shortcuts, receiving while logged out.
- Troubleshooting — missing tray icon, empty clipboard, nothing arriving, exit codes.
just build # or: just release
just test
just check # fmt + clippy
just ui # run from the source treeTaildrop cannot send to yourself, so a mock tailscale stands in and the send path can be
exercised with no peer online:
just fake-devices # a peer appears online
just fake-send /etc/hostname # a real transfer through the mock
just fake-hang # must abort on the stall timer, not hangThe receive half needs no mock — drop a file into the receive directory and the real code path runs:
printf 'hello' > /tmp/x.clip.txt && mv /tmp/x.clip.txt ~/Downloads/Taildrop/
wl-paste # -> helloLayout: crates/core (config, tailscale wrapper, clipboard, notifications, history — no
GUI), crates/cli → tailtoss, crates/ui → tailtoss-ui.
MIT.
