kcd is a lightweight, headless implementation of the KDE Connect protocol v8 written in Go. It lets Linux servers, containers, and minimal desktop environments participate in the KDE Connect ecosystem without a GUI, a full KDE installation, or heavy D-Bus dependencies.
| Plugin / Feature | What it does |
|---|---|
| Battery | Monitor remote device charge and charging state |
| Clipboard | Bi-directional sync (Wayland via wl-copy, X11 via xclip) |
| Notifications | Forward phone notifications to the desktop via notify-send> Desktop notification icons require libnotify ≥ 0.8.0 (Ubuntu 22.04+, Fedora 36+). Older versions receive text-only notifications. |
| Share | Receive files and URLs from the phone; send local files to it |
| RunCommand | Execute pre-configured local shell commands triggered from your phone |
| MPRIS | Control desktop media players via D-Bus and control phone music (play/pause/next/prev/volume/seek) from the desktop CLI |
| Mousepad | Use the phone as a wireless trackpad and keyboard |
| Find My Phone | Ring the phone to locate it |
| Telephony | Get call and SMS notifications on the desktop |
| SMS | Send SMS messages via the phone |
| SFTP | Browse the phone's filesystem |
| Lock / Unlock | Lock and unlock the desktop session |
| Ping | Simple connectivity check |
| Connectivity | Phone signal strength and network type reporting |
| System Volume | Control desktop audio volume from the phone |
| Send Notification | Push a notification from the PC to the phone |
| Auto-reconnect | Paired devices reconnect automatically after dropping |
Discovery is dual-mode: UDP broadcast (port 1716) and mDNS/Zeroconf (_kdeconnect._udp), so kcd works on both simple home networks and restricted environments (corporate Wi-Fi, Docker, university networks) where broadcast packets are dropped.
Note
Broadcast is off by default. It only runs during kcd pair (listen mode) and stops immediately after. The UDP/mDNS listener stays always-on, so paired devices reconnect automatically via remembered IPs at 0.0% idle CPU.
git clone https://github.com/bethropolis/kcd.git
cd kcd
./scripts/install.shInstall from the AUR using your preferred helper:
yay -S kcd-binMulti-arch images on GHCR: docs/CONTAINER.md
Download the latest pre-built binary from GitHub Releases.
KDE Connect needs three port ranges open:
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 1716 | UDP | bidirectional | Device discovery broadcast |
| 1716 | TCP | bidirectional | Encrypted control channel |
| 1739–1764 | TCP | inbound | File transfer side-channels |
Installed via .deb, .rpm, or AUR? The firewall rules are already in place — nothing to do on your end. These steps are only needed for manual installs.
sudo cp packaging/ufw-kcd /etc/ufw/applications.d/kcd
sudo ufw allow kcdsudo cp packaging/firewalld-kcd.xml /etc/firewalld/services/kcd.xml
sudo firewall-cmd --permanent --add-service=kcd
sudo firewall-cmd --reloadsudo ufw allow 1716/udp
sudo ufw allow 1716/tcp
sudo ufw allow 1739:1764/tcpIf you installed via the script, .deb, .rpm, or AUR, the systemd user service
is already set up — enable and start it:
systemctl --user enable --now kcdCheck that it's running:
systemctl --user status kcdTo run in the foreground instead (for testing):
kcd daemonOpen the KDE Connect app on your Android device and make sure it is on the same network. Then:
kcd devicesDEVICE ID NAME TYPE STATE CONNECTED
---------------------------------------------------------------------------------------------------
a1b2c3d4_e5f6_7890_abcd_ef1234567890 Pixel 8 Pro phone Unpaired true
Two ways to pair:
A. Send pair request to a discovered device:
kcd pair a1b2c3d4_e5f6_7890_abcd_ef1234567890you can accept the pair request on your phone
B. Listen mode — accept any incoming request (headless/server):
kcd pairBroadcast starts automatically so the phone can find the PC. Accept on the phone, the CLI confirms and exits. Broadcast stops immediately. Press Ctrl+C to cancel.
# Check daemon health and dependencies
kcd doctor
# Show daemon uptime, version, connected devices, and plugins
kcd status
# Push your clipboard to the first connected phone
kcd clipboard
# Send a file
kcd share <device-id> ~/Pictures/photo.jpg
# Ring the phone
kcd findmyphone <device-id>
# Watch live events
kcd watch
# Watch device connect/disconnect live
kcd devices --watch
# List MPRIS players on the phone and control playback
kcd mpris list
kcd mpris play
kcd mpris pause
kcd mpris next
kcd mpris previousThe daemon reads $XDG_CONFIG_HOME/kcd/kcd.toml (typically ~/.config/kcd/kcd.toml). All settings are optional — sensible defaults are applied automatically.
device_name = "my-desktop"
device_type = "desktop" # desktop | laptop | phone | tablet | tv
tcp_port = 1716
log_level = "info" # debug | info | warn | error | quiet
# Directory where received files are saved.
download_dir = "~/Downloads/kcd"
# Reload [commands] and log_level without restarting: kill -HUP $(pidof kcd)
[plugins]
# battery = true
# clipboard = true
# ... (see example config for full list)
# ─── Detailed Plugin Configuration ────────────────────────────────────────────
# Each plugin has its own section for fine-tuning behavior.
[battery]
# notify_low = true
# notify_full = true
# low_urgency = "critical"
[notification_plugin]
# fetch_icons = true
# expire_ms = -1
[share]
# auto_open = false
# open_command = "xdg-open"
[sftp]
# auto_open = true
# mount_dir = "/home/user/mnt"
[commands]
uptime = "uptime"
lock = "loginctl lock-session"
[notifications]
# "*" = "show"
# See packaging/kcd.example.toml for the full annotated reference of all settings.See packaging/kcd.example.toml for the full annotated reference.
Most of the installation methods, add the kcd nautilus plugin which allows you to right-click any file in Nautilus to send it directly to a paired device.
Add to ~/.config/waybar/config:
"custom/phone-battery": {
"exec": "kcd watch --events=battery.update | jq -r 'select(.type==\"battery.update\") | \"\\(.payload.charge)%\" + (if .payload.charging then \" \" else \"\" end)'",
"restart-interval": 0,
"format": " {}",
"return-type": ""
}A ready-made config snippet, bash script and stylesheet are in kcd-waybar-integration/.
kcd fits into WM setups without pulling in KDE Plasma — single binary,
systemd user unit alongside your compositor. All mpris commands auto-
discover your phone, no device ID needed.
Hyprland (~/.config/hypr/hyprland.conf):
bind = SUPER, F9, exec, kcd mpris toggle
bind = SUPER, F10, exec, kcd mpris previous
bind = SUPER, F11, exec, kcd mpris next
Sway / i3 (~/.config/sway/config or ~/.config/i3/config):
bindsym $mod+F9 exec kcd mpris toggle
bindsym $mod+F10 exec kcd mpris previous
bindsym $mod+F11 exec kcd mpris next
You can also bind kcd clipboard, kcd findmyphone, or any other
subcommand — pick keys that don't conflict with your existing layout.
kcd watch --json | while read -r event; do
type=$(echo "$event" | jq -r '.type')
case "$type" in
battery.update)
charge=$(echo "$event" | jq -r '.payload.charge')
notify-send "Phone battery" "${charge}%"
;;
telephony.ringing)
echo "$event" | jq -r '.payload | "Call from \(.contactName // .phoneNumber)"'
;;
esac
donekcd watch --json streams NDJSON events. All events include type, timestamp (RFC3339), and deviceId fields.
| Event type | Payload fields | Description |
|---|---|---|
device.added |
name, type |
New device seen for the first time |
device.removed |
— | Device unpaired and removed |
device.connected |
name, type |
TCP connection established |
device.disconnected |
— | Connection dropped |
pair.requested |
— | Phone sent a pairing request |
pair.accepted |
— | Pairing completed on both sides |
pair.rejected |
— | Pairing denied or cancelled |
battery.update |
charge, charging |
Battery reading received |
battery.threshold |
charge, charging, event |
Low (event=1) or full (event=2) |
notification |
appName, title, text, requestReplyId |
Phone notification forwarded |
notification.canceled |
id |
Phone dismissed a notification |
share.progress |
file, current, total |
File transfer in progress (~2/sec) |
share.complete |
file, path |
File transfer finished |
share.text |
text |
Plain text received |
share.url |
url |
URL received |
ping.received |
— | Ping arrived |
telephony.ringing |
contactName, phoneNumber |
Incoming call |
telephony.missed |
contactName, phoneNumber |
Missed call |
telephony.canceled |
— | Call ended |
connectivity.update |
signal, networkType |
Signal strength report |
mpris.update |
player, title, artist, album, isPlaying, pos, length, volume |
Phone now playing state changed |
volume.update |
name, volume, muted |
Desktop volume changed from phone |
sftp.mount |
uri, ip, port, user, password, path |
SFTP credentials received |
- TLS everywhere — all traffic is encrypted. Plaintext is only used for the initial identity exchange before the TLS upgrade.
- Certificate fingerprinting — trust is established by comparing the SHA-256 fingerprint of each peer's self-signed certificate during pairing. No CA required.
- Path sanitisation — filenames received via the Share plugin are strictly sanitised to prevent path traversal attacks.
- Memory-safe transfers — large file transfers stream directly to disk to prevent OOM conditions.
kcd is optimised for extremely low resource usage. Broadcast is off by default (only enabled during kcd pair listen mode for discovery), so the daemon sits at 0.0% idle CPU in normal operation. Paired phones reconnect automatically via the last known IP without any broadcast chatter.
On networks that block broadcast packets (corporate Wi-Fi, Docker bridges, university networks):
# Enter the PC's IP in the KDE Connect app: ⋮ → Add device by IP
# Then on the PC:
kcd connect 192.168.1.100| Document | Description |
|---|---|
docs/ARCHITECTURE.md |
System architecture, plugin system, event bus, IPC protocol |
docs/CLI.md |
Full CLI reference and sub-commands |
docs/CONTAINER.md |
Running kcd in Docker / Podman |
packaging/kcd.example.toml |
Annotated configuration reference |