lan-mouse icon indicating copy to clipboard operation
lan-mouse copied to clipboard

[documentation] Howto clipboard

Open DrYak opened this issue 11 months ago • 8 comments

Context

The roadmap in the main readme still lists clipboard unchecked:

  • [ ] Clipboard support

While technically true, as lan-mouse's own protocol doesn't (and can't easily for technical reasons around UDP realtime communications) carry clipboard content, in practice clipboard sharing works like a charm (see #105) thanks to hooks, and external tools such as wl-clipboard/wl-clipboard-rs and xclip (for clipboard management) and SSH forced commands (for encryption and security) or Syncthing (keeping clipboard history synced)

In this HOWTO issue, several users have documented their respective solutions for sharing clipboard content.

TODO

Link this issue from the roadmap.

UPDATE

DrYak avatar Feb 07 '25 11:02 DrYak

Turns out the resource you need is either so scarce or no longer exists. But it's alright, if i manage to get a way to get clipboard or drag and drop work i'll comment it here in detail until you're back.

kevinJ045 avatar May 01 '25 22:05 kevinJ045

Here's what i am doing to have it sync my clipboard:

#!/bin/bash
ips=($(cat ~/.config/clip-iplist))
clsport=${1:-52111}


listen_clipboard() {
    for ip in "${ips[@]}"; do
        socat TCP-LISTEN:$clsport,fork EXEC:"wl-copy" &
    done
}

send_clipboard() {
    while clipnotify; do
        content=$(wl-paste -n)
        for ip in "${ips[@]}"; do
            echo "$content" | socat - TCP:$ip:$clsport
        done
    done
}

listen_clipboard &
send_clipboard

Just keep the IPs you wanna sync in ~/.config/clip-iplist and then run this. You might wanna install socat and clipnotify if you don't have them.

Issues:

  • I tried clipnotify on sway and it didn't seem to work... But it's open source so if you need to you can clone the github repo and edit clipnotify.c.

It's not 100% solid, but i am using it right now.

kevinJ045 avatar May 02 '25 09:05 kevinJ045

And here is my script which is SSH-based instead of netcat/socat/bash's /dev/tcp/…/etc. clear-text network pipes (It should be a bit safer for copy-pasting passwords around).

Key points

  • uses ssh for encrypted transmission, instead of clear-text network pipes.
  • thus it also uses TCP instead of lan-mouse's UDP for content integrity.
  • is single peer-2-peer exchange instead of broadcast/distributed so it's suitable for using in lan-mouse config.toml's option enter_hook.
  • it relies on SSH keys and forced commands and works in the push direction to limit attack surface.
  • can work on Wayland and X11 senders and receivers.

Limitations

  • Only works on Linux with Wayland and X11
  • Does NOT work on Mac OS nor Windows as those environment are completely alien to me (somebody jump in?)
  • lan-mouse cannot (yet) detect mouse leaving on X11 clients, so will not call the entry_hook, but you can still use the script from command line.

Usage

$ pushclipboard -h
Usage:
        /home/deck/projects/shell-utils/pushclipboard [ -v ] [ -t {n} ] [ -4 | -6 ] [ -x | -w ] [ -i sshkey ] {target computer}
Copies the content of Wayland's clipboard to the target computer

Options:
        -t {n}  timeout for n seconds
        -4      connect SSH over IPv4
        -6      connect SSH over IPv6
        -w      use Wayland (auto-detected)
        -x      use X11 (fall-back)
        -v      verbose
        -i [sshkey]     Use sshkey as identity
        -f      build authorized_keys' forced-command line
  • if you don't manually specify a key with -i the script tries to search one named id_… blah …_clipboard
    • failing that is simply calls whatever you have in your ssh configuration
  • the script tries to auto-detect whether Wayland or X11 runs locally based on standard environment variables
    • you can override with -w or -x
  • if you haven't setup a forced command, the script will try remotely running the wl-copy command at the SSH receiver.

[!CAUTION] You absolutely SHOULD consider setting up SSH forced commands as per the Howto bellow for security reasons, even if pushclipboard may work without.

Howto

This is how you configure your local (sender) machine, so that it can push the content of the clipboard to a remote (receiver) machine. This can work no matter what display server the desktop environment uses at each end-point, you can mix and match Wayland or X11.

SSH Key

First you need to create a passphrase-less SSH key-pair dedicated to this script:

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_clipboard
output of `ssh-keygen`
Generating public/private ed25519 key pair.
Enter passphrase for "/home/dryak/.ssh/id_ed25519_clipboard" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/dryak/.ssh/id_ed25519_clipboard
Your public key has been saved in /home/dryak/.ssh/id_ed25519_clipboard.pub
The key fingerprint is:
SHA256:HRsePTditXVegjurN7o+Rq3R5gTThnXwkM/nX8yJV1M deck@steamdeck
The key's randomart image is:
+--[ED25519 256]--+
|            o+o +|
|           .++.=E|
|          ++=== o|
|         o+*=+ooo|
|        S +* o.=+|
|          o *. o=|
|         . B  . o|
|          = +   .|
|         o+= .   |
+----[SHA256]-----+

[!TIP]

  • just press enter with no passphrase (we will reduce the attack surface with forced commands)
  • you can replace ed25519 with your favorite algo
  • use a filename that looks like id_… blah …_clipboard so that the script can automatically find it.

SSH Forced Commands

generate

We will use forced commands, so that in case of leaked key, the worst that can happen is random shit copied into the clipboard (not even the clipboard content could be inspected, and no arbitrary commands could be run).

Use the -f option of the script to show the lines that should be added to the remote machine:

pushclipboard -f
output of `pushclipboard -f`
Found key:      /home/dryak/.ssh/id_ed25519_clipboard
# forced command in ~/.ssh/authorized_keys
# Wayland
command="/usr/bin/env WAYLAND_DISPLAY='wayland-0' wl-copy; echo copied",no-agent-forwarding,no-pty,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK0a9JD/Hwda0HdFKTk64nLAYoPDUFezEHgOjpCqvMwC dryak@nereid
# X11
command="xclip -display :0 -in -rmlastnl -selection clipboard; echo copied",no-agent-forwarding,no-pty,no-X11-forwarding ssh-ed25519 AAAAAC3NzaC1lZDI1NTE5AAAAIK0a9JD/Hwda0HdFKTk64nLAYoPDUFezEHgOjpCqvMwC dryak@nereid

configure remote receiving end

Depending on what desktop environment the remote (receiver) machine is running, copy the appropriate command="…etc line for either Wayland or X11 display server, and add it to the .ssh/authorized_keys file on that remote (receiver) machine:

[deck@steamdeck ~]$ nano ~/.ssh/authorized_keys

[!TIP] if the .ssh configuration directory does not exist for that user on the remote machine, you need to create it with the approriate access rights:

mkdir -m 0700 ~/.ssh

Test it on the CLI

On the local (sender) machine, copy something into the clipboard, either with the mouse, the keyboard, or the command line:

date --rfc-3339=seconds | wl-copy

Try pushing the content of the keyboard to the remote (receiver) machine using the script on the command line interface:

pushclipboard steamdeck.local
output of `pushclipboard steamdeck.local`
Found key:      /home/dryak/.ssh/id_ed25519_clipboard
Pushing clipboard to steamdeck.local using wl-paste on Wayland...
copied

[!TIP]

  • the script will attempt automatically detecting whether the local (sender) machine runs Wayland or X11 and will use wl-paste or xclip accordingly.
  • if you used a filename that looks like id_… blah …_clipboard the script can automatically find the SSH key.

Check that the remote (receiver) machine successfully put the string in its clipboard, either by checking the clipboard, or by pasting with the mouse, the keyboard, or the command line:

[deck@steamdeck ~]$ xclip -o -selection clipboard
2025-07-25 12:03:23+02:00

or

[dryak@nereid ~]$ wl-paste
2025-07-25 12:03:23+02:00

Configure lan-mouse

On the local (sender) machine you can edit the configuration file:

nano ~/.config/lan-mouse/config.toml

and add the enter_hook option for the clients entry specific to that remote (receive) machine:

# …other clients…

[[clients]]
position = "right"
hostname = "steamdeck.home"
activate_on_startup = true
ips = ["10.0.1.16"]
port = 4242
enter_hook="/home/dryak/bin/pushclipboard steamdeck.local"

[!IMPORTANT]

  • Due to lan-mouse not yet supporting X11 capture, you can only configure Wayland lan-mouse instance with this hook (X11 instances will never detect the mouse attempting to leaving screen and never trigger entering into another instance).
  • You might need to give the full path to this script depending on how it is installed. E.g., some distro (such as the Arch Linux demonstrated above) do not include $HOME/bin in their PATH.

Test it in lan-mouse

Now test that in lan-mouse, when moving the cursor from a Wayland Linux instance to another Linux instance, the clipboard gets copied over.

lan-mouse's logs
…
[2025-07-25T10:46:28Z WARN  lan_mouse::capture] releasing capture: not connected
[2025-07-25T10:46:28Z INFO  lan_mouse::connect] client 1 connecting ...
[2025-07-25T10:46:28Z INFO  lan_mouse::connect] client (1) connecting ... (ips: [10.0.1.16:4242])
[2025-07-25T10:46:28Z INFO  lan_mouse::connect] connecting to 10.0.1.16:4242 ...
[2025-07-25T10:46:28Z INFO  lan_mouse::service] entering client 1 ...
[2025-07-25T10:46:28Z INFO  lan_mouse::service] spawning command!
Found key:      /home/dryak/.ssh/id_ed25519_clipboard
Pushing clipboard to steamdeck.local using wl-paste on Wayland...
copied
[2025-07-25T10:46:29Z INFO  lan_mouse::service] /home/dryak/bin/pushclipboard steamdeck.local exited successfully
…

Rinse and repeat

You can repeat the above steps to add more machines.

[!TIP] Depending on how paranoid you are with your keys, you can skip the SSH Keys generation, and simply reuse the .ssh/id_ed25519_clipboard and ~/.ssh/config/id_ed25519_clipboard .pub files on all your machine (and copy the same line into ~/.ssh/authorized_keys At worst, if a key is leaked, an attacker could at best only fill your clipboard with crap, not even inspect the clipboard content, nor run arbitrary commands.

[!IMPORTANT] Due to lan-mouse not yet supporting X11 capture, you can only configure Wayland lan-mouse instance with the hook. On machine running X11, you can still run pushclipboard …target… from the command line, though.

Source code

pushclipboard
#!/bin/bash

declare -a timeout
declare -a read_t
declare -a ssh_options
ssh_key=
buildline=0
gfxsys=

usage() {
cat <<HELP
Usage:
	$0 [ -v ] [ -t {n} ] [ -4 | -6 ] [ -x | -w ] [ -i sshkey ] {target computer}
Copies the content of Wayland's clipboard to the target computer

Options:
	-t {n}	timeout for n seconds
	-4	connect SSH over IPv4
	-6	connect SSH over IPv6
	-w	use Wayland (auto-detected)
	-x	use X11 (fall-back)
	-v	verbose
	-i [sshkey]	Use sshkey as identity
	-f	build authorized_keys' forced-command line
HELP
	exit "$1"
}

# Process options
while getopts "t:46wxvi:f-:h" o; do
	case "${o}" in
		t)
			timeout=( timeout "${OPTARG}" )
			read_t=( '-t' "${OPTARG}" )
			;;
		4|6|v)	ssh_options+=( "-${o}" )	;;
		i)	ssh_key="${OPTARG}"
			if [[ ! -r "${ssh_key}" ]]; then
				echo "Cannot read ${ssh_key}" 1>&2
				exit 2
			else
				echo "Using ${ssh_key}"
			fi
			;;
		f)	buildline=1	;;
		w)	gfxsys=wayland	;;
		x)	gfxsys=x11	;;
		h)	usage 0	;;
		-)	case "${OPTARG}" in
				help)	usage 0	;;
				*)	printf 'Bad option %s\n\n' "${OPTARG}"; usage 2	1>&2	;;
			esac
			;;
		*)	printf 'Bad option %s\n\n' "${o}"; usage 2	1>&2	;;
	esac
