Simple clipboard sync and file transfer between a Linux machine and a MacBook, over your local network. No cloud, no accounts, no database — just two small Python daemons talking to each other.
- Bidirectional clipboard sync (Linux ⇄ macOS)
- Send files to the other device with one command
- Zero third-party dependencies — pure Python standard library
- One config file controls everything
- Auto-start on Linux with a user systemd service
- Auto-start on macOS with a LaunchAgent
Intended for trusted local networks only. Authentication is a single shared token over plain HTTP. Do not expose it to the public internet.
Both devices run the same program. Each one is at the same time:
- A tiny HTTP server that receives clipboard text and files.
- A client that pushes local clipboard changes and files to the peer.
Linux daemon ⇄ Mac daemon
- Python 3.11+ (uses the built-in
tomllib) - Linux:
wl-clipboardandlibnotifysudo pacman -S python wl-clipboard libnotify
- macOS: nothing extra —
pbcopy/pbpaste/osascriptare built in.
No pip install is needed.
Install in ~/projects/localdrop on both devices — the scripts and
systemd service assume this path:
mkdir -p ~/projects
git clone https://github.com/Manuel0516/localdrop ~/projects/localdrop
cd ~/projects/localdrop
cp examples/config.example.toml config.tomlThen edit config.toml. The two configs are nearly identical — only these
differ:
| field | Linux | macOS |
|---|---|---|
device.name |
linux |
macbook |
device.platform |
linux |
macos |
peer.name |
macbook |
linux |
peer.host |
Mac's IP | Linux's IP |
Set the same shared_token on both machines.
python main.py daemon # run the server + clipboard watcher
python main.py ping # check the peer is reachable
python main.py send FILE # send a file to the peerExample:
python main.py send ~/Pictures/photo.pngReceived files land in download_dir (default ~/Downloads/LocalDrop/).
Existing names are kept by adding a -1, -2, … suffix.
The service is attached to graphical-session.target so it starts after the
Wayland session environment is available for clipboard access.
mkdir -p ~/.config/systemd/user
cp examples/localdrop.service.example ~/.config/systemd/user/localdrop.service
# edit the paths inside the service file to match where you cloned localdrop
systemctl --user daemon-reload
systemctl --user enable --now localdrop.service
journalctl --user -u localdrop.service -f # follow logsLocalDrop needs Python 3.11+ because it uses the standard-library tomllib
module. On macOS, /usr/bin/python3 may be Apple's older Python and can fail
with ModuleNotFoundError: No module named 'tomllib'. Use the full path to a
newer Python instead, for example Homebrew's /opt/homebrew/bin/python3.
which python3
python3 --versionCopy the example, edit the Python path and project paths, then install it:
cp examples/com.localdrop.plist.example com.localdrop.plist
# edit com.localdrop.plist:
# - ProgramArguments[0] should be your Python 3.11+ path
# - /Users/YOURNAME/projects/localdrop should match your clone path
mkdir -p ~/Library/LaunchAgents
cp com.localdrop.plist ~/Library/LaunchAgents/com.localdrop.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.localdrop.plistAfter editing the plist, reload it:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.localdrop.plist
cp com.localdrop.plist ~/Library/LaunchAgents/com.localdrop.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.localdrop.plistCheck that it is running:
launchctl print gui/$(id -u)/com.localdrop| Method | Path | Auth header | Body |
|---|---|---|---|
| GET | /health |
— | — |
| POST | /clipboard |
X-LocalDrop-Token: <token> |
JSON {"source": "...", "text": "..."} |
| POST | /file |
X-LocalDrop-Token: <token> + X-LocalDrop-Filename: <name> |
raw file bytes |
Note:
/filetakes the raw request body plus a filename header rather than a multipart form. This keeps the code tiny and dependency-free (Python 3.13 removed thecgimodule). SeeDEVLOG.mdfor the reasoning.
See docs/shortcuts.md for step-by-step guides:
- Hyprland keybinding — open a file picker and send to Mac with one key press
- macOS Finder Quick Action — right-click any file → Send with LocalDrop
- iPhone Shortcuts — send clipboard or files to Linux from the Share Sheet (Linux → iPhone is not supported in v1)
main.py CLI entry: daemon | send | ping
src/
config.py load/validate config.toml
clipboard.py read/write clipboard + watcher loop
transfer.py HTTP server + client (send/ping)
notify.py optional desktop notifications
scripts/
localdrop-send.sh Linux/Hyprland file picker helper
localdrop-send-mac.sh macOS file picker helper
docs/
shortcuts.md Hyprland, macOS, and iPhone integration guide
examples/
config.example.toml config template (copy to config.toml)
localdrop.service.example Linux systemd user unit
com.localdrop.plist.example macOS launchd agent
Firewall: Linux may be blocking port 8765. If you use ufw:
sudo ufw allow 8765/tcpMultiple network interfaces: If Linux has both ethernet and WiFi, make sure
server.host = "0.0.0.0" in Linux's config.toml (not a specific IP), and
that the Mac's peer.host points to the correct Linux IP. You can find Linux's
IPs with:
ip addr show | grep "inet "Daemon not running: Confirm the daemon is up and listening:
ss -ltn | grep 8765macOS does not have a built-in firewall rule blocking this, but check that the
Mac's daemon is actually running (python main.py daemon) and that Linux's
peer.host is set to the Mac's current IP (can change on DHCP networks).
This usually means launchd could not start the job, but the message is generic.
For LocalDrop, first check the Python path in the plist. If it points to
/usr/bin/python3, it may be too old for tomllib. Replace it with the path
from which python3, as long as that Python is 3.11 or newer.
Also make sure you reload the job after changing the plist; copying the file alone does not update an already-loaded LaunchAgent.
MIT — see LICENSE.