🦶

足で動かすUnitTest

2021/12/24に公開

この記事は、クラウドワークス Advent Calendar 2021の24日目の記事です。

3行で

  • フットスイッチというインタフェースがある
  • Karabiner-Elements でいろいろ割り当てる
  • 快適 (?)

少し前、雑談でこんな話をした

※ 「川原さんキーボードなに使っているんスカ??」
ぼく 「Happy Hacking Keyboard Professional HYBRID Type-S 英語配列/墨です。配色がかっちょいいし、長年HHKBにはお世話になっているんですよね」
※ 「でも、あれ、ファンクションキーとかワンタッチでできないのがちょっと面倒じゃないですか?」
ぼく 「足で操作するので問題ありません」
※ 「え!?どういうことですか」

そういえば、どこのブログでも発信していなかった気がしたので(やったかもしれないけど)、このクラウドワークスAdvent Calendar2021の場をお借りして、説明をすることにしました。
そうです。なんと、この記事は、クラウドワークスアドベントカレンダー2021の24日目の記事です。

自分がリードエンジニアとして日々を生きているコデアル株式会社は、2021年10月1日をもって、株式会社クラウドワークスの子会社となりました
クラウドワークスの取締役の成田さんがコデアルのお手伝いに入ってきているなかで、「そういえば、アドベントカレンダーあるけど、やる?」というお声がけをいただき、せっかくなので参加してみました。よろしくおねがいします!

みなさまが、割と真面目な記事を書いているなか、こんなにもサービスとかの話を一切しない、個人的でお気楽な記事を書いてしまい、たいへん恐縮な思いなのですが、空気を読まずにやります。一応、JSONとか出てくるし、テストを書くときの物理的環境についての自分なりの考えを書いているので、技術的な内容ではあるはずです。
また、Mac上でキーボードの挙動をカスタマイズするためのKarabiner-Elementsの入門的な文章にしたつもりですので、ぜひご活用ください。

まずは実際に仕事の様子を御覧ください

TDDをしている様子の足元、画面の様子を御覧ください。さすがに実際の業務コードを出すわけにはいかないので、AtCoderのABCのA問題回答をTDDで行っています。

https://youtu.be/p6PLIc-aEo4

ことはじめ

数年前、なんとなく欲しい物リストに入れていたフットスイッチが、友人から送られてきました。

https://www.amazon.co.jp/dp/B0759SPDLZ/

今どきミニCDで割当ソフトもついていたのですが、残念ながらWindowsのみにしか対応していないように見えました。
開発はMacユーザだからちょっと残念だなー。どうっすっかなー、これ。とか思っていたところで、Amazonのレビューをよくよく見ると、Macでも動くらしいという報告を見かけたのでした。

早速、USBケーブルをマシンに差し込んでみたところ、普通のキーボードとして認識し、左から a, b, c が割り当てられていることが判明しました。
何らかの方法で、キーボードの設定を書き換えたり、挙動を変えることができれば、大変素敵なツールになりそうということで、いろいろやってみました。

Karabiner-Elementsを入れる

Mac上でキーボードの挙動を変更したいときは、Karabiner-Elements を使えばOKです。サイトに行きダウンロード・インストールしましょう。

このツールは、単純に特定のキーの入力を、別のキーとして割り当てるということもできるのですが、フットスイッチを使いたいからといって、a, b, cキーの挙動を変更するわけには行きません。
これについては、Karabinerの設定の、Simple modifications を使うことで特定のデバイスに限定してキー挙動を変更することができる機能があるので、単純なものであれば問題ありません。

例えば、以下のように Target device を、目的のキーボードにして (今回は FootSwitch3-F1.0) 、From key a (左) を、 escape キーに割り当てるといったことができます。

ただし、フットスイッチには3つしかキーがないので、全部のアプリケーションに対して、同じキーでしか動かないと、場合によっては、まったく使いづらい代物になってしまいます。また、Simple modifications は、割当先のキーについて、「組み合わせ」の対応ができません。例えば、Shift+Ctrl+F10のような組み合わせに変換するという妙技はできないわけです。

そこで、Karabinerには、Complex modifications という機能を使うことで、少し凝った設定をすることができます。

  • どのデバイスが
  • どのようなアプリで
  • どのようなキーを押下したら
  • どのようなキーにするか (組み合わせ、さらに、長押し、2回押しにも対応!)

