動機
自宅でリモート ワークをしているとき、少し席を外したりするとスクリーン セーバーが起動してしまうため、その都度パスワードを入力してロック画面を解除しなければなりません。これが地味に面倒です。
自宅で作業しているときは、外部の人間に PC の内容を見られるといった可能性は極めて低いため、スクリーン セーバーを起動させないようにさせたいものですが、組織のセキュリティ ポリシー設定により、スクリーン セーバーを無効化することはできません。
そこで、Raspberry Pi Pico を利用して、スクリーン セーバーの起動を阻止できないかと考えました。
仕組み
Raspberry Pi Pico を HID デバイスとして動作させ (Windows からは HID マウスと認識されます)、Raspberry Pi Pico から一定間隔でマウス メッセージを送信し続けることにより、PC からはあたかもマウス操作が行われているように見せかけます。
これにより、PC がアイドル状態にならないため、結果としてスクリーン セーバーの起動を阻止することができます。
ファームウェアの作成
プログラムは、Raspberry Pi Pico サンプル コードの dev_hid_composite サンプルを改造して作成します。dev_hid_composite サンプルは様々な HID デバイスをデモするように作られていますが、今回はマウスとして機能させるため、その他のデバイス用のコードは削除していきます。
以下で、サンプルの改造個所を示していきます。
enum 値の追加
サンプル コードの main.c に相当するファイル (私のプログラムでは、PicoSleepBlocker.c) に以下の enum を追加します。これは、マウスのカーソル移動のメッセージを表しています。
// マウス メッセージ
enum
{
MOUSE_MOVE_RIGHT = 1,
MOUSE_MOVE_LEFT,
};
関数のプロトタイプ宣言の追加
自作関数のプロトタイプ宣言を追加します。関数の本体は次項で説明します。
uint GetMessage(void);
マウス メッセージを取得する関数の追加
送信すべきマウス メッセージを取得する関数を作成します。この関数は、hid_task から定期的に呼び出されます。
関数内で、呼び出された回数を count 変数でカウントしており、100 回の倍数の時だけ MOUSE_MOVE_RIGHT または MOUSE_MOVE_LEFT メッセージを返し、それ以外の時は 0 (無効値) を返します。hid_task は 10 ms 間隔で実行されるため、結果として 1000 ms ごとにマウス メッセージが 1 回送信されることになります。
また、1000 ms ごとに返されるマウス メッセージは、MOUSE_MOVE_RIGHT、MOUSE_MOVE_LEFT へ交互に切り替わります。これにより、マウス カーソルが左右に動く動作をエミュレートします。
uint GetMessage(void)
{
static uint msg = MOUSE_MOVE_RIGHT;
static uint count = 0;
// 1000 ms ごとに、交互にメッセージを切り替えます (10 ms × 100 回 = 1000 ms)。
if (++count >= 100)
{
count = 0;
switch (msg)
{
case MOUSE_MOVE_LEFT:
msg = MOUSE_MOVE_RIGHT;
break;
default:
msg = MOUSE_MOVE_LEFT;
break;
}
}
else
{
return 0;
}
return msg;
}
send_hid_report 関数の改造
サンプル コードでは様々なデバイスを扱っていますが、今回はマウスとして動作させるため、switch ステートメントの REPORT_ID_MOUSE 以外の case ブロックを削除します。
そして、REPORT_ID_MOUSE ブロックで、左右方向 (X 方向) にカーソルを移動させるメッセージが送られるように書き換えます。ここで、カーソル移動量は delta 定数に 2 と定義しています。これにより、マウス カーソルが左右に 2 ピクセル移動するようになります。また、マウス ボタンやホイール動作についてはすべて 0 を指定しています。
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
switch(report_id)
{
// マウス
case REPORT_ID_MOUSE:
{
int8_t const delta = 2; // カーソルの移動量
switch (btn)
{
case MOUSE_MOVE_RIGHT:
// ボタン クリックなし、右移動、スクロールなし、パンなし
tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, 0, 0, 0);
break;
case MOUSE_MOVE_LEFT:
// ボタン クリックなし、左移動、スクロールなし、パンなし
tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, -delta, 0, 0, 0);
break;
default:
break;
}
}
break;
default:
break;
}
}
hid_task の改造
サンプル コードで board_button_read 関数を呼び出している箇所を、自作した GetMessage 関数に置き換えます。さらに、send_hid_report 関数呼び出しの第一引数を REPORT_ID_MOUSE に書き換えます。
void hid_task(void)
{
// Poll every 10ms
const uint32_t interval_ms = 10;
static uint32_t start_ms = 0;
if ( board_millis() - start_ms < interval_ms) return; // not enough time
start_ms += interval_ms;
// 送信するマウス メッセージを取得します。
uint32_t const msg = GetMessage();
// Remote wakeup
if ( tud_suspended() && msg )
{
// Wake up host if we are in suspend mode
// and REMOTE_WAKEUP feature is enabled by host
tud_remote_wakeup();
}else
{
// Send the 1st of report chain, the rest will be sent by tud_hid_report_complete_cb()
send_hid_report(REPORT_ID_MOUSE, msg);
}
}
tud_hid_report_complete_cb 関数の改造
今回はマウス メッセージしか処理しないため、この関数の処理は空にします。
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len)
{
(void) instance;
(void) len;
(void) report;
// 何も処理しません。
}
usb_descriptors.c の書き換え
サンプル コードのままでもプログラムの動作には特に影響ないのですが、string_desc_arr の 1 番 (Manufacturer) と 2 番 (Product) を一応書き換えました。
char const *string_desc_arr[] =
{
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Kentaro", // 1: Manufacturer
"Pseudo Mouse Device", // 2: Product
NULL, // 3: Serials will use unique ID if possible
};
使用方法
ビルドしたファームウェアを書き込んだ Raspberry Pi Pico を、USB ケーブルで PC に接続するだけです。既存のマウスなどは接続したままで構いません。
マウスを操作していないときも、マウス カーソルが 1 秒間隔で左右に動くようになります (マウスと競合するせいか、カーソルが左右に動かない時もあるのですが、試した範囲ではこの場合でもスクリーン セーバーの起動を阻止できているようです)。
マウス カーソルの移動量は 2 ピクセルであるため、たいていの操作では邪魔になることはありません。ただし、図形の描画など、繊細な操作を要する場合は、PC から取り外したほうが良いかもしれません。
ソース コード
ソース コード一式は、GitHub にて公開しています。
感想
サンプル コードの簡単な改造だけで、オリジナル HID デバイスを作成できるのは、Raspberry Pi Pico の魅力だなと改めて感じました。