Skip to content

Repository files navigation

tailtoss

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.

License: MIT Platform: Wayland

The tailtoss window: a drop zone, the device list and recent transfers

Why

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.

How it works

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
Loading
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 holding tailscale file get --loop open.

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.

Requirements

  • Tailscale, installed and logged in (tailscale up).
  • A Wayland session. There is no X11 support, by design.
  • wl-clipboard recommended; 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).

Install

git clone https://github.com/oiver12/tailtoss ~/projects/tailtoss
cd ~/projects/tailtoss
./install.sh
tailtoss doctor      # check the whole environment

Everything 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.

Using it

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.

Documentation

  • Configuration — every config key, the clipboard marker convention, keyboard shortcuts, receiving while logged out.
  • Troubleshooting — missing tray icon, empty clipboard, nothing arriving, exit codes.

Development

just build      # or: just release
just test
just check      # fmt + clippy
just ui         # run from the source tree

Taildrop 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 hang

The 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        # -> hello

Layout: crates/core (config, tailscale wrapper, clipboard, notifications, history — no GUI), crates/clitailtoss, crates/uitailtoss-ui.

Licence

MIT.

About

AirDrop-like companion for Tailscale Taildrop on Pop!_OS / COSMIC — drag-and-drop, clipboard sync, tray and CLI

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages