
역설계된 Logi Options+ 에이전트 IPC 프로토콜. Unix 소켓(macOS) 또는 명명된 파이프(Windows)를 통해 프로그래밍 방식으로 Logitech 멀티호스트 기기를 전환합니다.
Logi Options+ 에이전트 IPC 프로토콜에 대한 리버스 엔지니어링 문서입니다. macOS와 Windows 모두에서 원시 HID 접근 없이 Logitech 멀티 호스트 장치(호스트 전환, 장치 쿼리)를 프로그래밍 방식으로 제어할 수 있습니다.
이 프로젝트 이전에는 이 프로토콜이 공개적으로 문서화된 적이 없습니다.
macOS는 커널 수준에서 Bluetooth 입력 장치에 대한 원시 HID 접근을 차단합니다. 권한, 자격 또는 해킹으로 이를 우회할 수 없습니다. Logi Options+ 에이전트는 Apple 서명 자격(com.apple.security.device.bluetooth)을 보유하여 Bluetooth HID 접근 권한을 부여받습니다. 이 프로젝트는 IPC 채널을 통해 에이전트와 통신합니다.
| 파일 | 설명 |
|---|---|
logi-options-ipc-reverse-engineering.md | 전체 리버스 엔지니어링 연대기 |
software-kvm-setup.md | 양방향 소프트웨어 KVM 설정 가이드 (Windows + Mac) |
switch_to_windows.py | Mac 측 스크립트: Unix 소켓 IPC를 통해 Logitech 장치 및 모니터 입력 전환 |
api-reference.md | 에이전트 API 참조: 작동 중인 엔드포인트, protobuf 유형, 장치 기능 |
kvm.ahk | AutoHotkey v2 스크립트: Win+1/2/3 단축키로 kvm_daemon_windows.py --switch 호출 (시스템 트레이에서 실행) |
kvm_daemon_windows.py | Windows 장치 전환 (명명된 파이프 사용), DDC/CI를 통한 모니터 전환 |
kvm_config.ini | Windows 구성 (단축키, 모니터 입력) |
query_feature_index.py | Logitech 장치(Windows)에 대한 HID++ ChangeHost 기능 인덱스 검색 |
query_agent_windows.py | Windows에서 명명된 파이프를 통해 에이전트 쿼리 |
config.ini | 레거시 UnifiedSwitch 구성 (kvm_daemon_windows.py로 대체됨) |
python3 switch_to_windows.py 0 # 호스트 0으로 전환 (DisplayPort)
python3 switch_to_windows.py 1 # 호스트 1로 전환 (HDMI)
python3 switch_to_windows.py --dry-run 0 # 실행될 작업을 미리 보여줌
Logi Options+가 실행 중이어야 하며 m1ddc가 설치되어 있어야 합니다 (brew install m1ddc).
# AHK 단축키 리스너 시작 (시스템 트레이에서 실행, 콘솔 없음)
# AutoHotkey v2 필요: winget install AutoHotkey.AutoHotkey
start kvm.ahk
# 또는 명령줄에서 직접 전환
python kvm_daemon_windows.py --switch 1
# 전환하지 않고 발견된 장치 및 구성된 단축키 표시
python kvm_daemon_windows.py --dry-run
Logi Options+가 실행 중이어야 합니다. 의존성 설치: pip install pywin32.
AHK 스크립트는 Win+1/2/3을 감지하여 각각 kvm_daemon_windows.py --switch N을 호출합니다. Python 스크립트는 에이전트에서 장치를 자동으로 검색합니다(하드코딩된 장치 ID나 HID 경로 불필요). kvm_config.ini를 편집하여 단축키와 모니터 DDC/CI 입력 값을 구성합니다.
에이전트는 다음에서 수신합니다:
/tmp/logitech_kiros_agent-<hash>)\\.\pipe\logitech_kiros_agent-<hash>)두 플랫폼 모두 동일한 와이어 프로토콜을 사용합니다. 바이너리 프레임 형식:
LE32(total_len) + BE32(proto_name_len) + "json" + BE32(msg_len) + JSON_message
장치를 다른 호스트로 전환:
{
"msg_id": "1",
"verb": "SET",
"path": "/change_host/<device_id>/host",
"payload": {
"@type": "type.googleapis.com/logi.protocol.devices.ChangeHost",
"host": 0
}
}
페이로드는 인라인 JSON으로 직렬화된 google.protobuf.Any 필드이며 @type 주석이 포함됩니다. 에이전트는 엄격한 protobuf JSON 파서를 사용하므로, 알 수 없는 필드가 있으면 INVALID_MESSAGE_RECEIVED가 발생합니다.
요청은 msg_id(snake_case)를 사용합니다. 응답은 msgId(camelCase)를 사용합니다. 동사는 문자열입니다: "GET", "SET", "SUBSCRIBE", "BROADCAST".
전체 프로토콜 문서는 logi-options-ipc-reverse-engineering.md를 참조하세요.
장기 실행 자동화의 경우 BrokenPipeError 시 재연결하고 소켓/파이프 경로를 다시 검색하세요.
이 사항은 에이전트를 통하지 않고 직접 HID++ 명령을 보낼 때 적용됩니다.
kvm_daemon_windows.py는 에이전트의 명명된 파이프를 통해 통신하므로 이 모든 문제를 피합니다.
HID++ 컬렉션은 장치마다 다릅니다. MX Master 3S는 COL02에서 HID++을 노출합니다. MX Keys S는 COL05를 사용합니다. 둘 다 사용 페이지 FF43:0202를 사용합니다. 다음으로 확인:
Get-PnpDeviceProperty -InstanceId "<instance_id>" -KeyName DEVPKEY_Device_HardwareIds
# UP:FF43_U:0202 찾기
기능 인덱스는 장치마다 다릅니다. ChangeHost(0x1814)는 MX Keys S에서는 인덱스 0x0A에 있지만 MX Mechanical에서는 0x09에 있습니다. IRoot::GetFeature를 통해 런타임에 쿼리:
Send: {0x11, 0x00, 0x00, 0x0D, 0x18, 0x14, ...} (20 bytes)
Read: 응답 바이트 4 = 기능 인덱스
장치 재페어링 시 HID 경로가 변경됩니다. Bolt 수신기에서 직접 BT LE로 전환하면 경로가 완전히 변경됩니다. query_agent_windows.py를 실행하여 에이전트의 현재 경로를 가져오세요.
BT LE GATT 공급업체 컬렉션이 "알 수 없음"으로 표시됩니다. Windows가 때때로 HID++ GATT 서비스를 초기화하지 못합니다. 장치는 정상 작동하지만 공급업체 명령 채널이 죽습니다. 해결 방법: Windows 설정에서 Bluetooth를 껐다 켜세요. Windows/펌웨어 문제입니다.
| 버전 | 상태 |
|---|---|
| Logi Options+ 2.0.840907 | 작동 확인 (macOS Tahoe, Windows 11) |
와이어 프로토콜과 핵심 API 경로(/devices/list, /change_host/<id>/host)는 안정적으로 유지되었습니다. 장치 재페어링으로 HID 경로와 컬렉션 번호가 변경되었지만 IPC 프로토콜 자체에는 영향을 미치지 않았습니다.
이 프로젝트는 교육 및 연구 목적으로만 제공됩니다. Logitech이 언제든지 변경하거나 제거할 수 있는 문서화되지 않고 지원되지 않는 프로토콜을 문서화합니다. 저자는 이 코드 또는 문서 사용으로 인한 손상, 데이터 손실, 장치 손상 또는 기능 손상에 대해 책임을 지지 않습니다. 사용에 따른 위험은 사용자가 감수합니다.
이 프로젝트는 Logitech과 제휴하거나 보증하지 않습니다.
| 시나리오 | 발생 상황 | 감지 방법 |
|---|
| 에이전트 실행 중 아님 | 소켓/파이프가 존재하지 않음 | connect()에서 FileNotFoundError 또는 ConnectionRefusedError 발생 |
| 세션 중 에이전트 재시작 | 연결 끊김 | send()에서 BrokenPipeError 발생; recv()가 빈 값을 반환 |
| 장치가 다른 호스트에 있음 | NO_SUCH_PATH | result.code 확인 |
| 장치에 연결할 수 없음 | 약 3초 후 TIMEOUT | result.code 확인 |
| 잘못된 페이로드 | INVALID_MESSAGE_RECEIVED | @type 누락 또는 알 수 없는 필드 |
| 소켓 해시 변경 | 이전 경로 사라짐 | 항상 동적으로 검색하고 하드코딩하지 않음 |
| 재시작 후 오래된 소켓 | ConnectionRefusedError | 짧은 지연 후 재시도 |
| 동시 클라이언트 | 정상 작동 | 에이전트가 여러 연결 처리 |