はじめに
本記事は、前回の記事 をベースに、Raspberry Pi Zero 2 W を USB Gadget (g_hid) として仮想キーボード化し、
- ネットワーク経由 (gRPC + Protobuf) で受信した文字列 をターゲットPCへ“キーボード入力”として送信
- Bluetooth キーボードからのキー入力(Modifierキーや複数キー同時押下を含む)もターゲットPCへ送信
を同時に実現します。
さらに、Print Screen・Scroll Lock・Pause/Break などの特殊キーにも対応し、JIS配列向けとしてほぼ完成形の実装例を紹介します。
参考として、最後にUS配列向けのコード例も追加しています。
また、「Pi 側ではBluetooth キーボードからのキー入力を受け取りたくない(USB HIDへの入力のプロセス専用にする)」 場合の dev.grab()
の使い方やプロセス終了時の動作なども解説します。
注: 本記事のサンプルスクリプトでは、Python の evdev ライブラリを使用します。
Raspberry Pi OS には標準でインストールされていない場合が多いため、事前にsudo apt update && sudo apt install python3-evdev
あるいは
pip3 install evdev
などで導入してください。
また、実際に発生するキーコード (ecodes.KEY_???) はキーボードやOSにより微妙に異なる場合があるため、evtest
などで実機確認し、必要に応じてマップを調整してください。
1. 前回の設定が済んでいれば
前回の記事で、
- USB Gadget (g_hid) 設定: Pi Zero 2 W がターゲットPCから見て「USBキーボード」と認識される状態
-
gRPC サーバ: ネットワークで受け取った文字列 →
/dev/hidg0
に書き込む仕組み
が既に整っている場合、本記事の Bluetooth キーボード入力スクリプト を追加するだけで、以下の2つの入力ソースが同時に /dev/hidg0
へ書き込めるようになります。
- ネットワーク入力 (gRPC)
- Bluetooth キーボード入力 (evdev)
ターゲットPC側から見ると、単一のUSBキーボードが同時に2種類の入力を送信してくるイメージです。
なお、本記事は前提として「BluetoothキーボードがPiに接続済み」である必要があります。
2. システム全体の概要
2.1 構成
以下の図は、システム全体を示しています。
- A: ネットワーク越しの文字列を受け取り、1文字ずつキーコードに変換
- B: Bluetooth キーボードの押下情報を evdev で取得
- D: 各入力ソースを取りまとめ、ターゲットPCへ送るHIDレポートを生成
-
E:
/dev/hidg0
に対して HID レポートを書き込み - F: ターゲットPCは「USBキーボード」として認識し、キー入力が反映される
2.2 メリット
- ネットワーク入力 と 物理キーボード入力 をまとめて扱える
- USB OTG 機能のおかげで、ターゲットPC側に専用ドライバは不要(標準的なUSBキーボードとして動作)
- Bluetooth キーボードを Pi 側でも使えるし、必要に応じて Pi 側で受け取らず HID 専用にすることもできる
3. システム構築手順の要点
ステップ | 内容 | 補足 |
---|---|---|
1. USB Gadget 設定 (前回記事) |
/usr/local/bin/setup_hid_keyboard.sh などで Pi を「USBキーボード」に設定 |
ターゲットPCが Pi を USBキーボードとして認識する |
2. gRPC サーバ (前回記事) | ネットワークで文字列受信 → 1文字ずつ /dev/hidg0 に書き込む |
リモート入力をキーボード入力にエミュレート |
3. Bluetooth 入力スクリプト | evdev でキーイベントを取得 → HID レポート生成 → /dev/hidg0 に送る |
Modifierキーや複数同時押し、特殊キーにも対応 (本記事) |
4. systemd サービス | スクリプトを自動起動 | Pi のブート時から常時キーボードを監視 |
5. (任意) dev.grab() | Pi 側の入力を「奪って」HID専用にする | grab を呼ばなければ Pi OS でも通常のキーボード入力として使え、呼ぶ場合は Pi OS への入力が遮断される |
6. 競合対策 (必要に応じて) | gRPC と Bluetooth が同時書き込みする場合、Lock やキューを検討 | 複数プロセスによる同時アクセスが想定される場合は、必ず排他ロック(例: Python の threading.Lock)を導入してください。 |
4. dev.grab() を使って Pi 側への入力を遮断する方法
4.1 デフォルト状態(grab なし)
通常は、Bluetooth キーボードを接続すると、Pi OS としても使え、同時に evdev スクリプトでも読み取れます。
この状態だと、Pi のコンソールやGUI操作にも影響し、キーボード入力として反映されます。
4.2 dev.grab() の動作
evdev の dev.grab()
を呼び出すと、
- そのプロセスが動作中の間、入力デバイスが排他ロック状態になり、Pi OS の標準入力には送られず、USB HID へのみレポートが出力されます。
dev = evdev.InputDevice("/dev/input/event3")
dev.grab() # これを呼ぶと、Pi OS ではキー入力として認識されなくなります
注意: プロセスが終了するとファイルディスクリプタが閉じられ、
自動的に ungrab (アングラブ) されます。
そのため プロセス終了後は Pi OS が再び入力を受け取れる状態に戻ります。
4.2.1 SSH への影響はない
SSH はあくまでネットワーク経由なので、grab しても SSH の動作には影響しません。
Pi を操作する際は SSH を利用し、物理キーボードはUSB HID出力専用にする、といった使い分けが可能です。
5. Modifier+複数キー+特殊キー対応の実装例
以下は、Modifierキーや最大6キー同時押し、Print Screen/Scroll Lock/Pause なども含めた Python スクリプトの例です。
grab() を呼ぶかどうかは、コメントアウトの有無で制御できます。
補足:
- キーコード (ecodes.KEY_???) は実機で
evtest
や/proc/bus/input/devices
を使用して確認してください。- 環境によっては
ecodes.KEY_CARET
やecodes.KEY_AT
の定義が異なる場合があるため、必要に応じてマップを調整してください。- また、
time.sleep(0.005)
の値は環境によって 0.01~0.02 秒程度に増やすと取りこぼしが減る場合があります。必要に応じて調整してください。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JIS配列の物理キーボード入力 (evdev) を取得し、
/dev/hidg0 に対してHIDレポートを書き込むサンプル。
- Raspberry Pi Zero 2 W (g_hid でUSBキーボード化) などで利用。
- JIS配列を意識したKEYCODE_MAPを定義(ホストOSがJISキーボードとして認識している前提)。
- Modifierキー(Shift,Ctrl,Alt,Meta) や最大6キー同時押しに対応。
- Print Screen / Scroll Lock / Pause 等の特殊キーにも対応。
- CapsLockやNumLock、矢印キー、には未対応。
- 半角/全角キー、変換、無変換、カナなど日本語特有のキーも一部定義例あり(※実機要確認)。
"""
import time
import struct
import evdev
from evdev import ecodes
#=========================================================================
# 1. Modifierキー として扱うキーコード → ビットマスク
#=========================================================================
MODIFIER_MAP = {
ecodes.KEY_LEFTCTRL: 0x01,
ecodes.KEY_LEFTSHIFT: 0x02,
ecodes.KEY_LEFTALT: 0x04,
ecodes.KEY_LEFTMETA: 0x08, # Windowsキー or Commandキー
ecodes.KEY_RIGHTCTRL: 0x10,
ecodes.KEY_RIGHTSHIFT: 0x20,
ecodes.KEY_RIGHTALT: 0x40, # AltGr
ecodes.KEY_RIGHTMETA: 0x80,
}
#=========================================================================
# 2. 通常キー (JIS配列を想定した例)
#=========================================================================
KEYCODE_MAP = {
#----------------------------------------------------
# アルファベット A-Z
#----------------------------------------------------
ecodes.KEY_A: 0x04, ecodes.KEY_B: 0x05, ecodes.KEY_C: 0x06,
ecodes.KEY_D: 0x07, ecodes.KEY_E: 0x08, ecodes.KEY_F: 0x09,
ecodes.KEY_G: 0x0A, ecodes.KEY_H: 0x0B, ecodes.KEY_I: 0x0C,
ecodes.KEY_J: 0x0D, ecodes.KEY_K: 0x0E, ecodes.KEY_L: 0x0F,
ecodes.KEY_M: 0x10, ecodes.KEY_N: 0x11, ecodes.KEY_O: 0x12,
ecodes.KEY_P: 0x13, ecodes.KEY_Q: 0x14, ecodes.KEY_R: 0x15,
ecodes.KEY_S: 0x16, ecodes.KEY_T: 0x17, ecodes.KEY_U: 0x18,
ecodes.KEY_V: 0x19, ecodes.KEY_W: 0x1A, ecodes.KEY_X: 0x1B,
ecodes.KEY_Y: 0x1C, ecodes.KEY_Z: 0x1D,
#----------------------------------------------------
# 数字キー (上段)
#----------------------------------------------------
ecodes.KEY_1: 0x1E, ecodes.KEY_2: 0x1F, ecodes.KEY_3: 0x20,
ecodes.KEY_4: 0x21, ecodes.KEY_5: 0x22, ecodes.KEY_6: 0x23,
ecodes.KEY_7: 0x24, ecodes.KEY_8: 0x25, ecodes.KEY_9: 0x26,
ecodes.KEY_0: 0x27,
#----------------------------------------------------
# 改行, バックスペース, Esc, Tab, Space
#----------------------------------------------------
ecodes.KEY_ENTER: 0x28,
ecodes.KEY_ESC: 0x29,
ecodes.KEY_BACKSPACE: 0x2A,
ecodes.KEY_TAB: 0x2B,
ecodes.KEY_SPACE: 0x2C,
#----------------------------------------------------
# 記号キー (JIS配列版)
#----------------------------------------------------
ecodes.KEY_MINUS: 0x2D, # '-' (SHIFT時に '=')
ecodes.KEY_CARET: 0x2E, # '^' (SHIFT時に '~')
ecodes.KEY_AT: 0x2F, # '@' (SHIFT時に '`')
ecodes.KEY_LEFTBRACE: 0x30, # '[' (SHIFT時に '{')
ecodes.KEY_YEN: 0x31, # '¥' (SHIFT時に '|')
ecodes.KEY_SEMICOLON: 0x33, # ';' (SHIFT時に ':')
ecodes.KEY_COLON: 0x34, # 環境により未定義の場合も
ecodes.KEY_GRAVE: 0x35, # '`' など
ecodes.KEY_COMMA: 0x36, # ',' (SHIFTで '<')
ecodes.KEY_DOT: 0x37, # '.' (SHIFTで '>')
ecodes.KEY_SLASH: 0x38, # '/' (SHIFTで '?')
#----------------------------------------------------
# ファンクションキー
#----------------------------------------------------
ecodes.KEY_F1: 0x3A, ecodes.KEY_F2: 0x3B, ecodes.KEY_F3: 0x3C,
ecodes.KEY_F4: 0x3D, ecodes.KEY_F5: 0x3E, ecodes.KEY_F6: 0x3F,
ecodes.KEY_F7: 0x40, ecodes.KEY_F8: 0x41, ecodes.KEY_F9: 0x42,
ecodes.KEY_F10: 0x43, ecodes.KEY_F11: 0x44, ecodes.KEY_F12: 0x45,
#----------------------------------------------------
# Print Screen / Scroll Lock / Pause
#----------------------------------------------------
ecodes.KEY_SYSRQ: 0x46, # PrintScreen
ecodes.KEY_SCROLLLOCK: 0x47,
ecodes.KEY_PAUSE: 0x48,
#----------------------------------------------------
# 日本語特有キー (変換, 無変換, 全角/半角, カナ 等)
#----------------------------------------------------
ecodes.KEY_HENKAN: 0x8A,
ecodes.KEY_MUHENKAN: 0x8B,
ecodes.KEY_ZENKAKUHANKAKU: 0x91,
ecodes.KEY_KATAKANA: 0x92,
}
#==============================================================================
# build_hid_report(modifier_byte, pressed_keys):
# - modifier_byte: 1バイト (各ModifierキーのビットをORした値)
# - pressed_keys: 同時押しキーのUsage ID (最大6個まで)
# → 8バイトのHIDレポート (固定フォーマット) をbytesで返す
#==============================================================================
def build_hid_report(modifier_byte, pressed_keys):
report = [0]*8
report[0] = modifier_byte
report[1] = 0x00
idx = 2
for usage_id in list(pressed_keys)[:6]:
report[idx] = usage_id
idx += 1
return struct.pack('8B', *report)
def main():
# ※注意: 接続されている入力デバイスのパスは環境により異なります。
# ls /dev/input/event* や evtest でご使用のデバイスを確認してください。
device_path = "/dev/input/event3"
dev = evdev.InputDevice(device_path)
print(f"Using input device: {device_path} ({dev.name})")
# HID Gadget (ターゲットPCへ送る先)
hidg_path = "/dev/hidg0"
print(f"Writing HID reports to: {hidg_path}")
# dev.grab() を呼ぶと、Pi 側への入力を遮断しUSB HID専用にできます。
# dev.grab()
pressed_keys = set()
modifier_state = 0x00
with open(hidg_path, "wb") as hid:
for event in dev.read_loop():
if event.type == ecodes.EV_KEY:
# event.value: 0=key up, 1=key down, 2=key repeat
if event.value not in (0,1):
continue
is_pressed = (event.value == 1)
if event.code in MODIFIER_MAP:
bitmask = MODIFIER_MAP[event.code]
if is_pressed:
modifier_state |= bitmask
else:
modifier_state &= ~bitmask
elif event.code in KEYCODE_MAP:
usage_id = KEYCODE_MAP[event.code]
if is_pressed:
pressed_keys.add(usage_id)
else:
pressed_keys.discard(usage_id)
report = build_hid_report(modifier_state, pressed_keys)
hid.write(report)
# 環境によっては 0.01~0.02 秒程度に増やすと安定する場合があります
time.sleep(0.005)
if __name__ == "__main__":
main()
6. systemd サービスで自動起動
このスクリプトを /usr/local/bin/bluetooth_input.py
として保存し、
sudo chmod +x /usr/local/bin/bluetooth_input.py
を付与後、以下のような systemd サービスを作成してください。
sudo vi /etc/systemd/system/bluetooth-input.service
[Unit]
Description=Bluetooth Keyboard Input Service
After=hidgadget.service
Requires=hidgadget.service
[Service]
Type=simple
ExecStart=/usr/local/bin/bluetooth_input.py
Restart=always
[Install]
WantedBy=multi-user.target
次に、以下のコマンドでサービスを有効化・起動します。
sudo systemctl daemon-reload
sudo systemctl enable bluetooth-input.service
sudo systemctl start bluetooth-input.service
再起動(sudo reboot
)後、Pi は自動でスクリプトを起動し、Bluetooth キーボード入力が /dev/hidg0
に書き込まれます。
7. gRPC サーバとの統合
前回記事で構築した gRPC サーバ(ネットワーク → /dev/hidg0
)を同時に動かすと、以下2つが並行して /dev/hidg0
へ書き込みます。
- Bluetooth 入力スクリプト
- ネットワーク入力 (gRPC)
ターゲットPCに対しては単一のUSBキーボード入力として届くため、リモート文字列入力と物理キーボード入力が混在して送信されます。
7.1 競合対策
同時アクセスによりHIDレポートが上書きされ、キー入力が混ざる恐れがあります。必要に応じて以下のいずれかを導入する必要があります。(なお、今回は趣味レベルなので、gRPC クライアントから送るときは、必ず手を離してキーボードを押さない、という運用で対策しています。)
- 別プロセスの場合:
multiprocessing.Lock
やファイルロック (fcntl) などを利用し、両方のプロセスが共有できる手段でロックを実装する。 - 同一プロセスにした場合:
threading.Lock
などで/dev/hidg0
への書き込み区間を保護する。 - 1プロセス統合: gRPC サーバと Bluetooth 入力を同一プロセス内で管理し、送信を直列化する
- イベントキュー: 入力イベントをキューに貯め、順番に処理する
注意: 複数プロセスが並行して
/dev/hidg0
に書き込む場合は、厳密なロックやキュー管理を行わないと誤動作の原因となります。
8. CPU 負荷は少ない
for event in dev.read_loop():
はブロッキング読み込みのため、キーイベントがないときは待機状態となり、CPU 使用率はほぼ0% です。
高頻度のキー操作があっても、time.sleep(0.005)
により書き込みが制御され、CPU 負荷が急激に上がることはありません。
9. プロセス終了時に grab はどうなる?
dev.grab()
は該当プロセスが開いている間のみ有効です。プロセス終了時にはファイルディスクリプタが閉じられ、
- 自動的に ungrab (アングラブ) され、Pi OS が再び通常のキーボード入力として利用できるようになります。
SSH はネットワーク経由のため、この変更による影響はありません。
10. 「Pi 側での入力有無」を比較する表
以下の表は、本スクリプトにおいて dev.grab()
を呼ぶ/呼ばないときの違いをまとめたものです。
項目 | grab() 呼ばない | grab() 呼ぶ |
---|---|---|
Pi 側でのキー操作 | できる(通常のキーボードとして認識) | できない(OSが入力を受け取らない) |
USB HID 出力 | 可能(/dev/hidg0 への書き込みは常に行われる) | 可能(/dev/hidg0 への書き込みは常に行われる) |
Pi 側への影響 | Pi のコンソールや GUI でもキー操作が可能 | Pi は全くキーを受け取らない |
プロセス終了後 | 変わらず Pi 側は通常利用できる | ungrab され、Pi OS が再びキー入力を受け取る (プロセス動作中のみ grab 有効) |
SSH への影響 | 特になし(SSH はネットワーク経由) | 特になし(SSH はネットワーク経由) |
用途 | Pi 自身も操作したい、かつ USB HID に入力を送りたい場合 | Pi を完全に操作対象外とし、USB HID 専用キーボードにしたい場合 |
11. 今後の拡張・注意点
-
日本語配列 (JIS) と実機確認
- CapsLockやNumLock、矢印キー、に現状では未対応です
- 本記事はJIS配列版として記述していますが、実際のキーボードやOSのレイアウト設定により、円マークや変換キー、無変換キー、カナキーなどのUsage IDが異なる場合があります。
evtest
等でキーコードを確認し、必要に応じて調整してください。
-
追加の特殊キー・メディアキー
- 音量、ミュートなどが必要な場合は、
KEYCODE_MAP
に対応するキーコードを追加してください。
- 音量、ミュートなどが必要な場合は、
-
Nキーロールオーバー (NKRO)
- 8バイトのブートプロトコルは最大6キー同時押しまでの仕様です。Nキー全面対応にはHIDレポート形式の拡張が必要です。
-
エラーハンドリング
- デバイス切断時の再接続など、長期間の運用時は例外処理を追加することを推奨します。
-
US配列版との違い
- JIS配列版とUS配列版では、特に記号キーのUsage IDが異なります。使用環境に合わせた調整が必要です(後述のUS配列版の例を参照)。
12. まとめ
-
前回の記事の設定(USB Gadget + gRPCサーバ)が完了していれば、本記事の Bluetooth 入力スクリプトを追加するだけで動作
- ネットワーク入力 (gRPC) と Bluetooth 入力 (evdev) が同時に
/dev/hidg0
へ書き込み可能
- ネットワーク入力 (gRPC) と Bluetooth 入力 (evdev) が同時に
-
Pi 側で入力を使うかどうかは
dev.grab()
の呼び出し次第- grab() を呼ばなければ、Pi OS 側でも通常のキーボード入力として使えます(併用可能)
- grab() を呼ぶと、Pi OS への入力が遮断され、USB HID 出力専用になります(SSH は影響なし)
- プロセス終了時に自動的に ungrab され、再び通常の入力が可能になります
-
CPU 負荷は低い
-
read_loop()
はキー入力がない間はブロッキング待機となり、time.sleep(0.005)
により負荷が抑えられます - 環境により
0.01~0.02秒
に増やすと文字の取りこぼしが減ることがあります
-
-
競合時の排他制御
- gRPC と Bluetooth 入力を同時運用する場合は、Lock や1プロセス統合などで衝突を防ぐ(趣味の範囲を超える場合は必須)
-
JIS配列版のベースとしては、そこそこ完成しているが未対応のキーがある
- Modifier+複数キー+特殊キー (PrintScreen/ScrollLock/Pause) に対応済み
- CapsLockやNumLock、矢印キー、に未対応です
- 日本語配列やメディアキー、NKRO対応などは用途に合わせて追加可能です
これにより、Raspberry Pi Zero 2 W は“USB キーボード”として多彩な入力ソースを統合でき、Pi 側での使用可否(grab)も柔軟に切り替えられます。
13. 参考リンク
- Linux USB Gadget - ConfigFS Documentation (kernel.org)
- evdev (Python) ドキュメント
- USB HID Usage Tables (USB.org)
参考 US配列の場合のPythonスクリプト例
#!/usr/bin/env python3
import evdev
import struct
import time
from evdev import ecodes
"""
Modifier(Shift,Ctrl,Alt,Meta) + 複数キー同時押し(最大6キー)対応のサンプル。
/dev/hidg0 へ8バイトのHIDレポートを送信し、ターゲットPCがキーボード入力として認識。
こちらはUS配列のキーコードマッピング例です。
Print Screen, Scroll Lock, Pause/Break にも対応。
"""
# 1) Modifier キーの evdev code → HID modifier ビット対応表
MODIFIER_MAP = {
ecodes.KEY_LEFTCTRL: 0x01, # 左Ctrl
ecodes.KEY_LEFTSHIFT: 0x02, # 左Shift
ecodes.KEY_LEFTALT: 0x04, # 左Alt
ecodes.KEY_LEFTMETA: 0x08, # 左Meta(Win)
ecodes.KEY_RIGHTCTRL: 0x10, # 右Ctrl
ecodes.KEY_RIGHTSHIFT: 0x20, # 右Shift
ecodes.KEY_RIGHTALT: 0x40, # 右Alt(AltGr)
ecodes.KEY_RIGHTMETA: 0x80, # 右Meta
}
# 2) 通常キーの evdev code → HID Usage ID 対応表(US配列)
KEYCODE_MAP = {
# 文字キー (アルファベット)
ecodes.KEY_A: 0x04, ecodes.KEY_B: 0x05, ecodes.KEY_C: 0x06,
ecodes.KEY_D: 0x07, ecodes.KEY_E: 0x08, ecodes.KEY_F: 0x09,
ecodes.KEY_G: 0x0A, ecodes.KEY_H: 0x0B, ecodes.KEY_I: 0x0C,
ecodes.KEY_J: 0x0D, ecodes.KEY_K: 0x0E, ecodes.KEY_L: 0x0F,
ecodes.KEY_M: 0x10, ecodes.KEY_N: 0x11, ecodes.KEY_O: 0x12,
ecodes.KEY_P: 0x13, ecodes.KEY_Q: 0x14, ecodes.KEY_R: 0x15,
ecodes.KEY_S: 0x16, ecodes.KEY_T: 0x17, ecodes.KEY_U: 0x18,
ecodes.KEY_V: 0x19, ecodes.KEY_W: 0x1A, ecodes.KEY_X: 0x1B,
ecodes.KEY_Y: 0x1C, ecodes.KEY_Z: 0x1D,
# 数字キー (上段)
ecodes.KEY_1: 0x1E, ecodes.KEY_2: 0x1F, ecodes.KEY_3: 0x20,
ecodes.KEY_4: 0x21, ecodes.KEY_5: 0x22, ecodes.KEY_6: 0x23,
ecodes.KEY_7: 0x24, ecodes.KEY_8: 0x25, ecodes.KEY_9: 0x26,
ecodes.KEY_0: 0x27,
# 記号・特殊キー
ecodes.KEY_ENTER: 0x28,
ecodes.KEY_ESC: 0x29,
ecodes.KEY_BACKSPACE: 0x2A,
ecodes.KEY_TAB: 0x2B,
ecodes.KEY_SPACE: 0x2C,
ecodes.KEY_MINUS: 0x2D, # '-' or '_'
ecodes.KEY_EQUAL: 0x2E, # '=' or '+'
ecodes.KEY_LEFTBRACE: 0x2F, # '['
ecodes.KEY_RIGHTBRACE:0x30, # ']'
ecodes.KEY_BACKSLASH: 0x31, # '\'
ecodes.KEY_SEMICOLON: 0x33, # ';'
ecodes.KEY_APOSTROPHE:0x34, # '\''
ecodes.KEY_GRAVE: 0x35, # '`'
ecodes.KEY_COMMA: 0x36, # ','
ecodes.KEY_DOT: 0x37, # '.'
ecodes.KEY_SLASH: 0x38, # '/'
# ファンクションキー
ecodes.KEY_F1: 0x3A, ecodes.KEY_F2: 0x3B, ecodes.KEY_F3: 0x3C,
ecodes.KEY_F4: 0x3D, ecodes.KEY_F5: 0x3E, ecodes.KEY_F6: 0x3F,
ecodes.KEY_F7: 0x40, ecodes.KEY_F8: 0x41, ecodes.KEY_F9: 0x42,
ecodes.KEY_F10: 0x43, ecodes.KEY_F11: 0x44, ecodes.KEY_F12: 0x45,
# PrintScreen, ScrollLock, Pause
ecodes.KEY_SYSRQ: 0x46, # PrintScreen
ecodes.KEY_SCROLLLOCK: 0x47,
ecodes.KEY_PAUSE: 0x48,
}
def build_hid_report(modifier_byte, pressed_keys):
report = [0]*8
report[0] = modifier_byte
report[1] = 0x00
idx = 2
for usage_id in list(pressed_keys)[:6]:
report[idx] = usage_id
idx += 1
return struct.pack('8B', *report)
def main():
device_path = "/dev/input/event3"
dev = evdev.InputDevice(device_path)
# dev.grab() # Pi側への入力を遮断したい場合は有効化
print(f"Bluetooth keyboard device: {device_path}")
pressed_keys = set()
modifier_state = 0x00
with open("/dev/hidg0", "wb") as hid:
for event in dev.read_loop():
if event.type == ecodes.EV_KEY:
if event.value not in (0,1):
continue
pressed = (event.value == 1)
if event.code in MODIFIER_MAP:
bitmask = MODIFIER_MAP[event.code]
if pressed:
modifier_state |= bitmask
else:
modifier_state &= ~bitmask
elif event.code in KEYCODE_MAP:
usage_id = KEYCODE_MAP[event.code]
if pressed:
pressed_keys.add(usage_id)
else:
pressed_keys.discard(usage_id)
report = build_hid_report(modifier_state, pressed_keys)
hid.write(report)
# 同様に 0.005秒を 0.01~0.02 に調整可能
time.sleep(0.005)
if __name__ == "__main__":
main()
以上