done
shift $((OPTIND-1))

if [[ -z "${1}" ]] && (( ! buildline )); then
	usage 2
else
	target="${1}"
fi


# Search clipboard-specific SSH key
if [[ -z "${ssh_key}" ]]; then
	search_ssh_key=( ~/.ssh/id_*_clipboard )
	if [[ -r "${search_ssh_key[0]}" ]]; then
		ssh_key="${search_ssh_key[0]}"
		printf 'Found key:\t%s\n' "${ssh_key}"
	fi
fi

# Build forced command line for the authorized keys config
if (( buildline )); then
	echo '# forced command in ~/.ssh/authorized_keys'
	if [[ -z "${ssh_key}" ]]; then
		echo 'No key available to build a forced-command line' 1>&2
		exit 1
	fi
	echo "# Wayland"
	printf "command=\"/usr/bin/env WAYLAND_DISPLAY='wayland-0' wl-copy; echo copied\",no-agent-forwarding,no-pty,no-X11-forwarding %s\n" "$(< "${ssh_key%.pub}.pub" )"
	echo "# X11"
	printf 'command="xclip -display :0 -in -rmlastnl -selection clipboard; echo copied",no-agent-forwarding,no-pty,no-X11-forwarding %s\n' "$(< "${ssh_key%.pub}.pub" )"
	exit 0