というレベルまで設定することができ、様々なシチュエーションに対応することができます。

VenderID, ProductID を知る

キーボードやマウスのようなUSB製品には製品ごとに、VenderIDと、ProductIDが割り振られています。今回はフットベダルだけの設定をしたいので、予め調べておく必要があります。

Karbiner の 設定を開き、Devices タブを確認すると、現在マシンが認識している、デバイス一覧が出てきます。フットスイッチは、VenderIDが 3141、 ProductIDが 29700 であるということがわかったので、どこかに控えておきます。

これで どのようなデバイスが という問題を解決するための材料が揃いました。

アプリケーションの BundleIdentifier を知る

次に、独自のキーマップを適用するアプリケーションを特定するための BundleIdentifier を調べます。これは、アプリケーションを識別するためのものです。
Karbiner のメニューから、 Event Viewer を選びます。

Frontmost application というタブに、ログとして今フォーカスがあたっているアプリケーション情報が記載されています。ここに記載されている、目的のアプリケーションの Bundler Identifer が後々必要になりますので、控えます。

これで どのようなアプリで という問題を解決するための材料が揃いました。

設定をかく

基本形

それでは、実際に設定を書いていきましょう。形式は、みんな大好きJSON形式です。

Complex modifications の操作は、~/.config/karabiner/assets/complex_modifications/お好きな名前.json に保存していきます。

まずは基本の外枠。

~/.config/karabiner/assets/complex_modifications/footswitch.json
{
  "title": "フットスイッチ",
  "rules": [
  ]
}

この状態で、Karabinerの設定から、Complex modifications -> Add rule を選ぶと、このような画面になっているのがわかります。まだルールを書いていないので、中身がありません。

練習で、フットスイッチの左(a)に対して、いろいろなアクションを作っていきましょう。

