LoginSignup
7
2

More than 5 years have passed since last update.

ErgoDoxEZにて足を踏みならす代わりに指を踏みならそう(執筆途中)

Last updated at Posted at 2018-10-05

タップダンスとは、ドラムを叩くことを禁止された黒人たちが、ドラムの代わりに足を踏みならし、音を奏でたことでできあがったモダンダンスの一つ(Wikipediaから)。

今回は、キーボードの話だ。
キーボードを踏みつけてしまっては壊れてしまう。
そのため、指でキーボード上をタップダンスで操ろうという試みだ。

Tap Dance

1回の押下と2回連続押下で、キーが異なるという機能になる。
名前は似ているが、ACTION_FUNCTION_TAPとは違うようだ。

例えば

セミコロンキーを1回押下すれば、当然セミコロンが入力できる。
そして、セミコロンキーを2回連続で押下すれば、(コロンを設定しているならば)コロンが入力できる。
3回連続押下では、(設定しているならば)LEDが光る。
などなど・・・分かりにくい説明に思えるが、1つのキーで複数のキー入力の使い分けが可能と言うこと。

ACTION_FUNCTION_TAP

では、ACTION_FUNCTION_TAPとは何か。
残念ながらよく分からない。redditで説明している?
取りあえず、拙い説明を続ける。

唯一分かるのは、この機能を代替する場合骨が折れるだろうと言うこと。
簡単にするのが今回のタップダンスだというわけだ。

ACTION関数

ヘッダファイルにACTION_FUNCTION_TAPの記述があった。

action_code.h
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に記載することで使えるようになる。

rules.mk
TAP_DANCE_ENABLE=yes

キーの割り当て方法はTD(x)のように行う。もちろんxは事前準備に仕込んだ数字(文字)になる。
なぜかACTION_FUNCTION_TAP(x)ではなく、 TD(x)だ。
エイリアスに似た定義をするからなのだろう・・・きっと。

大雑把な使い方ACTION_TAP_DANCE_DOUBLE

1回のタップとそれ以外の連続タップ 2回連続タップで挙動が異なる。

関数名:ACTION_TAP_DANCE_DOUBLE
引数:2つ

利用例:ACTION_TAP_DANCE_DOUBLE(kc1, kc2)

ただし、これをそのままキーに割り当てることは出来ない。
また、事前準備として、別ファイルの用意が必要だ。

rules.mk
TAP_DANCE_ENABLE = yes

このファイルを用意しなければ、コンパイルエラーが発生する。
今までそんなことはなかったため、ヘッダファイルか何かを読み込むような何かがあるのだろう。
そして、このファイルを追加する以上hexファイルの容量が増える。
気をつけること。

他のコーディング方法も今までのより難易度は高くなっている(ほんのちょっとだけど)。

keymap.c
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_finisheddance_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ファイルにも書き込みをしている。
しかし、めんどくさいので、使わない。
そして、コメント部分は極力削除して、プログラム部分のみ流用している。

keymap.c
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関数を新しく追加している(用心のために)。

keymap.c
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)

};


調べたからと言って、快適に使いこなせるかどうかは別問題だが・・・。
なにせ、キーマッピング時の配置に悩みに悩むからな・・・(多用し、混乱し、戻し・・・さらに、汎用キーボードにまで戻すと言う・・・)。

keymap.c
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_ROLEACTION_TAP_DANCE_FNも使えるだろう。

preprocess_tap_dance関数は、マクロが使えるような引数名に見える。

process_tap_dance.hを読み解けるかどうかが鍵かな。

process_tap_dance.c

このソースファイルも存在がよく分からない。

使い方の説明がないのは何故だろうか。

最後に

幸せなら足ならそう。
幸せなら手を叩こう。
幸せなら指ならそう。
幸せならキーボードで奏でよう。
幸せなら態度で示そうよ。
今日も元気にキーマッパー。

以上だ。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2