タップダンスとは、ドラムを叩くことを禁止された黒人たちが、ドラムの代わりに足を踏みならし、音を奏でたことでできあがったモダンダンスの一つ(Wikipediaから)。
今回は、キーボードの話だ。
キーボードを踏みつけてしまっては壊れてしまう。
そのため、指でキーボード上をタップダンスで操ろうという試みだ。
Tap Dance
1回の押下と2回連続押下で、キーが異なるという機能になる。
名前は似ているが、ACTION_FUNCTION_TAP
とは違うようだ。
例えば
セミコロンキーを1回押下すれば、当然セミコロンが入力できる。
そして、セミコロンキーを2回連続で押下すれば、(コロンを設定しているならば)コロンが入力できる。
3回連続押下では、(設定しているならば)LEDが光る。
などなど・・・分かりにくい説明に思えるが、1つのキーで複数のキー入力の使い分けが可能と言うこと。
ACTION_FUNCTION_TAP
では、ACTION_FUNCTION_TAPとは何か。
残念ながらよく分からない。redditで説明している?
取りあえず、拙い説明を続ける。
唯一分かるのは、この機能を代替する場合骨が折れるだろうと言うこと。
簡単にするのが今回のタップダンスだというわけだ。
ACTION関数
ヘッダファイルにACTION_FUNCTION_TAP
の記述があった。
enum action_kind_id {
/* Key Actions */
・・・,
ACT_FUNCTION = 0b1111
};
typedef union {
uint16_t code;
struct action_kind {
uint16_t param :12;
uint8_t id :4;
} kind;
struct action_key {
uint8_t code :8;
・
・
・
}
}
/* action utility */
#define ACTION(kind, param) ((kind)<<12 | (param))
/* Function */
enum function_opts {
FUNC_TAP = 0x8, /* indciates function is tappable */
};
#define ACTION_FUNCTION_TAP(id) ACTION(ACT_FUNCTION, FUNC_TAP<<8 | (id))
ACTIONは、Functionの一種?
SPC
これ何?
簡易的な使い方(タップダンス披露開始)
いつも通りrules.mk
に記載することで使えるようになる。
TAP_DANCE_ENABLE=yes
キーの割り当て方法はTD(x)
のように行う。もちろんxは事前準備に仕込んだ数字(文字)になる。
なぜか **TD(x)**だ。ACTION_FUNCTION_TAP(x)
ではなく、
エイリアスに似た定義をするからなのだろう・・・きっと。
大雑把な使い方ACTION_TAP_DANCE_DOUBLE
1回のタップとそれ以外の連続タップ 2回連続タップで挙動が異なる。
関数名:ACTION_TAP_DANCE_DOUBLE
引数:2つ
利用例:ACTION_TAP_DANCE_DOUBLE(kc1, kc2)
ただし、これをそのままキーに割り当てることは出来ない。
また、事前準備として、別ファイルの用意が必要だ。
TAP_DANCE_ENABLE = yes
このファイルを用意しなければ、コンパイルエラーが発生する。
今までそんなことはなかったため、ヘッダファイルか何かを読み込むような何かがあるのだろう。
そして、このファイルを追加する以上hexファイルの容量が増える。
気をつけること。
他のコーディング方法も今までのより難易度は高くなっている(ほんのちょっとだけど)。
enum TapDanceDeclarations{
TD_BSPACE_K = 0
};
//Tap Dance Definitions
qk_tap_dance_action_t tap_dance_actions[] = {
[TD_BSPACE_K] = ACTION_TAP_DANCE_DOUBLE(KC_BSPACE, KC_K)
};
[BASEPlate] = LAYOUT_ergodox( // layer 0 : default
// left hand
TD(TD_BSPACE_K), KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 7桁 */
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 7桁 */
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 6桁 */
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 7桁 */
KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 5桁 */
・
・
・
TDキーは、左上(通常のキーボードであればEscキーやチルダに位置する場所)に設定した。
tap_dance_actions
は、何となく分かって頂けると思うのだが・・・これは、0番目の配列から順番にタップダンスの挙動を格納する配列名だ。
今回は例と同じACTION_TAP_DANCE_DOUBLE
を使った(なぜにヘッダファイルに記述している関数を使うのか)。
例では無名enumを使っていたが、分かりにくいので今回名前を付けた。
上記の挙動は、tap_dance_actions配列の[TD_BSPACE_K]
などによって変わる。
今回は、まさにタップダンスと言うことで、1回目の押下はバックスペースキーとして働き、2回連続押下はKキーとして働く。
3回連続の場合は、Kキー押下後にバックスペースキーという感じになる。
複雑だが大雑把な説明で1回と2回連続タップの挙動確認
例によって例を使う。
void dance_cln_finished (qk_tap_dance_state_t *state, void *user_data) {
if (state->count == 1) {
register_code (KC_RSFT);
register_code (KC_SCLN);
} else {
register_code (KC_SCLN);
}
}
void dance_cln_reset (qk_tap_dance_state_t *state, void *user_data) {
if (state->count == 1) {
unregister_code (KC_RSFT);
unregister_code (KC_SCLN);
} else {
unregister_code (KC_SCLN);
}
}
//All tap dance functions would go here. Only showing this one.
qk_tap_dance_action_t tap_dance_actions[] = {
[TD_BSPACE_K] = ACTION_TAP_DANCE_DOUBLE(KC_BSPACE, KC_K),
[CT_CLN] = ACTION_TAP_DANCE_FN_ADVANCED (NULL, dance_cln_finished, dance_cln_reset)
};
[BASEPlate] = LAYOUT_ergodox( // layer 0 : default
// left hand
TD(CT_CLN), KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, /* 7桁 */
・
・
・
※順番大事
C言語はあまり頭が良くないため、dance_cln_finished
やdance_cln_reset
を先に記述してからtap_dance_actions配列のなかでdance_cln_finished関数やdance_cln_reset関数を呼び出す必要がある。
tap_dance_actions配列を先に書いてから関数をその後ろに記述した場合、エラーになる。
上記の内容は、1回押下で:
キーになり、2回連続押下で;
キーになる。
第1引数は、指定の機能を呼び出すのだが、今回はNULL
として何もしないようにしている。
第2引数は、dance_cln_finished関数を呼び出す。
第3引数は、dance_cln_reset関数を呼び出す。
キーを押すことで、上記の引数の内容が第1引数から第3引数まで順番に呼び出される。
register_code
は、キー押下を表す。
unregister_code
は、キー押上を表す。
なぜに例の癖して第1引数をNullにしているのか分からない。どうやって使えば良いのか・・・。
普通のKC_Kを渡した場合、当然エラーになる。
タップで文字がダンスする
まさに、タップダンス^^
void dance_egg (qk_tap_dance_state_t *state, void *user_data) {
if (state->count >= 100) {
SEND_STRING ("Safety dance!");
reset_tap_dance (state);
}
}
qk_tap_dance_action_t tap_dance_actions[] = {
[CT_EGG] = ACTION_TAP_DANCE_FN (dance_egg)
};
100回以上連続押下をすることで、Safety dance
の文字列がテキストエディタに入力される。
もちろん、連続タップの時間が設定されているため、私は連続100回押下無理でした。
そのため3回にして文字をダンスさせた。
if (state->count >= 3) {
4回連続押下でも文字を躍らせることが出来た。
何に使うのか分からないが、メールを打つときの定型文には良いかもしれない。
LEDを使って光りを踊らせることも可能
例によって説明しているのだが、省略する。
過去の投稿でLED点灯は満足している。
正直に言えば、LEDは興味が薄れてしまった。
なにせ、LED付きのHelixごみキーボードでやる気をそがれてしまった。
無念だ。
4種類のタップダンス
かなり複雑な設定により、1回押下・押し続ける・2回連続押下・2回連続押下後押し続けることの4種類を設定することが可能らしい。
取りあえず、例通りに進めるが、見ただけで複雑なため、1回の作業では理解できないだろう。
そのため、今後加筆していくはず・・・きっと。
究めれば、2回連続押下の1回押下と言う訳の分からないことや3回連続押下や3回連続押下後の押し続けも対応可能らしい。
(自分でも何を言っているのか分かっていない)
例では、何やら複数のファイルを使い、mkファイルにも書き込みをしている。
しかし、めんどくさいので、使わない。
そして、コメント部分は極力削除して、プログラム部分のみ流用している。
enum TapDanceDeclarations{
XX_CTL,
SOME_OTHER_DANCE
};
typedef struct {
bool is_press_action;
int state;
} tap;
enum TapDanceQuadFunction {
SINGLE_TAP = 1,
SINGLE_HOLD = 2,
DOUBLE_TAP = 3,
DOUBLE_HOLD = 4,
DOUBLE_SINGLE_TAP = 5, //send two single taps
TRIPLE_TAP = 6,
TRIPLE_HOLD = 7
};
int cur_dance (qk_tap_dance_state_t *state) {
if (state->count == 1) {
if (state->interrupted || !state->pressed) return SINGLE_TAP;
else return SINGLE_HOLD;
}
else if (state->count == 2) {
if (state->interrupted) return DOUBLE_SINGLE_TAP;
else if (state->pressed) return DOUBLE_HOLD;
else return DOUBLE_TAP;
}
if (state->count == 3) {
if (state->interrupted || !state->pressed) return TRIPLE_TAP;
else return TRIPLE_HOLD;
}
else return 8; //magic number. At some point this method will expand to work for more presses
}
//instanalize an instance of 'tap' for the 'x' tap dance.
static tap xtap_state = {
.is_press_action = true,
.state = 0
};
void x_finished (qk_tap_dance_state_t *state, void *user_data) {
xtap_state.state = cur_dance(state);
switch (xtap_state.state) {
case SINGLE_TAP: register_code(KC_X); break;
case SINGLE_HOLD: register_code(KC_LCTRL); break;
case DOUBLE_TAP: register_code(KC_ESC); break;
case DOUBLE_HOLD: register_code(KC_LALT); break;
case DOUBLE_SINGLE_TAP: register_code(KC_X); unregister_code(KC_X); register_code(KC_X);
}
}
void x_reset (qk_tap_dance_state_t *state, void *user_data) {
switch (xtap_state.state) {
case SINGLE_TAP: unregister_code(KC_X); break;
case SINGLE_HOLD: unregister_code(KC_LCTRL); break;
case DOUBLE_TAP: unregister_code(KC_ESC); break;
case DOUBLE_HOLD: unregister_code(KC_LALT);
case DOUBLE_SINGLE_TAP: unregister_code(KC_X);
}
xtap_state.state = 0;
}
qk_tap_dance_action_t tap_dance_actions[] = {
[TD_BSPACE_K] = ACTION_TAP_DANCE_DOUBLE(KC_BSPACE, KC_K),
[XX_CTL] = ACTION_TAP_DANCE_FN_ADVANCED(NULL,x_finished, x_reset)
};
これだけで何をやっているのか分からず混乱する。
enumは、説明不要だよね。
typedef struct〜なんちゃら
は、構造体なので、変数の詰め込み版だと思えば良いだけ。たいして難しいものではない・・・と思うことにしよう。
その下で、static tap xtap_state〜なんちゃら
と言う記述があり、そこで構造体に値を設定している(何のために設定するのか不明)。
きっとxtap_state
の.state
にて、押下回数を記録させているんだと思う。
それ以降は関数になる。
ここまで来たら理解できない処理ばかり・・・あぁ。
これらの関数を呼び出しているのが、tap_dance_actions配列にあるACTION_TAP_DANCE_FN_ADVANCED
なのだが・・・。
また、3つの引数を渡しているが、1つ目はNULLと言う・・・。
第2引数でキーを押し続け、第3引数でキーを離す処理が絶対的な組合せなのだろう。
そのキー押下に関してだけでも理解したい。
1回押下:Xキー押下のこと。
1回押し続ける:コントロールキー押下のこと。
2回押下:Escキー押下のこと。
2回押下で2回目は押し続ける:Altキー押下のこと。
ダブルシングルタップ: 不明 理解の範疇を超えている。どのようにすれば再現するのかもよく分からない。キー押下のことでもない。挙動を目視できない。3回連続押下でも何も起きない。
(結局理解できなかった)
1回押下(・2回押下[・3回押下{
シフトキー押下との組みあわせキーコードが使えない。
結構めんどくさいが、取りあえず出来ることは出来る。
無駄な部分はほぼ省いた状態で記述する(上記で説明済みなので)。
省略無し
ヘッダファイルの読み込み不要だった。
ついでに、閉じ括弧も同じように実装している。
このとき、rprn_tap_state関数を新しく追加している(用心のために)。
enum TapDanceDeclarations{
// キーコード宣言
LPRN_LBRC_LCBR, // 開き括弧用
RPRN_RBRC_RCBR, // 閉じ括弧用
SOME_OTHER_DANCE
};
enum TapDanceQuadFunction {
// タップ回数宣言
SINGLE_TAP = 1,
SINGLE_HOLD = 2,
DOUBLE_TAP = 3,
DOUBLE_HOLD = 4,
DOUBLE_SINGLE_TAP = 5, //send two single taps
TRIPLE_TAP = 6,
TRIPLE_HOLD = 7
};
typedef struct {
// タップの保存変数宣言(正確には構造体)
bool is_press_action;
int state;
} tap;
static tap lprn_tap_state = {
// タップの保存変数(開き括弧用)
.is_press_action = true,
.state = 0
};
static tap rprn_tap_state = {
// タップの保存変数(閉じ括弧用)
.is_press_action = true,
.state = 0
};
int cur_dance (qk_tap_dance_state_t *state) {
// タップに関する挙動を処理する部分だろう。
if (state->count == 1) {
if (state->interrupted || !state->pressed) return SINGLE_TAP;
else return SINGLE_HOLD;
}
else if (state->count == 2) {
if (state->interrupted) return DOUBLE_SINGLE_TAP;
else if (state->pressed) return DOUBLE_HOLD;
else return DOUBLE_TAP;
}
if (state->count == 3) {
if (state->interrupted || !state->pressed) return TRIPLE_TAP;
else return TRIPLE_HOLD;
}
else return 8; //magic number. At some point this method will expand to work for more presses
}
void LPRN_finished (qk_tap_dance_state_t *state, void *user_data) {
// キー押下(開き括弧)
lprn_tap_state.state = cur_dance(state);
switch (lprn_tap_state.state) {
case SINGLE_TAP: register_code(KC_LSFT); register_code(KC_9);
break;
case DOUBLE_TAP: register_code(KC_LBRACKET); break;
case TRIPLE_TAP: register_code(KC_LSFT); register_code(KC_LBRACKET);
break;
}
}
void LPRN_reset (qk_tap_dance_state_t *state, void *user_data) {
// キー押上(開き括弧)
switch (lprn_tap_state.state) {
case SINGLE_TAP: unregister_code(KC_9); unregister_code(KC_LSFT);
break;
case DOUBLE_TAP: unregister_code(KC_LBRACKET); break;
case TRIPLE_TAP: unregister_code(KC_LBRACKET); unregister_code(KC_LSFT);
break;
}
lprn_tap_state.state = 0;
}
void RPRN_finished (qk_tap_dance_state_t *state, void *user_data) {
// キー押下(閉じ括弧)
rprn_tap_state.state = cur_dance(state);
switch (rprn_tap_state.state) {
case SINGLE_TAP: register_code(KC_LSFT); register_code(KC_0);
break;
case DOUBLE_TAP: register_code(KC_RBRACKET); break;
case TRIPLE_TAP: register_code(KC_LSFT); register_code(KC_RBRACKET);
break;
}
}
void RPRN_reset (qk_tap_dance_state_t *state, void *user_data) {
// キー押上(閉じ括弧)
switch (rprn_tap_state.state) {
case SINGLE_TAP: unregister_code(KC_0); unregister_code(KC_LSFT);
break;
case DOUBLE_TAP: unregister_code(KC_RBRACKET); break;
case TRIPLE_TAP: unregister_code(KC_RBRACKET); unregister_code(KC_LSFT);
break;
}
rprn_tap_state.state = 0;
}
[BASEPlate] = LAYOUT_ergodox( // layer 0 : default
// left hand
TD(CT_CLN), KC_NO, KC_NO, KC_NO, KC_NO, KC_NO, KC_NO,
// ・
// ・
// ・
)
};
//Tap Dance Definitions
qk_tap_dance_action_t tap_dance_actions[] = {
[LPRN_LBRC_LCBR] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, LPRN_finished, LPRN_reset),
[RPRN_RBRC_RCBR] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, RPRN_finished, RPRN_reset)
};
調べたからと言って、快適に使いこなせるかどうかは別問題だが・・・。
なにせ、キーマッピング時の配置に悩みに悩むからな・・・(多用し、混乱し、戻し・・・さらに、汎用キーボードにまで戻すと言う・・・)。
enum TapDanceDeclarations{
XX_CTL,
LPRN_LBRC_LCBR,
SOME_OTHER_DANCE
};
static tap lprn_tap_state = {
.is_press_action = true,
.state = 0
};
void LPRN_finished (qk_tap_dance_state_t *state, void *user_data) {
lprn_tap_state.state = cur_dance(state);
switch (lprn_tap_state.state) {
// case SINGLE_TAP: register_code(KC_LPRN); break;
// case SINGLE_TAP: register_code('('); break;
// case SINGLE_TAP: register_code( SS_LSFT("(") ); break;
// case SINGLE_TAP: register_code( SS_DOWN(X_LSHIFTR) ); break;
case SINGLE_TAP: register_code(KC_LSFT); register_code(KC_9);
break;
case DOUBLE_TAP: register_code(KC_LBRACKET); break;
// case TRIPLE_TAP: register_code(KC_LCBR); break;
case TRIPLE_TAP: register_code(KC_LSFT); register_code(KC_LBRACKET);
break;
}
}
void LPRN_reset (qk_tap_dance_state_t *state, void *user_data) {
switch (lprn_tap_state.state) {
case SINGLE_TAP: unregister_code(KC_9); unregister_code(KC_LSFT);
break;
case DOUBLE_TAP: unregister_code(KC_LBRACKET); break;
case TRIPLE_TAP: unregister_code(KC_LBRACKET); unregister_code(KC_LSFT);
break;
}
xtap_state.state = 0;
}
問題点は、コメントアウト(//行)で、KC_LPRN
は、9キーとShiftキーの組合せで実現している。
それを今回使用するつもりだったが、コンパイルエラーが発生し、なぜか使えない。
仕方ないため、Shiftキーを押し続け、その後9キーを押すことで括弧入力を実現した。
面倒だぞ。
当然ながらLPRN_reset
関数で、それを押し上げる処理を記述しなければならない。
ぬぅぅめんどくさい。
しかし、実現できた。
良かった。
4回連続押下は、無効になり、何も入力されない。
そんな処理を記述していないから当たり前なのだが・・・。
実際に使ってみて、あまり使い勝手が良いように感じられなかった。
今回お試し利用だからかな・・・。
使い方不明
他にも使えるはずの関数が軒を連ねているのだが、解説がないため使い方が分からずじまいになっている。
もし、これらが使えるのであれば、もっとキーボードの利用方法の幅は広がるだろう。
ACTION_TAP_DANCE_DOUBLE関数が使えるならば、ACTION_TAP_DANCE_DUAL_ROLE
やACTION_TAP_DANCE_FN
も使えるだろう。
preprocess_tap_dance関数は、マクロが使えるような引数名に見える。
process_tap_dance.hを読み解けるかどうかが鍵かな。
process_tap_dance.c
このソースファイルも存在がよく分からない。
使い方の説明がないのは何故だろうか。
最後に
幸せなら足ならそう。
幸せなら手を叩こう。
幸せなら指ならそう。
幸せならキーボードで奏でよう。
幸せなら態度で示そうよ。
今日も元気にキーマッパー。
以上だ。