
Protocolo IPC del agente Logi Options+ con ingeniería inversa. Cambia dispositivos Logitech multi-host programáticamente a través de socket Unix (macOS) o tubería nombrada (Windows).
Documentación con ingeniería inversa del protocolo IPC del agente Logi Options+. Permite el control programático de dispositivos Logitech multi-host (cambio de host, consultas de dispositivos) sin acceso HID en bruto, tanto en macOS como en Windows.
El protocolo no había sido documentado públicamente antes de este proyecto.
macOS bloquea el acceso HID en bruto a dispositivos de entrada Bluetooth a nivel de kernel. Ningún permiso, entitlement o truco lo evita. El agente Logi Options+ tiene entitlements firmados por Apple (com.apple.security.device.bluetooth) que le otorgan acceso HID Bluetooth. Este proyecto se comunica con el agente a través de su canal IPC.
| Archivo | Descripción |
|---|---|
logi-options-ipc-reverse-engineering.md | Crónica completa de la ingeniería inversa |
software-kvm-setup.md | Guía de configuración KVM de software bidireccional (Windows + Mac) |
switch_to_windows.py | Script del lado Mac que cambia dispositivos Logitech y entrada del monitor mediante IPC por socket Unix |
api-reference.md | Referencia de la API del agente: endpoints funcionales, tipos protobuf, capacidades de dispositivos |
kvm.ahk | Script AutoHotkey v2: teclas rápidas Win+1/2/3 que llaman a kvm_daemon_windows.py --switch (se ejecuta en la bandeja del sistema) |
kvm_daemon_windows.py | Cambio de dispositivos en Windows mediante named pipe, cambio de monitor mediante DDC/CI |
kvm_config.ini | Configuración de Windows (teclas rápidas, entradas de monitor) |
query_feature_index.py | Descubre el índice de la funcionalidad ChangeHost de HID++ para dispositivos Logitech (Windows) |
query_agent_windows.py | Consulta al agente en Windows mediante named pipe |
config.ini | Configuración heredada de UnifiedSwitch (reemplazada por kvm_daemon_windows.py) |
python3 switch_to_windows.py 0 # Cambiar al host 0 (DisplayPort)
python3 switch_to_windows.py 1 # Cambiar al host 1 (HDMI)
python3 switch_to_windows.py --dry-run 0 # Mostrar lo que sucedería
Requiere Logi Options+ en ejecución y m1ddc instalado (brew install m1ddc).
# Iniciar el oyente de teclas rápidas AHK (se ejecuta en la bandeja del sistema, sin consola)
# Requiere AutoHotkey v2: winget install AutoHotkey.AutoHotkey
start kvm.ahk
# O cambiar directamente desde la línea de comandos
python kvm_daemon_windows.py --switch 1
# Mostrar dispositivos descubiertos y teclas rápidas configuradas sin cambiar
python kvm_daemon_windows.py --dry-run
Requiere Logi Options+ en ejecución. Instalar dependencias: pip install pywin32.
El script AHK escucha Win+1/2/3 y llama a kvm_daemon_windows.py --switch N para cada uno. El script Python descubre los dispositivos automáticamente desde el agente (sin IDs de dispositivos ni rutas HID fijas). Edite kvm_config.ini para configurar teclas rápidas y valores de entrada DDC/CI del monitor.
El agente escucha en:
/tmp/logitech_kiros_agent-<hash>\\.\pipe\logitech_kiros_agent-<hash>Mismo protocolo alámbrico en ambas plataformas. Formato de trama binaria:
LE32(total_len) + BE32(proto_name_len) + "json" + BE32(msg_len) + JSON_message
Cambiar un dispositivo a un host diferente:
{
"msg_id": "1",
"verb": "SET",
"path": "/change_host/<device_id>/host",
"payload": {
"@type": "type.googleapis.com/logi.protocol.devices.ChangeHost",
"host": 0
}
}
El payload es un campo google.protobuf.Any serializado como JSON en línea con una anotación @type. El agente utiliza un analizador JSON estricto de protobuf; los campos desconocidos causan INVALID_MESSAGE_RECEIVED.
Las solicitudes usan msg_id (snake_case). Las respuestas usan msgId (camelCase). Los verbos son cadenas: "GET", "SET", "SUBSCRIBE", "BROADCAST".
Consulte logi-options-ipc-reverse-engineering.md para la documentación completa del protocolo.
Para automatización de larga duración, reconectar en BrokenPipeError y redescubrir la ruta del socket/pipe.
Estos aplican al enviar comandos HID++ directamente, no a través del agente.
kvm_daemon_windows.pylos evita todos al usar el named pipe del agente.
La colección HID++ varía por dispositivo. El MX Master 3S expone HID++ en COL02. El MX Keys S usa COL05. Ambos usan la página de uso FF43:0202. Verificar con:
Get-PnpDeviceProperty -InstanceId "<instance_id>" -KeyName DEVPKEY_Device_HardwareIds
# Buscar: UP:FF43_U:0202
Los índices de funcionalidad difieren por dispositivo. ChangeHost (0x1814) está en el índice 0x0A en MX Keys S pero en 0x09 en MX Mechanical. Consultar en tiempo de ejecución mediante IRoot::GetFeature:
Enviar: {0x11, 0x00, 0x00, 0x0D, 0x18, 0x14, ...} (20 bytes)
Leer: byte 4 de respuesta = índice de funcionalidad
El reemparejamiento del dispositivo cambia las rutas HID. Cambiar del receptor Bolt a BT LE directo cambia la ruta por completo. Ejecutar query_agent_windows.py para obtener las rutas actuales desde el agente.
La colección de proveedor GATT de BT LE aparece como "Desconocida". Windows falla ocasionalmente al inicializar el servicio HID++ GATT. El dispositivo funciona normalmente pero el canal de comandos del proveedor está muerto. Solución: alternar Bluetooth apagado/encendido en Configuración de Windows. Es un problema de Windows/firmware.
| Versión | Estado |
|---|---|
| Logi Options+ 2.0.840907 | Funcionando (macOS Tahoe, Windows 11) |
El protocolo alámbrico y las rutas principales de la API (/devices/list, /change_host/<id>/host) se han mantenido estables. El reemparejamiento del dispositivo rompió las rutas HID y los números de colección, pero el protocolo IPC en sí no se vio afectado.
Este proyecto es solo para fines educativos y de investigación. Documenta un protocolo no documentado y no soportado que Logitech puede cambiar o eliminar en cualquier momento. Los autores no son responsables de ningún daño, pérdida de datos, dispositivos inutilizados o funcionalidad rota resultante del uso de este código o documentación. Úselo bajo su propio riesgo.
Este proyecto no está afiliado ni respaldado por Logitech.
| Escenario | Qué sucede | Detección |
|---|
| Agente no en ejecución | Socket/pipe no existe | connect() lanza FileNotFoundError o ConnectionRefusedError |
| El agente se reinicia durante la sesión | La conexión se interrumpe | send() lanza BrokenPipeError; recv() devuelve vacío |
| Dispositivo en otro host | NO_SUCH_PATH | Verificar result.code |
| Dispositivo inalcanzable | TIMEOUT después de ~3s | Verificar result.code |
| Payload mal formado | INVALID_MESSAGE_RECEIVED | Falta @type o campos desconocidos |
| El hash del socket cambia | Ruta antigua desaparecida | Siempre descubrir dinámicamente, nunca fijar |
| Socket obsoleto tras reinicio | ConnectionRefusedError | Reintentar tras breve retardo |
| Clientes concurrentes | Funciona bien | El agente maneja múltiples conexiones |