Clipboard sharing workaround for Whonix KVM (note: NOT reviewed by Whonix developers)

@@@@@ Setting up a shared clipboard between Arch Linux (Wayland, you can redo it for any Linux yourself) and Whonix-Gateway (Wayland) works by pressing a button (for example, the “Pause” key):

@@@@@ Настройка общего буфера обмена между Arch Linux (Wayland, можете сами переделать под любой Linux) и Whonix-Gateway (Wayland) работает по нажатию кнопки (например, клавиши “Pause”):

1.0. Заходим в настройки Whonix-Gateway в Qemu “Обзор” xml - Найдите <channel> добавьте новые каналы связи:

	<channel type='unix'>
		<target type='virtio' name='clip.gw2host'/>
	</channel>
	<channel type='unix'>
		<target type='virtio' name='clip.host2gw'/>
	</channel>

1.1. Добавить пользователя в группу libvirt-qemu:
sudo usermod -aG libvirt-qemu $(whoami)

1.2. Перезапустить ПК

2.0. НИЖЕ НАСТРОЙКА РЕАЛЬНОГО ПК (Arch Linux, Wayland):
sudo pacman -S --noconfirm wl-clipboard socat

2.1. Скрипт автоприёма от Gateway:

tee ~/.local/bin/clip-gw2host-listen.sh > /dev/null <<'EOF'
#!/bin/bash
PID_FILE="/tmp/.clip-gw2host-wlcopy-pid"
while true; do
    SOCK=$(find /run/libvirt/qemu/channel -iname "clip.gw2host" 2>/dev/null | head -1)
    if [ -n "$SOCK" ]; then
        socat -u UNIX-CONNECT:"$SOCK" - | while IFS='|' read -r MIME B64; do
            [ -z "$B64" ] && continue
            OLD_PID=$(cat "$PID_FILE" 2>/dev/null)
            [ -n "$OLD_PID" ] && kill "$OLD_PID" 2>/dev/null
            echo "$B64" | base64 -d | wl-copy --foreground -t "$MIME" &
            echo $! > "$PID_FILE"
        done
    fi
    sleep 1
done
EOF
chmod +x ~/.local/bin/clip-gw2host-listen.sh

2.2. Сервис автоприёма от Gateway:

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/clip-gw2host.service <<'EOF'
[Unit]
Description=Clipboard sync Gateway->host

[Service]
ExecStart=%h/.local/bin/clip-gw2host-listen.sh
Restart=always
KillMode=control-group

[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now clip-gw2host.service

2.3. Персистентный скрипт отправки (host → Gateway VM):

mkfifo -m 600 /tmp/clip-host2gw-fifo 2>/dev/null
tee ~/.local/bin/clip-host2gw-sender-daemon.sh > /dev/null <<'EOF'
#!/bin/bash
FIFO="/tmp/clip-host2gw-fifo"
[ -p "$FIFO" ] || mkfifo -m 600 "$FIFO"
exec 3<>"$FIFO"
while true; do
    GW_SOCK=$(find /run/libvirt/qemu/channel -iname "clip.host2gw" 2>/dev/null | head -1)
    if [ -n "$GW_SOCK" ]; then
        socat -u FD:3 UNIX-CONNECT:"$GW_SOCK"
    fi
    sleep 1
done
EOF
chmod +x ~/.local/bin/clip-host2gw-sender-daemon.sh

2.4. Персистентный сервис отправки (host → Gateway VM):

cat > ~/.config/systemd/user/clip-host2gw-sender.service <<'EOF'
[Unit]
Description=Persistent clipboard sender host->Gateway
[Service]
ExecStart=%h/.local/bin/clip-host2gw-sender-daemon.sh
Restart=always
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now clip-host2gw-sender.service

2.5. Один скрипт для отправки буфера обмена во все запущенные в данный момент VM по нажатию кнопки (host → Gateway VM):

mkdir -p ~/.local/bin
tee ~/.local/bin/send_clipboard_to_all_vm.sh > /dev/null <<'EOF'
#!/bin/bash
MIME=""
for TYPE in image/png image/bmp image/tiff image/jpeg image/gif image/webp; do
    FOUND=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^${TYPE}\$")
    [ -n "$FOUND" ] && { MIME="$FOUND"; break; }
done
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 '^text/plain;charset=utf-8$')
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 '^text/plain$')
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 '^UTF8_STRING$')
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 '^STRING$')
[ -z "$MIME" ] && exit 0
RAW_SIZE=$(timeout 3 wl-paste -n -t "$MIME" 2>/dev/null | wc -c)
[ "$RAW_SIZE" -eq 0 ] && exit 0
[ "$RAW_SIZE" -gt 1073741824 ] && exit 0
B64=$(timeout 3 wl-paste -n -t "$MIME" 2>/dev/null | base64 -w0)
[ -z "$B64" ] && exit 0
LINE="${MIME}|${B64}"
for FIFO in /tmp/clip-host2vm-fifo /tmp/clip-host2gw-fifo; do
    [ -p "$FIFO" ] || continue
    ( printf '%s\n' "$LINE" > "$FIFO" ) &
    WPID=$!
    ( sleep 1.5; kill -9 "$WPID" 2>/dev/null ) 2>/dev/null &
