US配列HHKBライクなカーソル操作のショートカットをmacで自作する

この記事は、フラー株式会社 Advent Calendar 2021の2日目の記事です。 (昨年のAdvent Calenderのためにブログを開設したものの、一年間放置してしまいました... ) qiita.com

昨日は@chooblarinさんの「今年のJリーグのBar Chart Raceをつくった」でした。 Bar Chart Raceは学生さんたちに人気らしいです。Youtubeとかでよく動画になってるからでしょうか? 見てると途中で頭の中にユーロビートが流れ出します。

US配列のHHKBのカーソル操作を他のキーボードでも実現したい

職場でのタイピングにはUS配列のHappy Hacking Keyborad (HHKB)を使用しています。HHKBの感想やレビューはネット上に山ほどあるので特に書きませんが、HHKBはUS配列だとカーソルキーがないんです

f:id:kanterbury:20211128211102j:plain
US配列のHHKB(上記リンク、PFU 公式通販より引用)
じゃあどうやってカーソルを操作するのかというと、fnキーを押しながら、[, ;, ', /のいずれかを押すことで、対応する方向にカーソルが動きます。 これだけだと、どういうことか分かりづらいので、図示すると以下のようになります。
f:id:kanterbury:20211129185113p:plain
fnキーを押しながらだと、各方向へのカーソル操作ができるようになります。
このカーソル操作、初めは戸惑いますが、慣れるとかなり快適になります。 カーソルキーってホームポジションからやや離れた位置にあるので、通常のキー配置だとカーソル操作のために右手の移動が必要になりますが、HHKBのこの方式だと文字のタイピング中でも小指がfnキーに届くので、ほとんど右手を移動させることなく、タイピングができるようになります。

でも、これに慣れると逆に困るのが

他のキーボードでカーソルキーを押すのが面倒になる

ということです。

なので、他のキーボードでも、何かしらのキーと[, ;, ', /(以下、記号キー)の同時押しでカーソルを操作するショートカットを実現できるようにしました。

なお、今回は主にMac OSでの実現方法について説明します。Windows OSでの実現方法についても、最後に少しだけ触れます。

Karabiner-Elementsに自作ショートカットを追加する

目的のショートカットを実現するために、Macキーバインド設定ができるKarabiner-Elements(以下、Karabiner)を使用します。 使用している方も多いと思いますし、解説記事もたくさんあるので、インストール方法は割愛します。

Karabinerでは、単純なキー割り当てを変更するだけでなく、有志の方が作成したショートカットコマンドが公式サイトに公開されていて、簡単にインポートできるようになっています。

さらに、有志の方が作っているようなショートカットを自作することもできます。 この自作機能を使って、目的のショートカットを実現します。

どういうショートカットにするのか

今回は、右シフト + 記号キーで各方向のカーソル操作ができるようにします。

なぜ右シフトを採用したのかというと、以下の理由になります。

  • HHKBのFnキーが右シフトのすぐ隣にあるので、位置が似ている
  • 右シフトはほとんどのキーボードにありそうだし、位置も変わらなさそう
  • 既存のショートカットとバッティングしても、左シフトがあるから既存のショートカットが使えなくなることがない

commandキーやfnキーも候補に上がりましたが、前者はVScodeなどで/との同時押しでコメントアウトのショートカットに使われており、後者はキーボードによって配置位置が違うため、落選となりました。

自作ショートカット用ファイルを作成する

適当なテキストエディタで以下のJSONファイルを作成します。名前は適当でいいですが、今回はright_shift_arrow_shortcut.jsonとします。 同一のファイルをGithubにも上げました。 https://github.com/kanterbury/karabiner_custom_modifications/blob/main/right_shift_arrow_shortcut.json

{
  "title": "Right Shift Arrow Shortcut",
  "rules": [
    {
      "description": "Right Shift + [ to Up",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "open_bracket",
            "modifiers": {
              "mandatory": ["right_shift"],
              "optional": ["left_shift"]
            }
          },
          "to": [
            {
              "key_code": "up_arrow"
            }
          ]
        }
      ]
    },
    {
      "description": "Right Shift + / to Down",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "slash",
            "modifiers": {
              "mandatory": ["right_shift"],
              "optional": ["left_shift"]
            }
          },
          "to": [
            {
              "key_code": "down_arrow"
            }
          ]
        }
      ]
    },
    {
      "description": "Right Shift + ; to Left",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "semicolon",
            "modifiers": {
              "mandatory": ["right_shift"],
              "optional": ["left_shift"]
            }
          },
          "to": [
            {
              "key_code": "left_arrow"
            }
          ]
        }
      ]
    },
    {
      "description": "Right Shift + ' to Right",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "quote",
            "modifiers": {
              "mandatory": ["right_shift"],
              "optional": ["left_shift"]
            }
          },
          "to": [
            {
              "key_code": "right_arrow"
            }
          ]
        }
      ]
    }
  ]
}

