
リバースエンジニアリングされた Logi Options+ エージェント IPC プロトコル。Unix ソケット (macOS) または名前付きパイプ (Windows) を介して、Logitech マルチホストデバイスをプログラムで切り替えます。
Logi Options+ エージェント IPC プロトコルのリバースエンジニアリング文書。macOS および Windows 上で、生の HID アクセスなしに Logitech マルチホストデバイス(ホスト切り替え、デバイスクエリ)のプログラム制御を可能にします。
このプロトコルは、本プロジェクト以前に公開文書化されていません。
macOS はカーネルレベルで Bluetooth 入力デバイスへの生の HID アクセスをブロックします。権限、エンタイトルメント、ハックのいずれでもこれを回避できません。Logi Options+ エージェントは、Bluetooth HID アクセスを許可する Apple 署名済みエンタイトルメント(com.apple.security.device.bluetooth)を持っています。このプロジェクトは代わりにエージェントの IPC チャネルを通じて通信します。
| File | Description |
|---|---|
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 デバイスの HID++ ChangeHost 機能インデックスを探索する (Windows) |
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
}
}
ペイロードは google.protobuf.Any フィールドで、@type アノテーション付きのインライン JSON としてシリアライズされます。エージェントは厳格な protobuf JSON パーサーを使用しており、未知のフィールドがあると INVALID_MESSAGE_RECEIVED が発生します。
リクエストは msg_id (スネークケース) を使用します。レスポンスは msgId (キャメルケース) を使用します。動詞は文字列: "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 を使用します。両方とも USAGE ページ FF43:0202 を使用します。以下で確認:
Get-PnpDeviceProperty -InstanceId "<instance_id>" -KeyName DEVPKEY_Device_HardwareIds
# Look for 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: response byte 4 = feature index
デバイスの再ペアリングにより 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 とは提携しておらず、Logitech によって承認されていません。
| シナリオ | 何が発生するか | 検出方法 |
|---|
| エージェントが実行されていない | ソケット/パイプが存在しない | connect() が FileNotFoundError または ConnectionRefusedError を発生させる |
| セッション中にエージェントが再起動される | 接続が切断される | send() が BrokenPipeError を発生させる;recv() が空を返す |
| デバイスが別のホストにある | NO_SUCH_PATH | result.code を確認 |
| デバイスに到達できない | 約 3 秒後に TIMEOUT | result.code を確認 |
| 不正なペイロード | INVALID_MESSAGE_RECEIVED | 欠落 @type または未知のフィールド |
| ソケットのハッシュが変更される | 古いパスがなくなる | 常に動的に発見し、ハードコードしない |
| 再起動後に古いソケットが残っている | ConnectionRefusedError | 短い遅延後に再試行 |
| 同時クライアント | 正常に動作 | エージェントは複数の接続を処理 |