done
wait 2>/dev/null
EOF
chmod +x ~/.local/bin/send_clipboard_to_all_vm.sh

2.6. Добавить бинд на синхронизацию последних скопированных данных (c ПК на VM):

  • Важно: Редактирование существующее сочетания нельзя (может не примениться), нужно пересоздавать!
  • Название: send_clipboard_to_all_vm
  • Команда: sh -c "$HOME/.local/bin/send_clipboard_to_all_vm.sh"
  • Кнопка: например, клавиша “Pause”

3.0. НИЖЕ НАСТРОЙКА ВНУТРИ Whonix-Gateway:

3.1. Чтобы все команды ниже не переписывать вручную - создаем блокнот “New Paste | Rocket Powered Pastebin” и вставляем пункты 3 и 4 гайда ниже (внутри VM открываем свою короткую ссылку, замените “https://paste.rs/Tguxi” обязательно без .txt в конце ссылки):

sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y torsocks
torsocks curl -s https://paste.rs/Tguxi ;echo

3.2. Установить пакеты:
sudo apt-get install -y wl-clipboard socat

3.3. Разрешить чтение устройства без root:

sudo tee /etc/udev/rules.d/99-virtio-clip-gw.rules > /dev/null <<'EOF'
SUBSYSTEM=="virtio-ports", ATTR{name}=="clip.host2gw", MODE="0660", GROUP="sudo"
SUBSYSTEM=="virtio-ports", ATTR{name}=="clip.gw2host", MODE="0660", GROUP="sudo"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger

3.4. Скрипт приёма (host → Gateway VM):

sudo tee /usr/local/bin/clip-host2gw.sh > /dev/null <<'EOF'
#!/bin/bash
PID_FILE="/tmp/.clip-host2gw-wlcopy-pid"
while true; do
    while IFS='|' read -r MIME B64; do
        [ -z "$B64" ] && continue
        OLD_PID=$(cat "$PID_FILE" 2>/dev/null)
        [ -n "$OLD_PID" ] && kill "$OLD_PID" 2>/dev/null
        printf "%s" "$B64" | base64 -d | wl-copy --foreground -t "$MIME" &
        echo $! > "$PID_FILE"
    done < /dev/virtio-ports/clip.host2gw
    sleep 0.2
done
EOF
sudo chmod +x /usr/local/bin/clip-host2gw.sh

3.5. Сервис приёма (просто вызывает скрипт выше):

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/clip-host2gw.service <<'EOF'
[Unit]
Description=Clipboard sync host->Gateway
After=graphical-session.target

[Service]
ExecStart=/usr/local/bin/clip-host2gw.sh
Restart=always
RestartSec=3

[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now clip-host2gw.service
systemctl --user status clip-host2gw.service --no-pager

3.6. Скрипт автоотправки (Gateway VM → host):

sudo tee /usr/local/bin/clip-gw2host-send.sh > /dev/null <<'EOF'
#!/bin/bash
MIME=""
for TYPE in image/png image/bmp image/tiff image/jpeg image/gif image/webp; do
    FOUND=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^${TYPE}\$")
    [ -n "$FOUND" ] && { MIME="$FOUND"; break; }
done
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^text/plain;charset=utf-8\$")
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^text/plain\$")
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^UTF8_STRING\$")
[ -z "$MIME" ] && MIME=$(timeout 2 wl-paste --list-types 2>/dev/null | grep -m1 "^STRING\$")
[ -z "$MIME" ] && exit 0
RAW_SIZE=$(timeout 3 wl-paste -n -t "$MIME" 2>/dev/null | wc -c)
[ "$RAW_SIZE" -eq 0 ] && exit 0
[ "$RAW_SIZE" -gt 1073741824 ] && exit 0
B64=$(timeout 3 wl-paste -n -t "$MIME" 2>/dev/null | base64 -w0)
[ -z "$B64" ] && exit 0
printf "%s|%s\n" "$MIME" "$B64" > /dev/virtio-ports/clip.gw2host
EOF
sudo chmod +x /usr/local/bin/clip-gw2host-send.sh

3.7. Добавить бинд на синхронизацию последних скопированных данных (c Gateway VM на ПК), например клавиша “Pause”:

mkdir -p ~/.config/labwc
cat > ~/.config/labwc/rc.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config>
  <keyboard>
    <keybind key="Pause">
      <action name="Execute" command="/usr/local/bin/clip-gw2host-send.sh"/>
    </keybind>
  </keyboard>
</openbox_config>
EOF
labwc --reconfigure

RU - Написать код было тяжело, получился он довольно безопасным, умеет передавать как текст так и фото, код я тестировал/чинил долгое время это финальный вариант. Тоже самое на Github (еще у меня там есть гайд общего буфера обмена между Arch Linux и Arch Linux VM оба Wayland):

EN - It was hard to write the code, it turned out to be quite secure, it can transmit both text and photos, I tested / repaired the code for a long time, this is the final version. The same is on Github (I also have a shared clipboard guide there between Arch Linux and Arch Linux VM, both Wayland):

https://github.com/MonExpl/Fix-copy-paste-Whonix-Gateway-VM-Clipboard