fi


# Auto-detect display server
if [[ -n "${gfxsys}" ]]; then
	:
elif [[ "${XDG_SESSION_TYPE,,}" =~ wayland|x11 ]]; then
	gfxsys="${XDG_SESSION_TYPE,,}"
elif [[ -n "${WAYLAND_DISPLAY}" ]]; then
	gfxsys=wayland
	echo "Wayland detected"
elif [[ -n "${DISPLAY}" ]]; then
	gfxsys=x11
	echo "X11 detected"
fi

# Display-specific command
clipcmd=
if [[ "${gfxsys,}" == "x11" ]]; then
	clipcmd=( xclip -out -rmlastnl -selection clipboard ) # -display :0
else
	clipcmd=( wl-paste --no-newline )
fi


# Copy-Paste time!
echo "Pushing clipboard to ${target} using ${clipcmd[0]} on ${gfxsys^}..."

while read ${read_t:+ "${read_t[@]}" } -r result; do
	ssh_pid="$!"
	echo -e "${result}"
	kill -TERM "${ssh_pid}"
	exit 0
done < <(
	exec	\
		${timeout:+ "${timeout[@]}" }	\
		ssh	\
			${ssh_options:+ "${ssh_options[@]}" }	\
			${ssh_key:+ -o"ControlPath none" -o"IdentitiesOnly yes" -i "${ssh_key}"}	\
			"${target}"	\
			--	\
				/usr/bin/env WAYLAND_DISPLAY="wayland-0" wl-copy \;	\
				echo copied	\
			< <( "${clipcmd[@]}" )	\
	|| echo "fail ?" >&2
)