~/.config/karabiner/assets/complex_modifications/footswitch.json
{
  "title": "フットスイッチ",
  "rules": [
   {
      "description": "FS: 左 -> ミッションコントロール",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "a"
          },
          "to": [
            {
              "key_code": "mission_control"
            }
          ],
          "conditions": [
            {
              "type": "device_if",
              "identifiers": [
                {
                  "vendor_id": 3141,
                  "product_id": 29700
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

まずは、単純に左キーに「ミッションコントロールを割り当てる」というものです。ミッションコントロールとは、Macで現在開いている画面を操作するためのやつですね。結構何開いているかわからなくなるので、大事なやつです。
https://support.apple.com/ja-jp/HT204100

基本は配列manipulators にオブジェクトを作り、 from, to で指定していきます。type については、ドキュメント見る限り basic を指定すれば良さそうです。conditions では、この条件が発動する条件を設定します。
conditionstypedevice_if を指定することで、特定のデバイスのみでの条件を指定することができます。ここでは、先程控えた vendor_idproduct_id を指定します。

to については、単一のキーであれば、以下のように配列で指定します。

{
  "to": [
    {
      "key_code": "mission_control"
    }
  ]
}

一方で、複数キーを同時に押下したい場合は、配列になっているので、以下のように指定するわけです。

{
  "to": [
    {
      "key_code": "a"
    },
    {
      "key_code": "b"
    }
  ]
}

ただし、修飾キー (Ctrl, Command, Shift, Fnなど)の場合は、以下のような形で、modifiers を使います。left_shift, left_command のような形で入力し、複数の指定が可能です。Shift+Ctrl+? のような複雑なショートカットを指定することも可能です。

{
  "to": [
    {
      "key_code": "a",
      "modifiers": ["left_shift"]
    }
  ]
}

ファイルを保存するだけではまだ、適用されていません。Karabinerの設定から、Complex modifications -> Add rules に行き、作成したルールを Enable によって適用する必要があります。
もし、この一覧に条件が出ていなかったら、JSONの設定や、置き場所がなにか間違っている可能性があります。

Enable を行うと、Enabled rulesとして、自分で作ったものが登録されているはずです。実際の動作を確認してみましょう。

やりました!

長押し

さらに左を長押しをすることで、デスクトップを表示できるようにしましょう。スクリーンショットを撮ったあとのファイルを確認しに行ったりする場合に便利です。Macデフォルトは、F11なのですが、IDEなどで使えるようにすることを考え、Ctrl+F11に変える設定をしている前提です。

~/.config/karabiner/assets/complex_modifications/footswitch.json
{
  "title": "フットスイッチ",
  "rules": [
    {
      "description": "FS: 左 -> ミッションコントロール + デスクトップ表示",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "a"
          },
          "parameters": {
            "basic.to_if_alone_timeout_milliseconds": 500,
            "basic.to_if_held_down_threshold_milliseconds": 500
          },
          "to_if_alone": [
            {
              "key_code": "mission_control"
            }
          ],
          "to_if_held_down": [
            {
              "key_code": "f11",
              "modifiers": ["left_control"]
            }
          ],
          "conditions": [
            {
              "type": "device_if",
              "identifiers": [
                {
                  "vendor_id": 3141,
                  "product_id": 29700
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

to_if_alone では、 from を押下した時間が、parameters の中にある、basic.to_if_alone_timeout_milliseconds 未満の時間だった時間特定のキーが押下されていた場合に、変換先として使われます。
to_if_held_down では、fromを押下した時間が、parameters の中にある、basic.to_if_held_down_threshold_milliseconds より大きな場合に、変換先として使われます。
この2つを組み合わせつつ、パラメータの値を同じにすることで、すぐに押した場合と、長押しした場合の挙動を変えることができます。

同時押し

左(a)と、右(c)を同時に押した場合には、Mission Controlの右画面へ移動する挙動を入れましょう。ただし、フットスイッチ側の挙動のクセで、このデバイス時代が同時押しには対応してないことがわかりました。
普通のキーボードだと、

  • a: key-down
  • c: key-down
  • a: key-up
  • c: key-up

のような形になり、Karabinerでは、これを

{
  "from": {
    "simultaneous": [
      { "key_code": "a" },
      { "key_code": "c" }
    ]
  }
}

のような形で指定できます。ただし、このフットスイッチは左と右を同時に押下しても

  • a: key-down
  • a: key-up
  • c: key-down
  • c: key-up

のような形でしか認識しないことがわかりました。残念!
この場合、少し複雑にはなりますが、a -> c の順番で押された場合の設定を作っていきます。

~/.config/karabiner/assets/complex_modifications/footswitch.json
{
  "title": "フットスイッチ",
  "rules": [
    {
      "description": "FS: 左関連操作",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "b"
          },
          "to": [
            {
              "key_code": "left_arrow",
              "modifiers": ["left_control"]
            }
          ],
          "conditions": [
            { "type": "variable_if", "name": "fs_left_key", "value": 1 },
            { "type": "device_if", "identifiers": [
              {
                "vendor_id": 3141,
                "product_id": 29700
              }
            ]}
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "c"
          },
          "to": [
            {
              "key_code": "right_arrow",
              "modifiers": ["left_control"]
            }
          ],
          "conditions": [
            { "type": "variable_if", "name": "fs_left_key", "value": 1 },
            { "type": "device_if", "identifiers": [
              {
                "vendor_id": 3141,
                "product_id": 29700
              }
            ]}
          ]
        },
        {
          "type": "basic",
          "from": {
            "key_code": "a"
          },
          "to": [
            { "set_variable": { "name": "fs_left_key", "value": 1 } }
          ],
          "to_delayed_action": {
            "to_if_invoked": [
              { "set_variable": { "name": "fs_left_key", "value": 0 } }
            ],
            "to_if_canceled": [
              { "set_variable": { "name": "fs_left_key", "value": 0 } }
            ]
          },
          "conditions": [
            { "type": "variable_if", "name": "fs_left_key", "value": 0 },
            { "type": "device_if", "identifiers": [
              {
                "vendor_id": 3141,
                "product_id": 29700
              }
            ]}
          ]
        }
      ]
    }
  ]
}

複雑でパズルみたいになってきました。先程の、Mission Ctonrol の割当と互換性がないので、消しています。
これは、左(a)が押下された後に、右(c) が押下された後の動きについて記載しています。

まず、左が押下されると、set_variableで、変数fs_left_key1 を設定します。
to_delayed_action では、一定時間経過後 (basic.to_delayed_action_delay_milliseconds)特定のアクションを起こします。
to_if_invoked では、一定時間たった場合に、実行されるもので、変数 fs_left_key0 にします。
to_if_canceled では、一定時間内に別のキー操作がされた場合に実行されるもので、同じく変数を 0 にします。

さらに、conditionsvariable_if により、fs_left_key1 にっているときには、右(c)の入力を受け付け、Ctrl + → に変換します。

2回押し

これについても、フットスイッチにおける同時押しと同じような対応でできます。左(a)について、同時押しのための変数の設定のために利用されてしまったので、2回押しによってミッションコントロールを実行できるように以下を、先程の manipulators に追加します。

{
  "type": "basic",
  "from": {
    "key_code": "a"
  },
  "parameters": {
    "basic.to_if_alone_timeout_milliseconds": 500,
    "basic.to_if_held_down_threshold_milliseconds": 500
  },
  "to_if_alone": [
    {
      "key_code": "mission_control"
    }
  ],
  "to_if_held_down": [
    {
      "key_code": "f11",
      "modifiers": ["left_control"]
    }
  ],
  "conditions": [
    { "type": "variable_if", "name": "fs_left_key", "value": 1 },
    { "type": "device_if", "identifiers": [
      {
	"vendor_id": 3141,
	"product_id": 29700
      }
    ]}
  ]
}

左→左とやれば、ミッションコントロールとなるし、左→左(長押し)とすれば、デスクトップ表示。
といった動きが完成しました。

割当実用: IntelliJでテスト実行

フットスイッチの左はOSのウインドウ関連の操作などを割り当てるとして、中央、右の操作については、アプリケーションごとの操作を割り当てています。IntelliJでテストを書くときに最も利用するのが、Shift+F10, Shift+Ctrl+F10です。Shift+F10は「実行」で、最後に実行したテストを再度実行したりするために利用します。Shift+Ctrl+F10は現在開いているファイルのカーソルのあたった部分にあるテストコードを実行できたりするものです。

Test Driven Developmentの著者として有名な、ケント・ベック氏は物理的な環境について

必要であれば、他の家具はすべてケチっても、本当に良質の椅子を用意するべきだ。

マンフレッド・ラングはコンピューターのハードウェアにも慎重なリソース割当は適用されると指摘する。

(Kent Beck. Test Driven Development p.140, ピアソン版の日本語訳では p.138)

などと述べていますが、自分はそこに「よいキーボード」「テストを億劫にならずに即座に実行できるボタン」を入れていきたいなと思ってます。TDDでは頻繁にテストを実行することになるので、億劫にならないで素早く実行できるのは大事です。

それでは、IntelliJの場合に限って、中央をShift+Ctrl+F10にマップする設定を入れてみます。

{
  "title": "フットスイッチ",
  "rules": [
    { "description": "FS: 左関連操作", ..省略 },
    {
      "description": "FS IntelliJ: 中央 -> Shift + Ctrl + F10割当",
      "manipulators": [{
        "type": "basic",
        "from": {"key_code": "b"},
        "to": [{
          "key_code": "f10",
          "modifiers": ["left_shift", "left_control"]
        }],
        "conditions": [{
          "type": "device_if",
          "identifiers": [{
            "vendor_id": 3141,
            "product_id": 29700
          }]
        }, {
          "type": "frontmost_application_if",
          "bundle_identifiers": ["^com\\.jetbrains\\.intellij"]
        }]
      }]
    }
  ]
}

今回新しく出てきた要素は、conditionsfrontmost_application_if を指定していることです。これは、特定のアプリケーションにフォーカスがあるときにだけ適用される条件であることを示します。

割当実用: Zoomのミュート

Zoomではマイクや、ビデオのオンオフに対して、ショートカットが割あたっているので、Zoomでビデオ通話をしていた場合は、フットスイッチに、これらの挙動を与えていきましょう。
中央を、マイクのミュート切り替え(Shift+Command+A)、右をビデオの切り替え(Shift+Command+V)を割り当ててみました。

{
  "description": "FS Zoom",
  "manipulators": [{
    "type": "basic",
    "from": {"key_code": "b"},
    "to": [{
      "key_code": "a",
      "modifiers": ["left_shift", "left_command"]
    }],
    "conditions": [{
      "type": "device_if",
      "identifiers": [{
	"vendor_id": 3141,
	"product_id": 29700
      }]
    }, {
      "type": "frontmost_application_if",
      "bundle_identifiers": ["^us\\.zoom\\.xos"]
    }]
  },
  {
    "type": "basic",
    "from": {"key_code": "c"},
    "to": [{
      "key_code": "v",
      "modifiers": ["left_shift", "left_command"]
    }],
    "conditions": [{
      "type": "device_if",
      "identifiers": [{
	  "vendor_id": 3141,
	  "product_id": 29700
      }]
    },
    {
      "type": "frontmost_application_if",
      "bundle_identifiers": ["^us\\.zoom\\.xos"]
    }]
  }]
}

割当実用: Meetでのミュート

Meetは、ブラウザ上で動作するアプリケーションなので、仮にキーを割りてるとしたら、Chrome全体にキーマップが適用されてしまうという課題があります。Chromeには、中央に戻る、右に進むを割り当てておきたいので、ビデオ通話の場合独特の操作がある場合、少し面倒です。

そこでPWAアプリケーションとしてMeetをインストールし、その上で利用するという作戦としましょう。この場合、ChromeブラウザとはBundleIdentifierが別物になるので、そのアプリケーション独自のキーマップが割り当てられるという算段です。

https://meet.google.com/ をChrome等のPWAに対応したブラウザで開くと、アドレスバーの右側にインストールボタンが出てきますので、そこからインストールすると、デスクトップアプリのように .app 拡張子のファイルが作られます。

これにより、com.google.Chrome.app.kjgfgldnnfoeklkmfkjfagphfepbbdan のような形で BundleIdentifier が別なアプリが作れました。

マイクは、Command+D、ビデオのオンオフは Command+E なので、それぞれ割り当てていきます。

{
  "description": "FS Meet",
  "manipulators": [{
    "type": "basic",
    "from": {"key_code": "b"},
    "to": [{
      "key_code": "d",
      "modifiers": ["left_command"]
    }],
    "conditions": [{
      "type": "device_if",
      "identifiers": [{
        "vendor_id": 3141,
	"product_id": 29700
      }]
    }, {
      "type": "frontmost_application_if",
      "bundle_identifiers": ["^com\\.google\\.Chrome\\.app\\.kjgfgldnnfoeklkmfkjfagphfepbbdan$"]
    }]
  }, {
    "type": "basic",
    "from": {"key_code": "c"},
    "to": [{
      "key_code": "e",
      "modifiers": ["left_command"]
    }],
    "conditions": [{
      "type": "device_if",
      "identifiers": [{
	"vendor_id": 3141,
	"product_id": 29700
      }]
    }, {
      "type": "frontmost_application_if",
      "bundle_identifiers": ["^com\\.google\\.Chrome\\.app\\.kjgfgldnnfoeklkmfkjfagphfepbbdan$"]
    }]
  }]
}

おわりに

こんな感じで、足にもキーを割り当てて生活しています。ちょっとだけ楽しくなるかもしれないテクニックの紹介でした。みんなもフットスイッチ、買おう。

さらに、いつかやってみたいこととしては、パワーグローブとか、Miburiみたいなデバイスみたいなのをキーボードとして使い、全身でプログラミング作業を行うことであります。

https://www.youtube.com/watch?v=pqJAo-nFiWI

https://www.youtube.com/watch?v=D_QE20h6wMg

手、足、そして腕!ひざ!筋肉!!心臓!!!脳波!!!!と操作をどんどん拡張していき、最後には老人Zみたいになっちゃうかもしれないですね。そうなったとき、プログラミング作業とは、舞を踊るような、そんな形に昇華するのかもしれません。

自作キーボードにはあまり興味がないのですが、こういう身体の拡張系であればぜひ作ってみたいとか思ってます。次のチャレンジとして、お気軽にやっていくなら、Arduino Microとか使うと、HID(キーボードやマウス)として認識するので、なにかヘンテコなものを作れるかなと思ってます。

明日は、クラウドワークスの社長たる、吉田さんの記事でフィナーレな感じだと思いますので、明日も クラウドワークスAdvent Calendar2021 をよろしくおねがいします。

参考記事

Discussion