A scriptable LocalSend client for Linux, built the Unix way.
lsenddruns in the background and talks to other LocalSend devices.lsendctlcontrolslsendd: one command per call, with plain text or JSON output.
The LocalSend protocol itself is not reimplemented: lsendd uses the official Rust core crate from localsend/localsend (packages/core).
{
inputs.lsend.url = "github:MetehanYurtseven/lsend";
outputs = { nixpkgs, lsend, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
lsend.nixosModules.default
{
services.lsendd = {
enable = true;
openFirewall = true;
};
}
];
};
};
}This runs lsendd as a systemd user service and puts lsendctl on the PATH.
| Option | Default | Description |
|---|---|---|
enable |
false |
Run lsendd as a systemd user service. |
package |
this flake's package | The package providing lsendd and lsendctl. |
openFirewall |
false |
Open port 53317 (TCP and UDP). |
accept |
"known" |
Which requests are accepted automatically: all, known, or none. |
onText |
"cat; echo" |
Shell command run for each received text message. |
systemd.target |
"default.target" |
The user target that starts lsendd. |
Example: copy received text to the Wayland clipboard. wl-copy needs the graphical session:
services.lsendd = {
enable = true;
openFirewall = true;
onText = lib.getExe' pkgs.wl-clipboard "wl-copy";
systemd.target = "graphical-session.target";
};nix build
./result/bin/lsenddcargo build --release
./target/release/lsenddWithout the NixOS module, make sure port 53317 (TCP and UDP) is open in your firewall.
lsendd & # or use the NixOS module
lsendctl list # reachable devices
lsendctl send --to iphone report.pdf # send a file
lsendctl send --to 192.168.0.27 photos/ # send a directory, by IP| Command | Description |
|---|---|
lsendctl status |
Show the daemon's alias, fingerprint, and port. |
lsendctl list [--fingerprint] |
List reachable devices: alias, address, type (and fingerprint). |
lsendctl send --to <alias|ip> <paths…> |
Send files or directories (recursively). |
lsendctl send --to <alias|ip> --text <text|-> |
Send a text message. - reads it from stdin. |
lsendctl pending |
Show the request waiting for a decision. |
lsendctl accept [--from <fingerprint>] |
Accept the pending request. A text message is printed to stdout. |
lsendctl decline [--from <fingerprint>] |
Decline the pending request. |
lsendctl trust <alias|ip|fingerprint> |
Add a device to the known senders. |
--json works with every command and prints the daemon's response as a single line of JSON.
Devices are found passively through LocalSend's multicast discovery, there is no network scan. A device that has not announced itself yet can always be reached by its IP address.
lsendd --accept decides which requests are accepted automatically:
| Policy | Accepted automatically |
|---|---|
all |
every request |
known (default) |
requests from fingerprints in $XDG_CONFIG_HOME/lsend/known |
none |
nothing |
All other requests wait for lsendctl accept or lsendctl decline and are declined after 60 seconds. Only one request can be pending at a time.
The known file holds one fingerprint per line. It is read on every request, so edits take effect immediately. lsendctl trust appends to it:
lsendctl pending # iphone 192.168.0.27 A72024B1… 1 files
lsendctl trust iphone # Trusted iphone (A72024B1…)Received files are saved to XDG_DOWNLOAD_DIR (the current directory if it is not set). Directories keep their structure, and name collisions get a free name instead of overwriting.
Sending:
lsendctl send --to iphone --text "hello"
wl-paste --no-newline | lsendctl send --to iphone --text -Text read from stdin is sent unchanged, nothing is trimmed.
Receiving: an automatically accepted text message is passed on the stdin of lsendd --on-text (run through sh -c). The default, cat; echo, prints it to the daemon's log:
lsendd --on-text wl-copy # copy to the clipboard
lsendd --on-text 'notify-send lsend "$(cat)"'A text message that waits for a decision is printed by lsendctl accept instead:
lsendctl accept | wl-copyEvery command exits with 0 on success. On failure, the error goes to stderr and the exit code is 1.
# Aliases of all reachable phones
lsendctl --json list | jq -r '.devices[] | select(.device_type == "mobile") | .alias'
# Accept a pending request only from a known fingerprint
lsendctl accept --from A72024B1…
# Send a screenshot to the phone
grim - > /tmp/shot.png && lsendctl send --to iphone /tmp/shot.png| Path | Content |
|---|---|
$XDG_RUNTIME_DIR/lsend/lsendd.sock |
Socket between lsendctl and lsendd |
$XDG_STATE_HOME/lsend/identity.pem |
Device key and certificate, which define the fingerprint |
$XDG_CONFIG_HOME/lsend/known |
Fingerprints of trusted senders |