More than 1 year has passed since last update.

この記事は ErgoDox Advent Calendar 2016 の4日目の記事です。

ErgoDoxを使用している大抵の人はキーマップを変更していると思いますが、マクロはあまり使用されていない(気がする)ので解説します。
この記事ではErgoDox EZ + qmk_firmwareを前提としています(EZじゃなくても良いハズ)。

↓記事とは関係ないですが、ぼくが使用しているイケてるキーマップです。
keymap.c (osx)
keymap.c (win&osx)

基礎編

keymap.mdに記載されている内容です。

マクロで使用できるコマンド一覧

コマンド 動作
I() マクロの実行間隔を変更(ミリ秒)
D() キーを押下
U() キーを離す
T() キーをタップ(押して離す)
W() wait(ミリ秒)
END マクロ終了

マクロの定義

マクロアクションは、次の様にコマンドを実行したい順に記述して作成します。
(例) 'hello'と入力

MACRO( T(H), T(E), T(L), T(L), T(O), END )

キー押下時に呼び出すには、action_get_macro内のswitchでidに割り当てる必要があります。
action_get_macroはkeydown/keyupそれぞれのタイミングで呼ばれる為、record->event.pressedの値を使用し、どちらで実行するかを明示的に指定する必要があります(しないと2回実行されちゃう)。
下記の例では、M(0)を割り当てたキーをkeydownすると'Hello'、M(1)をkeyupすると'Bye'と入力します。

keymap.c
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
    switch(id) {
    case 0: // id=0のマクロ定義
        if (record->event.pressed) {
            return MACRO(D(LSFT), T(H), U(LSFT), T(E), T(L), T(L), T(O), END); // keydown時の動作('Hello'と入力)
        }
        break;
    case 1: // id=1のマクロ定義
        if (!record->event.pressed) {        
            return MACRO(D(LSFT), T(B), U(LSFT), T(Y), T(E), END); // keyup時の動作('Bye'と入力)
        }
        break;
    }
    return MACRO_NONE;
}

マクロ実行中はキー入力を受け付けません。
長すぎるインターバルを設定したり、無限ループしないよう気をつけましょう。

注意
誰もやらないと思うけど、パスワードを1キーで打てるようにするのはやめよう。
plain textで保存するよりは良いとおもうけど...

マクロの割り当て

マクロを実行させたいキーに、KC_*等の代わりにM(id)を割り当てます。
マクロを沢山使用するならdefineした方が良いかも。

keymap.c
#define M_BYE M(1)

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [BASE] = KEYMAP(
        M(0),  KC_1, KC_2, KC_3, KC_4, KC_5, KC_NO,
        M_BYE, KC_Q, // 以下略...
    ),
}

応用編

もう少し実用的な例を示します。

キー入力マクロをもっと簡単に

SEND_STRING()を使うと、単純なキー入力マクロを簡単に記述できます。
ただし、インターバルの設定等はできない為、複雑な処理をしたい場合は前述のreturn MACRO()で記述する必要があります。

keymap.c
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
    switch(id) {
    case 0: // id=0のマクロ定義
        if (record->event.pressed) {
            SEND_STRING("Hello"); // keydown時の動作('Hello'と入力)
        }
        break;
    }
    return MACRO_NONE;
}

コレ系のマクロは、SEND_STRING(":w\n"); とか SEND_STRING(":qa\n"); とか作っておくと便利そう。
(と思ったけど、.vimrc書けば良いか...)

quantum.cの実装は次のようになっていました。
quantum.hで #define SEND_STRING(str) send_string(PSTR(str)) が定義されています。
なお、register_code()はkeydown、unregister_code()はkeyupです。下記の通り、send_stringでは大文字等含む場合はLSFTを押してくれます。

