[DEV_CODE] Win32 HID APIを使用したWindows向けMagic Keyboardバッテリー監視ツールの実装
はじめに
Windows環境で Apple Magic Keyboardを使用しているのですが、地味に不便だったのがバッテリー残量がわからないこと。
そこで、ゲーム開発がいち段落し、Bluetooth環境のアップデートも行った事で、開発にチャレンジしてみました。
結果完成して、C#でコードを書きgit hub にリポジトリを作成して、公開まで一気に進めています。
build後のバイナリーファイルも公開済みです。
EXEの実行がもし怖い場合は、ソースコードを公開してますので、ご自身でコマンドラインからビルドを実行してください。
以下は、Windows環境において、Apple Magic Keyboard(テンキー付きモデル)のバッテリー残量をタスクトレイで常駐監視する軽量アプリケーションの実装内容をまとめています。
スクリーンショット
マルチ言語にも対応しました(v.1.2.0)
📦 リポジトリ・配布先
本プロジェクトのソースコードおよびコンパイル済みバイナリは以下で公開しています。
- GitHub: ixtan/MagicKeyBattery
GitHub - fixtan/MagicKeyBattery: A lightweight Windows system tray application to monitor the battery level of Apple Magic Keyboard.
A lightweight Windows system tray application to monitor the battery level of Apple Magic Keyboard. - fixtan/MagicKeyBattery
github.com- Download (ZIP): [Latest Release v1.0.0]
Release v1.0.0 - Initial Release · fixtan/MagicKeyBattery
This is a lightweight tool that displays the battery level of your Magic Keyboard in the Windows taskbar.
github.com🔍 技術的課題とアプローチ
1. キーボード排他制御の回避(アクセス権 0 開け)
Windowsはセキュリティおよびキー入力の整合性を保つため、標準キーボードデバイス(Col01 系統)をシステム側でガッチリ占有(排他制御)しています。そのため、通常の外部アプリケーションが GENERIC_READ | GENERIC_WRITE でデバイスハンドルを開こうとすると、Win32Error: 5 (Access Denied) で拒絶されます。
【対策】
Win32 APIの CreateFile を呼び出す際、第2引数のアクセス権(dwDesiredAccess)にあえて 0(アクセス権なし) を指定してハンドルを取得します。HIDデバイスの仕様上、アクセス権が0であっても、HidD_GetInputReport 等によるコントロールパケットの送受信は許可されるという定石を利用し、システムの排他制御をすり抜けて通信経路を確保しました。
2. 通信窓口(Collection)と Report ID の特定
Apple製デバイスは、タイピング用のメインチャンネルとは別に、特殊な制御信号をやり取りするための拡張チャンネル(Col02)を持っています。また、モデルやファームウェアによってバッテリー情報を返却するマジックナンバー(Report ID)が異なります。
【対策】
0x01 から 0xFF(255)までのすべてのReport IDに対し、バッファサイズを変えながら HidD_GetInputReport と HidD_GetFeature を網羅的に叩く総当たりプロービングを実施しました。その結果、検証に使用したテンキー付きモデル(PID: 026c)においては以下の仕様でデータが取得できることを突き止めました。
- 窓口:
Col02インターフェース - API:
HidD_GetInputReport - Report ID:
0x90 - 格納先: 返却バッファの 3バイト目(インデックス 2) に16進数で残量データ(0〜100%)が格納
🛠️ 主な実装機能
1. ゼロアセットによる動的アイコン生成
アプリケーションの軽量化とポータビリティを担保するため、外部の .ico ファイルを一切同梱していません。C#の Graphics クラスを使用し、メモリ上の16x16ピクセルのキャンバス(Bitmap)に直接バッテリーの外枠と残量インジケーターを1ピクセル単位で描画しています。
描画のたびに増えるHiconハンドルによるGDIリソースリークを防ぐため、user32.dll の DestroyIcon をP/Invokeし、古いハンドルを厳密に破棄する設計としています。
2. 監視負荷の最適化(3分ポーリング)
OSスタックがキャッシュしているHIDレポートを取得するため、高頻度なスキャンは不要です。常駐アプリとしてのリソース消費を極小化するため、スキャン間隔を 3分(180,000ms) に設定。CPU・Bluetooth帯域・キーボード側のバッテリーのいずれに対しても負荷をほぼゼロに抑えています。
3. レジストリ連携によるスタートアップ登録
コンテキストメニュー内の「Windows起動時に実行」のチェック状態と連動し、Windowsのレジストリ(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run)へ、実行中プロセスの絶対パスを自動的に書き込み/削除する仕組みを組み込んでいます。