Repository navigation
LocalSend
LocalSend is an open, cross-platform "AirDrop for everyone": files and text go straight between devices on the same Wi-Fi, encrypted, with no account and no cloud. The shell makes your desktop both a receiver (an incoming-transfer popup with Accept / Decline) and a sender (drop a file on the island, pick a device, done), using the official localsend-cli under the hood.
If your phone runs Android, LocalSend is the quickest no-setup option; KDE Connect is the richer, paired alternative. Both live side by side in the shell.
-
Install the CLI. Open Settings → Devices & Phone → LocalSend. If
localsend-cliis not found, press Install — it downloads the prebuilt binary for your OS and architecture from LocalSend's GitHub releases into~/.local/bin/localsend-cli(no Rust toolchain needed on x86_64 / aarch64 Linux or macOS). The page shows the installed version, the install log and the equivalent manual command. On other architectures the installer printscargo build --releaseinstructions instead. -
Receiving starts by itself (
localsend.autoStart, on by default) when the shell starts. Your desktop appears in the LocalSend app on your phone under its alias. -
Receive: send from the phone. A popup (or a notification, see
preferPopupOverNotification) shows who is sending, the files and their sizes, with Accept / Decline. Accepted files are saved tolocalsend.downloadPath(default:~/Downloads/localsend) and a "saved" summary appears. Text sent to you arrives as text. -
Send: use any of —
- Drop files on the Dynamic Island (it opens a drop area while you drag),
- drag files onto the Clock widget in the bar,
- the LocalSend quick toggle in the Dashboard,
- More actions in the launcher (Ctrl + K on a file result — Launcher & Search), — then choose the destination device from the list of devices found on the network.
qs -c ii ipc call localsend start # start the receiver
qs -c ii ipc call localsend stop
qs -c ii ipc call localsend status # JSON: available, running, cliVersion, pyteAvailable…- Popup anchoring. The transfer popup docks to the bar's edge and stays visible when a sidebar opens on the same side, shifting instead of closing (with Bluetooth connection and Color Picker popups).
- Island integration. An active transfer shows in the island as progress ("Live progress" in Bar & Dynamic Island), and the island is a drop target for files.
- Quick toggle to switch the receiver on and off, and the "Send file" actions from a Dashboard dialog (a queued file opens the device list on the next sidebar open).
-
Notifications (
showNotifications) for incoming and finished transfers;preferPopupOverNotificationpicks the popup when both would fire.
The official CLI is a fully interactive terminal program (crossterm + ratatui): receiving needs you to press Y / N / P, and sending opens a full-screen device list navigated with the arrow keys. There is no --json or headless mode. So the shell does not reimplement the protocol; it drives the real program through a pseudo-terminal:
LocalSend.qml ──JSON events── services/localsend_bridge.py ──pty── localsend-cli (official, Rust)
(UI, popup) reads the screen, writes keys (protocol v2.2, TLS, checksums)
-
Receive mode — the CLI's log view is plain appended text, never an alternate screen. The bridge runs an ANSI tokenizer, drops lines that get erased (the status line redraws itself), and feeds complete lines to a small state machine that reconstructs the multi-line blocks (
R <alias>→Files (N, size):→ file lines →Accept? Y/N/P) and re-emits JSON events:ready,device,incoming,text,saved,cancelled. Accepting or declining just forwardsy/n/pto the pty as a keystroke. -
Send mode —
--fileopens the device list immediately. The bridge usespyte, a headless terminal emulator, only during that alternate-screen window, reads the rendered rows (stripping the box borders), finds the target device by IP, and presses Down the right number of times and Enter. This is the only part that needs thepytePython module (pip install --user pyte); receiving works without it and a missingpyteis shown as a separate warning, not as "LocalSend unavailable". - Sizes are approximate in the UI — the CLI prints only formatted sizes like "1.2 MB"; the bridge reverses that for display. Integrity is never affected: the bridge never reads or writes a file's bytes, so the checksum verification is the official implementation's, end to end. (An earlier version used a third-party Python re-implementation that started corrupting files when the LocalSend app moved to protocol v2.2; replacing it with the official CLI removed that class of bug by construction.)
-
One port, one process. The CLI is a combined sender + receiver + discoverer bound to a single port (53317), with no client-only mode. To send, the service remembers whether the receiver was running, stops it, waits 400 ms for the port, starts the one-shot
send, and restores the receiver when it exits. There is therefore a brief window (seconds) during a send when you cannot receive — inherent to the CLI, not a bug. -
Discovery is the shell's own (
services/localsend_scan.py, multicast + subnet probing), independent of the CLI, so the device list is available before you pick a file.
The installer also removes the old, unrelated localsend-cli pip package and its orphaned certificate files before installing, because that package installed into the same ~/.local/bin/localsend-cli path.
| Key | Default | Meaning |
|---|---|---|
autoStart |
on | Run the receiver at login |
downloadPath |
~/Downloads/localsend |
Where accepted files go |
showNotifications |
on | Notify on transfers |
preferPopupOverNotification |
on | Popup instead of a notification for an incoming transfer |
| Symptom | Fix |
|---|---|
| Page says the CLI was not found |
Install, or run scripts/localsend/install_localsend_cli.sh
|
| Phone doesn't see the desktop | Same network? A firewall must allow TCP/UDP 53317 (multicast discovery uses 224.0.0.167) |
| Sending fails with Address already in use | Another instance holds the port; qs -c ii ipc call localsend stop, then retry |
| "pyte missing" warning | Only the send flow needs it: pip install --user pyte
|
| A release has no CLI binary | LocalSend publishes the CLI only with some releases; the installer pages through releases to find the newest one that has it |
| Piece | Role |
|---|---|
services/LocalSend.qml |
The singleton: availability / version / pyte checks, receiver lifecycle, send orchestration, events → UI |
services/localsend_bridge.py |
The pty bridge (tokenizer, receive parser, pyte device-list scraper) |
services/localsend_scan.py |
Discovery |
scripts/localsend/install_localsend_cli.sh |
Prebuilt-binary installer |
modules/ii/localSendPopup/ |
The transfer popup |
modules/common/models/quickToggles/LocalSendToggle.qml |
Quick toggle |
modules/settings/configs/DevicesPhoneConfig.qml |
Settings |
- Phone Integration — KDE Connect, the paired alternative
- Bar & Dynamic Island
- Sidebar Dashboard
- System Integration