キーボード #1 Advent Calendar 2021 23日目の記事です。
アドベントカレンダーの参加者(特に今週)を見て、自分の誕生日だからって今日にしなくても良かったんじゃね…と、壁サークルに挟まれた緩衝材状態になっている堕落猫です。
普段は60%汎用ケースで様々な配列のキーボードや格安日本語配列キーボードなどを作っています。(作品群はこちら)
今回は半年でキーボードを13台作って販売している話でもなく、山田75号開発記でもなく、自作キーボードをより便利に使えるようにする補助アプリの話です。
GPK RCというQMK/VIAキーボードを外部から命令を出して実行させるアプリケーションを作りました。
動画を見てもらえればどんな感じか分かると思います。
GPK RCの設定はこんな感じです。
設定したいアプリを触るとActiveWindowにアプリ名が表示されるので、それをコピペして設定します。
実際に動作にするとこんな感じです。
メモアプリではデフォルトレイヤーであるレイヤー0のキーマップで設定されている6が入力され、VSCodeに切り替えるとレイヤー2に自動的に切り替わり、3が入力されます。
再びメモアプリに行くとレイヤー0に戻り6が入力されます。
どこか忘れたのですがマクロパッドをアプリケーションごとに複数、物理的に使い分けてるという話を見て、アプリケーションにアクセスしたら自動でキーマップが切り替われば、お気に入りの1個で済むし、机の上がスッキリするのになぁと思い、同じようなことを考えてる人いるだろうし、お便利アプリとかあるんじゃね?と、ググってみたところ、特になし…。
スクリプトやconfigを自分で書いたりすれば動くものはあるけど、GUIでみんなが簡単に使えるものが全くなかった。
一から作るの面倒くさいなぁと思っていたら、QMK RCなるライブラリー作っている方がいたので、それを使わせて頂くことに。
また、それとは別に同じ頃GRIN配列を採用したGPK60-47GR1REを設計していて、色々機能を盛り盛りにする一環でOLEDを実装することになって、その使い道を考えいて、LEDキレイだなぁとか記号キーどこだよ!とかキーボード見るついでに時間が見れたらいいかな?と思ったけど、Pro Microは時間が取れない。
RTC入れるとコストが上がるし、既存のキーボードには適応出来ないなぁと思っていたら、QMK RCはOLEDの書き込みにも対応していたので、文字の種類や文字数制限がありますが、PCで扱える文字列ならなんでも表示できます。
それならPCサイドで時間作って送り込めば時間表示出来るよねってことになり、自動レイヤー切替とOLEDに時間を書き込むアプリケーションを作ることに相成りました。
これがGPK RCの出発点です。
言わずと知れた自作キーボードのデファクトスタンダードファームウェア。
QMKを外部から実行するコマンドを纏めたライブラリ。 コマンド一覧
Nodeを用いてマルチOSアプリケーションを作るためのフレームワーク。
実際にキーボードに接続してコマンド発行するNode用ライブラリ。
QMK RCを使うためにはRAW HIDを使うのですが、同時にVIA/Remapも使用したい。
ちゃんとやると色々面倒なので、簡単にquantumにあるvia,h、via.cを拡張してスーパーセットにします。
拡張して使うと言っても、とっても簡単。
まずvia.hにQMK RCのコマンドを追加していきます。
enum via_command_id {
id_get_protocol_version = 0x01,
id_get_keyboard_value = 0x02,
id_set_keyboard_value = 0x03,
id_dynamic_keymap_get_keycode = 0x04,
id_dynamic_keymap_set_keycode = 0x05,
id_dynamic_keymap_reset = 0x06,
id_lighting_set_value = 0x07,
id_lighting_get_value = 0x08,
id_lighting_save = 0x09,
id_eeprom_reset = 0x0A,
id_bootloader_jump = 0x0B,
id_dynamic_keymap_macro_get_count = 0x0C,
id_dynamic_keymap_macro_get_buffer_size = 0x0D,
id_dynamic_keymap_macro_get_buffer = 0x0E,
id_dynamic_keymap_macro_set_buffer = 0x0F,
id_dynamic_keymap_macro_reset = 0x10,
id_dynamic_keymap_get_layer_count = 0x11,
id_dynamic_keymap_get_buffer = 0x12,
id_dynamic_keymap_set_buffer = 0x13,
id_qmk_rc_olde_off = 0x15,
id_qmk_rc_olde_on = 0x16,
id_qmk_rc_olde_write = 0x17,
id_qmk_rc_olde_clear = 0x18,
id_qmk_rc_rgblight_off = 0x19,
id_qmk_rc_rgblight_on = 0x1a,
id_qmk_rc_rgblight_setrgb_range = 0x1b,
id_qmk_rc_rgb_matrix_off = 0x1c,
id_qmk_rc_rgb_matrix_on = 0x1d,
id_qmk_rc_rgb_matrix_setrgb_range = 0x1e,
id_qmk_rc_layer_on = 0x1f,
id_qmk_rc_layer_off = 0x20,
id_qmk_rc_layer_clear = 0x21,
id_qmk_rc_layer_move = 0x22,
id_qmk_rc_sned_string = 0x23,
id_qmk_is_olde_on = 0x24,
id_unhandled = 0xFF,
};
0x01〜0x13はVIAが使っているので、それ以降をQMK RCに振ってあげます。id名などは適当です。
次にvia.c
#include "qmk_rc.h"
#define QMK_RC_BUFFER_MAX 64
uint8_t qmk_rc_buffer[QMK_RC_BUFFER_MAX] = {};
void raw_hid_receive(uint8_t *data, uint8_t length) {
uint8_t *command_id = &(data[0]);
uint8_t *command_data = &(data[1]);
switch (*command_id) {
case id_get_protocol_version: {
command_data[0] = VIA_PROTOCOL_VERSION >> 8;
command_data[1] = VIA_PROTOCOL_VERSION & 0xFF;
break;
}
.....
case id_qmk_rc_olde_off:
case id_qmk_rc_olde_on:
case id_qmk_rc_olde_write:
case id_qmk_rc_olde_clear:
case id_qmk_rc_rgblight_off:
case id_qmk_rc_rgblight_on:
case id_qmk_rc_rgblight_setrgb_range:
case id_qmk_rc_rgb_matrix_off:
case id_qmk_rc_rgb_matrix_on:
case id_qmk_rc_rgb_matrix_setrgb_range:
case id_qmk_rc_layer_on:
case id_qmk_rc_layer_off:
case id_qmk_rc_layer_clear:
case id_qmk_rc_layer_move:
case id_qmk_rc_sned_string: {
qmk_rc_receive(qmk_rc_buffer, QMK_RC_BUFFER_MAX, data, length);
}
#if defined(OLED_ENABLE)
case id_qmk_is_olde_on: {
char* oled_on = (is_oled_on() ? "is_oled_on " : "is_oled_off ");
raw_hid_send((uint8_t*)oled_on, 32);
}
#endif
default: {
// The command ID is not known
// Return the unhandled state
*command_id = id_unhandled;
break;
}
}
// Return the same buffer, optionally with values changed
// (i.e. returning state to the host, or the unhandled state).
raw_hid_send(data, length);
}
qmk_rc.h
をincludeして、raw_hid_receive
にQMK RCのコマンドを追加してデータをqmk_rc_receive
へ渡すだけ。
このカスタム(zip)をしたvia.h,via.cとqmk_rc.h
をquantumへコピペして、後はkeyboards以下、使いたいキーボードのフォルダにqmk_rc.c
を入れて、rules.mkにSRC += qmk_rc.c
を追加するだけです。とっても簡単ですね。
QMKでVIA対応しているキーボードなら、ほとんどのものが対応できます。
キーボード側での対応が終わったので、次はPC側からコマンドを送るアプリケーションを作ります。
ここからは地味に実装していくだけで特に面白いことがないので、ダイジェストでやっていることを説明します。
詳しく知りたい方はこちらのindex.js、preload.js、qmkrcd.jsなどを読むと何をやっているか、だいたい分かると思います。
アプリケーションの接続設定がオンであれば、Node-HIDでキーボードを探して接続します。
この時、QMKなデバイスはデフォルトではusage: 0x61、usagePage: 0xFF60と定義されているので、これを探してあげましょう。
接続設定がオン時にキーボードのケーブルを抜き差ししても大丈夫です。
レイヤーを切り替えるためには今現在、なんのアプリケーションを触っているか知る必要があるので取得します。
アプリケーションを切り替えた時、アクティブウィンドウ名が変わるので差分を検知し、新しいアクティブウィンドウ名とレイヤーが設定されいる場合はNode-HIDを使ってコマンドを発行します。
これでレイヤーが自動的に切り替わり、専用のキーマップになります。
マクロパッドを多用する人に恩恵があるかと思います。
ElectronではOSの情報を取得できるので、OSとレイヤーが設定されていれば接続時にそちらに切り替えます。
QMKにはDFなどがあるのですが、面倒だし1キー消費したくないですね。DFコマンドを発行している訳ではなくGPK RCの方でレイヤーを振っています。
この機能はマクロパッドというより、一つのキーボードをWindowsやmacなど色んな環境で使う人に向いていると思います。
これもアクティブウィンドウと同じで分単位で差分を見て変わっていたらOLEDへ書き込みをします。
OLEDへ書き込みは毎分行うので、OLEDのディスプレイオフ時間を60秒以上にしているとオフになる前に書き込まれるます。
その為、ディスプレイがオフにならないので気をつけましょう。
以上のことを500msecくらいの間隔で繰り返し実行しています。
設定ファイルはただのjsonファイルなので別のマシンにコピペすればOSを問わず使えます。
ファイルの場所はRegisterable Keyboard Listの下に書いてあります。(ちゃんとINFOタブ作って移動しないとね…)
自動レイヤー切り替えと時間表示がすこぶる快適なので特に考えてないです…。
が、使用者が増えれば何かしらの要望が出てくるのかなぁと。
また、この手のユーティリティアプリが本当にないので、自作キーボードの裾野を広げる為には今後もっと必要になってくるのではないかなと思います。
ので、もっと使いやすい便利なアプリケーションを読んでくださっている方が作ってくれることを切に願ってます…。
ではでは〜。