rulesの配列に各ショートカットを登録していくことになります。 fromに実際に押すキー操作のルールを、toの割り当てたいキー操作(つまり、方向キー)のルールを記載します。 今回のポイントはfromのほうです。

key_codeに押す英字や記号のキーを登録します。今回は各記号キーが相当します。

modifierskey_codeで登録したキーと同時押しする装飾キーを登録します。登録方法は2種類あります。

mandatory

必ず押すキーを登録します。今回は右シフトキーが相当します。

optional

同時に押すことを可能とするキーを登録します。 optionalに登録したキーも同時に押している場合、変換後のキーとoptionalのキーが同時に押されたように処理されます。

今回の場合は、左シフトキーを登録しています。これは、Shift + カーソル操作によるテキスト選択を自作したショートカットでも実現するためです。 例えば、右シフト + [に相当しますが、optionalである左シフトも同時押しすると右シフト + [ + 左シフト↑ + 左シフトと認識されるようになり、カーソルを上に動かしながらのテキスト選択という操作になります。

作成したルールをkarabiner-Elementにインポートする

作成したJSONファイルを以下のリポジトリにコピーします

/Users/[ユーザ名]/.config/karabiner/assets/complex_modifications

その後、Karabiner-Elementsの設定画面にて、Complex modifications → Rules → Add ruleを選択します。

f:id:kanterbury:20211129212218p:plain

別ウィンドウが開くので、その中から先ほど追加したルール群Right Shift Arrow Shortcutを見つけて、Enable Allをクリック。

f:id:kanterbury:20211129212335p:plain

これで、独自ショートカットの登録が完了です。右シフト + 記号キーでカーソル移動ができるようになりました!

使ってみて

右シフトの押下がやや重い

今回のショートカットの場合、私は右シフトを右手の小指で押下していますが、fnキーに比べると大きいのでやや重みを感じます。

もっといい組み合わせがありかもしれない

カーソル移動のショートカットを考えだすと、今回の右シフト + 記号キーよりもいいかもと思える組み合わせが浮かんできます。 私は右シフトを通常のタイピングで全くと言っていいほど使わないので、右シフト + WASDでゲームライクなカーソル移動にするのも良さそうです。

備え付けのカーソルキーを使用してしまう

ここまで書いてそりゃないぜという感じもありますが、使っているキーボードにカーソルキーがついているとどうしてもそちらを使用してしまいます。HHKBを使っている時は、何も意識せずにショートカットを使用しているのに...

Windowsでも同様のショートカットを適用したい

私はMicrosoft PowerToysというツールを使っています。 Microsoftが開発しているユーティリティツールで、ショートカットやキーバインド設定だけでなく、ウィンドウの並列配置などもサポートしてくれて、いろいろ遊べそうです。

github.com

まとめ

今回は、Karabinerへの独自ショートカットの追加機能を行って、カーソル操作のショートカットを自作してみました。 意気揚々と作ってみたはいいものの、使ってみると思ったほどでもないっていうのは、効率化あるあるだと思います。 でも、ショートカットが自作できるというのは、何かと応用が効いて便利なはずです。

皆さんの考えた最強の独自ショートカットをぜひ教えてほしいです!

参考

complex_modifications manipulator definition | Karabiner-Elements

Karabiner-Elementsの独自ルールの作成方法 – Webrandum

概念や理論を学ぶと技術が理解しやすくなるという話

この記事はフラー Advent Calendar 2020における9日目の記事です。

8日目の記事はいのりこさんのBlock Kitを用いたSlack通知をGoで実装してみるでした。 定期的な癒しの提供は、なによりも優先されるべき事項だと思います。

概要

今年9月にフラー株式会社に入社し、Webフロントエンジニアとして、React + TypeScriptで開発を行っています。 入社後一ヶ月のオンボーディング期間に、Reduxの学習を行いました。 その中で、概念や理論を学ぶことで技術の理解がしやすくなるということを体験したので共有します。

よくわからなかったけど動かせていたRedux

Reduxについては、入社前に独学でReactを学んでいた時に読んでいた書籍で使用していたため、触れてはいました。 しかし、「Reduxとは」といった概念についての説明が乏しく、「こう書けば動く」ということがメインでした。 当時はあまり理解しておらず、「ReduxってReducerとかDispatchとか登場する要素が多くてよくわかんないなぁ」と思っていましたが、書籍内の例を真似して書けば動かせるからいいやと思っていました。

Reduxの基本概念

オンボーディング期間で、Reduxの公式チュートリアルを一通り進めていきました。 その冒頭でのReduxの概念とコンセプトについての説明があり、Reduxを組み込むとアプリケーションがどういう振る舞いをするのかがとてもわかりやすかったです。 f:id:kanterbury:20201208110310p:plain (出典:Redux Essentials, Part 1: Redux Overview and Concepts)

  • Stateがアプリケーションにおける特定の時点の状態を表す。
  • Viewが現時点でのStateに基づいてUIを描画する。
  • ユーザーの操作に応じてActionが発行される。現在のStateとActionの内容によって、Stateが更新される。
  • Stateが更新されたことによって、ViewがUIを更新する。

State, View, Actionがサイクルを描くのが、Reduxの基本的な動き方です。

immutability

Reduxの公式チュートリアルには以下の様に記載されています。(引用元

Redux expects that all state updates are done immutably.

「Reduxはすべての状態の更新が不変に行われることを期待しています」

Google翻訳

ここで語られているimmutabilityとはどういったものなのか、 公式チュートリアルでも紹介している以下のサイトにて勉強しました。

Immutability in React and Redux: The Complete Guide

immutabilityはオブジェクトが持つ性質を指します。オブジェクトがimmutableであるということは、そのオブジェクトは生成された後に値を変更されないことを指します。 JavaScriptはmutableな言語なので、生成されたオブジェクトの値を変更することが可能です。ですが、immutableに更新を行うことができます。

immutableじゃない例

addAgeは第一引数のオブジェクトに第二引数の数値をAgeプロパティとして追加したオブジェクトを返す関数ですが、引数として渡されたオブジェクトにプロパティの追加という変更が生じているのでimmutableではないです。

ちなみに、既存のオブジェクトに変化が生じることを副作用といいます。

let person = {
    name: "kanterbury",
    bloodType: "A"
}

// personオブジェクトに年齢を追加する関数
function addAge(person, num) {
    person.age = num // 既存のオブジェクトに変化が生じてしまっている
    return person
}

person = addAge(person, 18)

immutableな例

一方、こちらのaddAgeでは、引数のpersonオブジェクトにageプロパティを追加したオブジェクトnewPersonを生成し、それを返り値としています。 既存のpersonはあくまでコピー元として使用されただけなので、変化はありません。 personはaddAgeの返り値であるageが追加されたオブジェクトに置き換えられるので、immutableに更新が行われています。

let person = {
    name: "kanterbury",
    bloodType: "A" 
}

function addAge(person, num) {
    let newPerson = { //引数のpersonを元に、新しいオブジェクトを生成
        ...person,
        age: num
    }
    return newPerson
}

person = addAge(person, 18) //addAgeで生成された新しいオブジェクトに置き換える

この例のように、Reduxでも、アプリの状態を表すStateを更新する場合は、Stateの値を変更するのではなく、新しいStateを生成し、それに置き換えるという操作を行います。

上記の二点を踏まえると

ここまで説明したReduxのサイクル、そして、状態の更新はimmutableに行うことを踏まえると、ReduxがReducerやDispatchなどに分かれているのは、「状態更新の不変性を保った上で、サイクルを実現するため」だということが理解できました。

  • Action Creater: ユーザーの操作や入力に応じたActionを生成する。
  • Reducer: 現在のStateとActionの内容から、次のStateを生成する。現在のStateの内容を変更するのではなく、新しいStateを生成するのが役割。
  • Dispatch: Action Createrで生成されたActionをReducerに渡して、新しいStateを生成させる。Stateは生成された新しいものに置き換えられ、更新される。

まとめ

結局なにが言いたいのかというと、技術を学ぶ際には、その技術の概念、つまり、どんなことをどのように実現しようとしているのかを理解することが肝要ということです。

概念を理解すると、

→ 技術の構成要素それぞれにどういった役割があるのかがわかる

→ 役割がわかると、なにをさせればいいのかわかる

→ なにをさせればいいかわかると、どうコードを書けばいいのかわかるようになる

ということを身をもって体感しました。

実際の開発においても、既存のコードの真似をすればある程度動くものは書くことができますが、きちんと概念を理解した上で使用していきたいと思います。