
Protocollo IPC dell'agente Logi Options+ ottenuto tramite reverse engineering. Passa da dispositivi Logitech multi-host programmaticamente tramite socket Unix (macOS) o named pipe (Windows).
Documentazione ricostruita tramite reverse engineering del protocollo IPC dell'agente Logi Options+. Abilita il controllo programmatico dei dispositivi Logitech multi-host (cambio host, query sui dispositivi) senza accesso HID raw, sia su macOS che su Windows.
Il protocollo non era stato documentato pubblicamente prima di questo progetto.
macOS blocca l'accesso HID raw ai dispositivi di input Bluetooth a livello di kernel. Nessun permesso, entitlement o hack lo bypassa. L'agente Logi Options+ possiede entitlement firmati da Apple (com.apple.security.device.bluetooth) che gli concedono l'accesso HID Bluetooth. Questo progetto comunica con l'agente attraverso il suo canale IPC invece.
| File | Descrizione |
|---|---|
logi-options-ipc-reverse-engineering.md | Cronaca completa del reverse engineering |
software-kvm-setup.md | Guida alla configurazione KVM software bidirezionale (Windows + Mac) |
switch_to_windows.py | Script lato Mac che cambia i dispositivi Logitech e l'input del monitor tramite IPC su socket Unix |
api-reference.md | Riferimento API dell'agente: endpoint funzionanti, tipi protobuf, capacità dei dispositivi |
kvm.ahk | Script AutoHotkey v2: hotkey Win+1/2/3 che chiamano kvm_daemon_windows.py --switch (gira nella tray di sistema) |
kvm_daemon_windows.py | Cambio dispositivi Windows tramite named pipe, cambio monitor tramite DDC/CI |
kvm_config.ini | Configurazione Windows (hotkey, ingressi monitor) |
query_feature_index.py | Scopre l'indice della feature HID++ ChangeHost per i dispositivi Logitech (Windows) |
query_agent_windows.py | Interroga l'agente su Windows tramite named pipe |
config.ini | Configurazione legacy UnifiedSwitch (sostituita da kvm_daemon_windows.py) |
python3 switch_to_windows.py 0 # Passa all'host 0 (DisplayPort)
python3 switch_to_windows.py 1 # Passa all'host 1 (HDMI)
python3 switch_to_windows.py --dry-run 0 # Mostra cosa succederebbe
Richiede Logi Options+ in esecuzione e m1ddc installato (brew install m1ddc).
# Avvia l'ascoltatore di hotkey AHK (gira nella tray di sistema, senza console)
# Richiede AutoHotkey v2: winget install AutoHotkey.AutoHotkey
start kvm.ahk
# Oppure cambia direttamente dalla riga di comando
python kvm_daemon_windows.py --switch 1
# Mostra i dispositivi trovati e le hotkey configurate senza cambiare
python kvm_daemon_windows.py --dry-run
Richiede Logi Options+ in esecuzione. Installa le dipendenze: pip install pywin32.
Lo script AHK ascolta Win+1/2/3 e chiama kvm_daemon_windows.py --switch N per ciascuno. Lo script Python scopre automaticamente i dispositivi dall'agente (senza ID dispositivo o percorsi HID hardcodati). Modifica kvm_config.ini per configurare hotkey e valori di ingresso monitor DDC/CI.
L'agente ascolta su:
/tmp/logitech_kiros_agent-<hash>\\.\pipe\logitech_kiros_agent-<hash>Stesso protocollo di rete su entrambe le piattaforme. Formato frame binario:
LE32(total_len) + BE32(proto_name_len) + "json" + BE32(msg_len) + JSON_message
Per cambiare un dispositivo a un host diverso:
{
"msg_id": "1",
"verb": "SET",
"path": "/change_host/<device_id>/host",
"payload": {
"@type": "type.googleapis.com/logi.protocol.devices.ChangeHost",
"host": 0
}
}
Il payload è un campo google.protobuf.Any serializzato come JSON inline con un'annotazione @type. L'agente utilizza un parser JSON protobuf rigoroso; campi sconosciuti causano INVALID_MESSAGE_RECEIVED.
Le richieste usano msg_id (snake_case). Le risposte usano msgId (camelCase). I verbi sono stringhe: "GET", "SET", "SUBSCRIBE", "BROADCAST".
Vedi logi-options-ipc-reverse-engineering.md per la documentazione completa del protocollo.
Per automazione a lunga esecuzione, riconnetti in caso di BrokenPipeError e riscopri il percorso del socket/pipe.
Queste si applicano quando si inviano comandi HID++ direttamente, non tramite l'agente.
kvm_daemon_windows.pyle evita tutte passando attraverso la named pipe dell'agente.
La collection HID++ varia per dispositivo. MX Master 3S espone HID++ su COL02. MX Keys S usa COL05. Entrambi usano la pagina di utilizzo FF43:0202. Verifica con:
Get-PnpDeviceProperty -InstanceId "<instance_id>" -KeyName DEVPKEY_Device_HardwareIds
# Cerca: UP:FF43_U:0202
Gli indici delle feature differiscono per dispositivo. ChangeHost (0x1814) è all'indice 0x0A su MX Keys S ma 0x09 su MX Mechanical. Interroga a runtime via IRoot::GetFeature:
Invio: {0x11, 0x00, 0x00, 0x0D, 0x18, 0x14, ...} (20 byte)
Lettura: byte 4 della risposta = indice feature
L'accoppiamento del dispositivo cambia i percorsi HID. Passare dal ricevitore Bolt al BT LE diretto cambia completamente il percorso. Esegui query_agent_windows.py per ottenere i percorsi correnti dall'agente.
La collection vendor GATT BT LE diventa "Unknown". Windows occasionalmente non riesce a inizializzare il servizio HID++ GATT. Il dispositivo funziona normalmente ma il canale di comando vendor è morto. Soluzione: spegni/riaccendi Bluetooth da Impostazioni Windows. È un problema di Windows/firmware.
| Versione | Stato |
|---|---|
| Logi Options+ 2.0.840907 | Funzionante (macOS Tahoe, Windows 11) |
Il protocollo di rete e i percorsi API principali (/devices/list, /change_host/<id>/host) sono rimasti stabili. Il riaccoppiamento dei dispositivi ha rotto i percorsi HID e i numeri di collection, ma il protocollo IPC stesso non è stato influenzato.
Questo progetto è solo a scopo educativo e di ricerca. Documenta un protocollo non documentato e non supportato che Logitech può modificare o rimuovere in qualsiasi momento. Gli autori non sono responsabili per danni, perdita di dati, dispositivi danneggiati o funzionalità interrotte derivanti dall'uso di questo codice o della documentazione. Utilizzo a proprio rischio.
Questo progetto non è affiliato o approvato da Logitech.
| Scenario | Cosa succede | Rilevamento |
|---|
| Agente non in esecuzione | Socket/pipe non esiste | connect() solleva FileNotFoundError o ConnectionRefusedError |
| Agente riavviato durante la sessione | Connessione interrotta | send() solleva BrokenPipeError; recv() restituisce vuoto |
| Dispositivo su altro host | NO_SUCH_PATH | Controlla result.code |
| Dispositivo irraggiungibile | TIMEOUT dopo ~3s | Controlla result.code |
| Payload malformato | INVALID_MESSAGE_RECEIVED | @type mancante o campi sconosciuti |
| Hash socket cambiato | Vecchio percorso scomparso | Scopri sempre dinamicamente, mai hardcodare |
| Socket obsoleto dopo riavvio | ConnectionRefusedError | Riprova dopo un breve ritardo |
| Client concorrenti | Funziona bene | L'agente gestisce più connessioni |