echo "timeout" >&2
exit 1

TODO

  • [ ] Store the code and the present documentation somewhere better, e.g., pull request into lan-mouse?

DrYak avatar Jul 25 '25 12:07 DrYak

@kevinJ045 small improvement suggestion:

content=$(wl-paste -n)
for ip in "${ips[@]}"; do
  echo "$content" | socat - TCP:$ip:$clsport

You paid attentions to avoid trailing lines with -n option of wl-paste... but are then accidentally reintroducing one with echo. I would suggest using echo's -n option:

content="$(wl-paste -n)"
for ip in "${ips[@]}"; do
  echo -n "$content" | socat - TCP:$ip:$clsport

also side-note, as you're using bash: bash has its own network stack with /dev/tcp/ and /dev/udp, so you could also potentially write:

echo -n "${content}" > "/devtcp/${ip}/${clsport}"

Also, FYI, I am trying to gather a collection of what people have implemented as solutions for clipboard sharing, so we can try to properly document it into lan-mouse. I think we could add yours, too.

DrYak avatar Jul 25 '25 12:07 DrYak

A very simple way I've found to get clipboard syncing to work is by using https://github.com/quackduck/uniclip.

Once installed, you can run uniclip on one machine and then it will prompt you with the command to run on the other, for example uniclip 10.42.0.200:38815.

One downside I've found with uniclip is that you currently cannot directly target which network interface you want to use, however the network interface it chooses from what I've found is consistent and will always choose the same network interface given the same network configuration.

I would first try running uniclip on both machines and see if either one targets the desired network interface first. Then you can turn off undesired network interfaces until it targets the desired one.

Additionally, you can set the port using the -p flag. That way the command to pair the devices always stays the same, for example uniclip -p 38815 and uniclip 10.42.0.200:38815.

tmcnulty387 avatar Jul 30 '25 15:07 tmcnulty387

Additionally, I've found that wl-copy and wl-paste, which all wayland clipboard sharing methods use, doesn't work and freezes when in GNOME's overview mode. I switched to hyprland on both devices using end4's dotfiles and it's now working great. Highly recommend if you're looking for an alternative to GNOME.

tmcnulty387 avatar Jul 30 '25 15:07 tmcnulty387

i let KDE Connect handle the clipboard and it works fine

paperluigis avatar Aug 06 '25 11:08 paperluigis

Note for people who run Plasma 6.5 and use the "focus stealing prevention" options: wl-clipboard is now broken (https://github.com/bugaevc/wl-clipboard/issues/268), you need to switch to package wl-clipboard-rs, at least version 0.9.2.

DrYak avatar Oct 24 '25 14:10 DrYak