Llevo muchísimo tiempo utilizando Parsec como mi compañero de aventuras para Remote Desktop y para poder jugar desde cualquier parte. Siempre me ha funcionado muy bien, pero tiene un pequeño “gran” inconveniente para mi caso de uso: llevan una eternidad sin sacar una aplicación oficial para iOS. Tienen app para Android, sí, pero como yo utilizo fuertemente el ecosistema de Apple, esto siempre ha supuesto una piedra en el camino.

Así que decidí dar el salto a Moonlight. Concretamente, me he montado el chiringuito a través de Apollo, el fork de Sunshine. La experiencia de vídeo y respuesta es excelente, pero enseguida me di cuenta de que había ciertas cosas que necesitaba imperiosamente y que no estaban muy afinadas.

🔧 El problema: El portapapeles en Moonlight

Para un programa específico que utilizo a diario, necesito recibir lo copiado en el portapapeles (clipboard) de forma rápida y transparente. Resulta que Moonlight, aunque tiene una forma de copiar texto, no lo hace automáticamente. Por si fuera poco, requiere usar una combinación de teclas diferente, lo que corta por completo el flujo de trabajo.

🔧 La solución: Sincronización bidireccional

Como no me iba a quedar de brazos cruzados, decidimos buscar una forma de compartir los portapapeles directamente entre Windows y el Mac. La solución pasaba por crear un pequeño script en bash para el lado de macOS que hiciera uso de Netcat (nc) para enviar y recibir datos en texto plano.

rita Avatar
Rita
“¡A su mecha! Ya me estaba saliendo callo en el dedo de tanto copiar a la antigua.
kaotchi Avatar
Kaotchi
“Bueno, no es para tanto, que solo es un script para no perder el ritmo…” 😅
adrian Avatar
Adrian
“En realidad, usamos Netcat porque es como un túnel directo; sin protocolos pesados, el texto vuela de una máquina a otra al instante.” ⚡

Os dejo por aquí los códigos que utilizo para que la magia ocurra. En el Mac uso un script bash con Netcat (nc), y en Windows un script de PowerShell que hace de espejo.

🍎 Lado Mac (clip-sync.sh)

#!/bin/bash
echo "クリップボード双方向同期中..."
echo "Windows -> Mac (ポート9999) / Mac -> Windows (ポート9998)"
echo ""

WINDOWS_IP="192.168.50.2"
ULTIMO=""
RECIBIDO=""

# Listener en background: recibe de Windows
while true; do
  contenido=$(nc -l 9999)
  RECIBIDO="$contenido"
  echo "$contenido" | pbcopy
  preview=$(echo "$contenido" | head -c 80 | tr '\n' ' ')
  echo "受信 $(date '+%H:%M:%S') Win->Mac: $preview"
done &

# Sender: envía a Windows cuando cambia el portapapeles local
while true; do
  actual=$(pbpaste)
  if [ "$actual" != "$ULTIMO" ] && [ "$actual" != "$RECIBIDO" ]; then
    echo "$actual" | nc -w 1 $WINDOWS_IP 9998
    preview=$(echo "$actual" | head -c 80 | tr '\n' ' ')
    echo "送信 $(date '+%H:%M:%S') Mac->Win: $preview"
    ULTIMO="$actual"
    RECIBIDO=""
  fi
  sleep 0.1
done

🪟 Lado Windows (clip-sync.ps1)

Para Windows, el script es algo más complejo porque maneja directamente los sockets de red de .NET para asegurar que la codificación UTF8 sea perfecta:

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
chcp 65001 | Out-Null

$mac = "192.168.50.2" # IP de tu Mac
$puertoEnvio = 9999
$puertoRecibe = 9998
$ultimo = ""
$recibido = ""

Write-Host "クリップボード双方向同期中..." -ForegroundColor Cyan
Write-Host "Windows -> Mac (ポート$puertoEnvio) / Mac -> Windows (ポート$puertoRecibe)`n" -ForegroundColor Gray

$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $puertoRecibe)
$listener.Start()
Write-Host "受信待機中 (ポート$puertoRecibe)..." -ForegroundColor Gray

while ($true) {
    if ($listener.Pending()) {
        $client = $listener.AcceptTcpClient()
        $stream = $client.GetStream()
        $bytes = New-Object byte[] 65536
        $len = $stream.Read($bytes, 0, $bytes.Length)
        $texto = [System.Text.Encoding]::UTF8.GetString($bytes, 0, $len)
        $client.Close()
        Set-Clipboard -Value $texto
        $recibido = $texto
        $ultimo = $texto
        $preview = if ($texto.Length -gt 80) { $texto.Substring(0,80) + "..." } else { $texto }
        $preview = $preview -replace "`r`n|`n", " "
        Write-Host "受信 $(Get-Date -Format 'HH:mm:ss') Mac->Win: $preview" -ForegroundColor Yellow
    }

    $actual = (Get-Clipboard -Raw)
    if ($actual -and $actual -ne $ultimo) {
        try {
            $client = New-Object System.Net.Sockets.TcpClient($mac, $puertoEnvio)
            $stream = $client.GetStream()
            $bytes = [System.Text.Encoding]::UTF8.GetBytes($actual)
            $stream.Write($bytes, 0, $bytes.Length)
            $client.Close()
            $preview = if ($actual.Length -gt 80) { $actual.Substring(0,80) + "..." } else { $actual }
            $preview = $preview -replace "`r`n|`n", " "
            Write-Host "送信 $(Get-Date -Format 'HH:mm:ss') Win->Mac: $preview" -ForegroundColor Green
        } catch {
            Write-Host "エラー $(Get-Date -Format 'HH:mm:ss') Macに接続できません" -ForegroundColor Red
        }
        $ultimo = $actual
    }

    Start-Sleep -Milliseconds 300
}

Básicamente, lo que hace el script de Mac es levantar un proceso en segundo plano escuchando en el puerto 9999 todo lo que venga desde Windows y meterlo directamente al pbcopy. A su vez, el script de Windows escucha en el puerto 9998 y utiliza Set-Clipboard para actualizar el portapapeles local.

Con esto corriendo en ambas máquinas, ya he podido hacer un buen uso del clipboard sin tener que pelearme con atajos de teclado extraños de Moonlight. Otra pequeña victoria para el cacharreo.

rita Avatar
Rita
“¡Excelente! Ahora podré mandarte todos mis stickers peruanos al Windows en un segundo. ¡Prepárate, Kaotchi!” 🇵🇪🔥
kaotchi Avatar
Kaotchi
“¡Madre mía! No sé si ha sido buena idea arreglar esto…” 🤦‍♂️
adrian Avatar
Adrian
“Por cierto, si alguna vez no conecta, revisad que el Firewall de Windows no esté bloqueando los puertos 9998 o 9999, que a veces se pone caprichoso.” 🤖🛠️
tako-chan
Avatar de KAOTCHI
ESCRITO POR:

Amo la música, los juegos y aprender idiomas.

Me encanta descubrir nuevas cosas sobre diseño web, y sueño con lograr todo lo que tengo planeado en ese mundo.