この記事は ここのえ Advent Calendar 2023 Day 18の記事です。
自作左手デバイスでQoLを上げる
突然ですが、皆さんはいわゆる 左手デバイス はお持ちでしょうか?
一般的にお絵描きやクリエイティブ用途としては、Loupedeck とか、TourBox とかが有名で、ゲーミング用途で持っている場合は Razer Tartarus Pro がメジャーです。
ただこれらのデバイスはPC側に専用のファームウェアが必要だったり、それなりにイイお値段がするので、なかなか敬遠してしまいがちです。
そんな時、知人が自作キーボードを作り始めたことから興味を持ち、遊舎工房 を眺めていたらこんなものを見つけました。
自作左手デバイス…!?
要は自作キーボードの一種ですが、コンパクトにまとまっていたり、特殊なインプットとしてホイール等がついている自作キットが複数存在しています。
今回組み立てたのは先述のキットで、はやしたろう さんの Handyman を選びました。
ビルドガイド がとても丁寧に書かれておりオススメです。自分も初めての自作でしたが、何とかなりました。この場を借りて感謝申し上げます……
自分はソフトウェア畑で育ってきたので、電子工作はラズパイとArduinoをブレッドボードとかでいじって触ったことはあるけれど、半田付けには1μmも自信がない、といった具合でした。頑張れば人間なんとかなります。
カスタマイズする
実際に組み立てたら、デバイスのカスタマイズを行っていきます。
ここが自作デバイスの一番面白いところだと思います。
一般的なユースケースでは、Remap を使用することを強く推奨します。ブラウザベースでキーマッピングを設定して、すぐに書き込むことができます。
ですが、それでは収まらないのがプログラマーの性です。
レイヤーの切り替えタイミングでコールバック関数を走らせて独自の処理を走らせたりとか、徹底的にカスタマイズしたくなります。
こうなると、ておくれです。
自分でファームウェアをビルドするしかありません……
そこで QMK Firmware が出てきます。
QMKは自作キーボード界隈で(たぶん)一番使われているファームウェアで、例えばWindowsならQMK MSYSというQMKのCLIや、ファームウェア書き込み&デバッグコンソールの機能を兼ねたQMK Toolboxといった、強力なツールチェーンが揃っています。
先述の Remap も、QMK Firmwareベースの端末に対応しており、Raw HID
の機能を使って実装されているようです1。
実装サンプルなど
自前のカスタム後のコードを基に少し解説していきます。
今回は実際のセッティング例を挙げるので、環境構築などは省きます。
環境構築からビルドまでの一連の流れは、以下の記事がお勧めです。
Handyman は作者のはやしたろうさんが、ファームウェアのサンプルを公開して下さっていますので、これをベースに使っています。
レイアウト
例えば、Adobe Illustrator
用のレイヤーはこんな構成になっています。
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[LAYER_AI] = LAYOUT( /* Adobe Illustrator */
LCTL(KC_2), LALT(KC_DOWN), LALT(KC_UP), LCTL(KC_1), LCTL(KC_0),
KC_V, LCTL(KC_S), LCTL(LSFT(KC_G)), KC_DEL, LCTL(KC_RBRC),
KC_A, XXXXXXX, LCTL(KC_G), XXXXXXX, LCTL(KC_LBRC),
KC_LSFT, LCTL(KC_X), LCTL(KC_C), LCTL(KC_V), XXXXXXX,
KC_LCTL, KC_LALT, LCTL(KC_7), LCTL(LSFT(KC_O)), KC_SPACE,
DF(LAYER_XIV), XXXXXXX, DF(LAYER_CUBASE)
),
簡単に説明すると、以下のような感じです。
-
KC_A
KC_LBRC
など- KeycodeのKC。キーボードのキーが対応している
-
LCTL()
,LSFT()
- 左Ctrl、左Shift + αの操作を定義できる。
-
LCTRL(LALT(Keycode))
のように重ねることも可能。
-
DF(LAYER_ID)
- デフォルトレイヤーの変更。
- キーマップのセットを「レイヤー」として保持しておき、後から切り替えることができる。
- 基本的にレイヤーのIDはint。今回はサンプルコード外で
#define LAYER_AI 0
などしている。
実装が何となくわかっても、イラレを知らないとなんのこっちゃいですね。さすがに怒られそうなので画像を作っておきました。
選択
~テキスト
までの列については、イラレのツールバーのうちよく使うものを抽出、違和感がないように画面上の表示と同じ順番で並べています。
カット
, コピー
, 貼り付け
については一般的なQWERTYキーボードと同様の並び順にすることで、使用時に違和感が起きないようにしています。
全選択
だけ逆になってやや微妙なのですが、あまり頻繁に使わないのでこの位置にしています。
やり直し
, 取り消し
がかなり特殊な位置に設定しています。
ダイヤル式のエンコーダーに当てることで、進む・戻るを直感的にしつつ、通常のCtrl+Zでは指が疲れてしまう連打も、ぐるぐる回して一気に戻せるので操作感が良く、かなり気持ちいいです。
加えてキーボードとは違うデバイスであることを無意識的にわからせるという意味でも役立っています。
何気に気に入ってるのが画面サイズに拡大
や前面/背面
あたりで、この辺りはキーボードから手を離さないとならず、加えて0
なんか特に押し損ねがちです。この辺は何を選ぶか人の好みがありそうです。
エンコーダーの押し込みについては、誤ってスクロールするとストレスフルになりそうなので、敢えてイラレのレイアウトでは使っていません。他のレイヤーでは使うこともあります。
エンコーダーのキーマッピング
先程のレイヤーのコードを見て勘のいい方は気づいたかもしれませんが、実はレイヤー定義ではホイールやダイヤルが動いたときの処理は定義していません。
エンコーダーが動くと encoder_update_user
が呼び出されるので、ここに処理を書いてみます。
bool encoder_update_user(uint8_t index, bool clockwise) {
switch(get_highest_layer(default_layer_state)) {
case LAYER_AI:
if (index == 0) { /* First encoder */
if (clockwise) {
tap_code(KC_WH_U);
} else {
tap_code(KC_WH_D);
}
} else if (index == 1) { /* Second encoder */
if (clockwise) {
tap_code(KC_WH_L);
} else {
tap_code(KC_WH_R);
}
} else if (index == 2) { /* third encoder */
if (clockwise) {
tap_code16(LCTL(LSFT(KC_Z)));
} else {
tap_code16(LCTL(KC_Z));
}
}
break;
エンコーダの処理については、サンプルコードを基にレイヤー処理を追加しています。
index
はエンコーダーの番号で、clockwise
は回転の方向です。
肝心の処理レイヤーチェックですが、default_layer_state
を参照しています。stateの値はレイヤーのON/OFF状況をビットで保存しているので、レイヤー番号で比較するために get_highest_layer()
を使っています。
レイヤーでswitch
したら、後は各エンコーダーごとに処理を定義していきます。
tap_code
は気を付ける必要がある
上記コードの3番ダイヤルの処理ですが、tap_code16
になっています。この16は何でしょうか?
例えばこのコードは、以下のコードで置き換えることが可能です。
if (clockwise) {
register_code(KC_LCTL);
register_code(KC_LSFT);
tap_code(KC_Z);
unregister_code(KC_LCTL);
unregister_code(KC_LSFT);
これは Ctrl + Shift + Z
を実現しているのですが、ちょっと厄介な手順を踏まなければいけません。
前提としてこれらの関数は、以下の役割を果たします。
-
register_code(KeyCode)
- 指定されたキーコードのキーを押す。
-
unregister_code(KeyCode)
- 指定されたキーコードのキーを離す。
-
tap_code(KeyCode)
-
register_code
とunregister_code
の合わせ技。
-
ここでtap_code(LCTL(KC_Z))
などとすると、コンパイル時に怒られます。
quantum/quantum_keycodes.h:45:18: error: unsigned conversion from 'int' to 'uint8_t' {aka 'unsigned char'} changes value from '285' to '29' [-Werror=overflow]
#define LCTL(kc) (QK_LCTL | (kc))
^~~~~~~~~~~~~~~~
./keyboards/test/handyman/keymaps/default/keymap.c:117:30: note: in expansion of macro 'LCTL'
tap_code(LCTL(KC_Z));
ドキュメントには関数定義までは書いていませんが、ソースコードを読んでみるとquantum/action.c
でtap_code
はuint8_t
を引数に取っています。uint8_t
は0
~255
なので、複合キーの値を取れなそうです。
そこで上記の例ではKC_LCTL
とKC_LSFT
を個々でキーをDown, Upすることで対応しています。
ただ、これではあまりスマートではないです。
そこでtap_code16(uint16_t code)
を使います。こちらは文字通りuint16_t
を引数に取るので、tap_code16(LCTL(LSFT(KC_Z)));
のような書き方ができます。
レイヤーごとにRGB LEDの色を変える
苦労してLEDを全部半田付けしたので、せっかくなら設定中のレイヤーに合わせてLED色が変わるようにしたいです。
この実装はそんなに難しくありません。
layer_state_t default_layer_state_set_user(layer_state_t state) {
switch(get_highest_layer(state)) {
case LAYER_AI:
rgblight_setrgb(247, 149, 0);
break;
case LAYER_CUBASE:
rgblight_setrgb(21, 55, 82);
break;
...
return state;
レイヤーの変更時に、default_layer_state_set_user
関数が呼ばれるので、これを利用して色を変えます。
レイヤーの取り方については先述のエンコーダーの実装と同じ原理で、get_highest_layer
を使ってレイヤー番号に変換し、switch
で回しています。
RGBはinfo.json
で定義してあったため、特に設定せず使っています。rgblight_setrgb(r, g, b)
関数を呼び出せば、全体の色を変更することができます。
RGB Matrixを使う人は ここ を見てください。自分はレイアウトを定義するのがめんどくさすぎて諦めました。
まとめ
大分長文になってしまいましたが、レイアウトの具体例やコーディング時のTipsなどを纏めてみました。
左手デバイスは確かに癖はあるのですが、自作だとカスタマイズ性が段違いに高い上、キーキャップを好きなものに交換出来たり、「Pro MicroをMicro-BじゃなくType-Cにしたい…」といったハードウェア面での変更もでき、故障時もキーの交換などできます。何より愛着が湧くのが最大のオススメポイントです。
今回の記事を書いた段階ではまだ調整中なのですが、LAYER_MARKDOWN
を作ってマークダウンのドキュメント・記事作成を左手デバイスで効率化できないか、色々試している所です。また纏まったら記事にしようかなと思ってます。
自分はC言語も電子工作も専門外ではありますが、意外となんとかなりました。フロントエンドがメインで、特にデザインとかのソフトを使うのであれば、結構便利な場面があると思います。良かったらチャレンジしてみてください。
おまけの雑談
QMKの全キーコードのリストを見ると、感熱式プリンタ というセクションがあります。何に使うんだ?という疑問の前に、そもそもどうやって使うんでしょうか……
……自作界隈はまだまだ奥が深いですね。