quantum.c
void send_string(const char *str) {
    while (1) {
        uint8_t keycode;
        uint8_t ascii_code = pgm_read_byte(str);
        if (!ascii_code) break;
        keycode = pgm_read_byte(&ascii_to_qwerty_keycode_lut[ascii_code]);
        if (pgm_read_byte(&ascii_to_qwerty_shift_lut[ascii_code])) {
            register_code(KC_LSFT);
            register_code(keycode);
            unregister_code(keycode);
            unregister_code(KC_LSFT);
        }
        else {
            register_code(keycode);
            unregister_code(keycode);
        }
        ++str;
    }
}

押下時間で処理内容を変える

action_get_macro実行時、keydownとkeyupの処理を分けられる為、押下している時間を使用したマクロを作ることができます。
次の例では、長押し(150ms以上押してた)時にCmd+C、タップ時にCmd+Vします。

keymap.c
static uint16_t start;
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
    switch(id) {
    case 0: // id=0のマクロ定義
        if (record->event.pressed) {
            // 押下したとき
            start = timer_read();
        } else {
            // 離したとき
            if (timer_elapsed(start) >= 150)
                // 押下してから150ms以上(長押し)ならコピー
                return MACRO(D(LGUI), T(C), U(LGUI), END);
            else
                // 押下してから150ms未満(タップ)ならペースト
                return MACRO(D(LGUI), T(V), U(LGUI), END);
        }
        break;
    }
    return MACRO_NONE;
}

ランダムな文字列を入力する

quantum.cのtap_random_base64()で、Base64の文字をランダムで一つ出力できます。
次の例では、マクロ実行時に8桁のランダムな文字列を出力します。何かに使えそう。

keymap.c
const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt) {
    switch(id) {
    case 0: // id=0のマクロ定義
        if (record->event.pressed)
            for (int i = 0; i < 8; ++i)
                tap_random_base64();
        break;
    }
    return MACRO_NONE;
}

ちなみにここで使用されている乱数は下記の通りです。

quantum.c
  #if defined(__AVR_ATmega32U4__)
    uint8_t key = (TCNT0 + TCNT1 + TCNT3 + TCNT4) % 64;
  #else
    uint8_t key = rand() % 64;
  #endif

ルーレット的なものを作る

何か良いネタは無いかなあと考えていたところふと思いついたので、ルーレット(?)を作りました。
roulette()を叩くとキーボード右上の3つのLEDが順番に点灯し、ランダムでどれか一つに止まります。
それだけです...

keymap.c
void single_light_on(int num) {
    ergodox_board_led_off();
    ergodox_right_led_1_off();
    ergodox_right_led_2_off();
    ergodox_right_led_3_off();
    switch (num) {
    case 1:
        ergodox_right_led_1_on();
        break;
    case 2:
        ergodox_right_led_2_on();
        break;
    case 3:
        ergodox_right_led_3_on();
        break;
    }
}

void roulette(void) {
#if defined(__AVR_ATmega32U4__)
    uint8_t val = (TCNT0 + TCNT1 + TCNT3 + TCNT4) % 3;
#else
    uint8_t val = rand() % 3;
#endif
    for (int i = 0; i <= 9 + val; ++i) {
        single_light_on(i % 3 + 1);
        _delay_ms(100);
    }
    for (int i = 0; i < 3; ++i)
    {
        single_light_on(0);
        _delay_ms(100);
        single_light_on(val + 1);
        _delay_ms(100);
    }
    _delay_ms(900);
    matrix_scan_user();
}

const macro_t *action_get_macro(keyrecord_t *record, uint8_t id, uint8_t opt)
{
  // MACRODOWN only works in this function
    switch(id) {
    case MACRO_TEST:
    if (record->event.pressed) {
        roulette();
    }
    break;
    }
    return MACRO_NONE;
};

まとめ

やろうと思えば色々できそう。grep -r MACROすると良いサンプルが沢山見つかるので参考になると思います。

この記事では扱いませんでしたがゲームでは色々活用できそうな気がする(コンボとか)?
スイッチにLEDが載っていれば色々遊べそうだけどEZには載せられない... InfinityのDropを待とう。

ちなみにぼくは普段マクロを使用していません...