⌨️

キーの同時押し(nミリ秒以内の連続打下)を別のキーに割り当てると快適

2022/12/18に公開

この記事は UEC Advent Calendar 2022 18日目の記事です。

17日目の記事はあずきバーさんの 快適で超高速GAS開発 でした。GAS で TypeScript が使えるのが非常に良いですね。人類には型が必要です。


キーリマッパー

みなさんキーリマッパーは使っていますでしょうか。キーリマッパーとは、あるキー入力を別のキー入力に差し替えるツールです。たとえば CapsLock と Ctrl を入れ替えたり、無変換キーを BackSpace にしたり、変換キーを Enter にしたりできます。Linuxだと xkeysnailxremap が有名だと思います。

上で言ったような単純な入れ替えは結構多くの人がやっている印象がありますが、キーの「同時押し」を別のキーに割り当てているという人はあまり見たことがありません。

同時押しとは

ここでいう「同時押し」とは、Ctrl+C のような、「片方のキーを押しながらもう片方のキーを押す」というやつではありません。「片方のキーを押すのと同時にもう片方のキーを押す」という意味です。もちろん完全な同時というのは存在しないので、正確に言うなら「片方のキーを打下してからnミリ秒以内(例えば50ミリ秒以内)にもう片方のキーを打下する」となります。

同時押しはいいぞ

このような同時押しを検出して別のキー入力に割り当てるというのは、自作日本語配列界隈ではよく行われていることですが、プログラマーの間ではなぜか流行っていません。なので布教したいです。この記事は同時押しの布教をするための記事です。

私が現在設定している同時押しは次の9つです。

  • kl の同時押しで _
  • sd の同時押しで日本語入力切り替え(半角/全角)
  • wi の同時押しで "
  • eo の同時押しで '
  • df の同時押しで (
  • jk の同時押しで [
  • jn の同時押しで ?
  • fv の同時押しで !
  • シフト(SandSを使っているので実際にはスペースキーですが)を押しながら文字キーを押し、そのままシフトを離さずに別の文字キーを押し、それから100ミリ秒以内にシフトを離した場合に、2つ目の文字キーを押す前にシフトを離したことにする設定(何を言っているのかわからないと思いますが、簡単に言うと、THisのように頭文字だけを大文字にしたかったのにシフトを離さないまま次のキーを入力してしまうことを防ぐための設定)

同時押し判定のしきい値は、上の8つは50ミリ秒、最後のやつは100ミリ秒にしています。

最後の1つは無視してもらうとして、前半8個を見ればその魅力がわかると思います。ホームポジションから手を大きく動かすことなく記号類(などのあなたの好きなキー)が打てるんです。良いと思いませんか?

50ミリ秒とは何か

同時押しを「片方のキーを打下してからnミリ秒以内(例えば50ミリ秒以内)にもう片方のキーを打下する」ことだと説明しましたが、なぜこのようなしきい値を設けているのか疑問に思うかもしれません。

これはやってみればわかるのですが、Ctrl や Shift のように「片方のキーを押してからそのキーを上げる前に片方のキーを押す」ことを同時押しと判定してしまうと、同時押しをするつもりがなくても同時押しと判定されてしまう誤発火が頻繁に起きるからです。

意識していないと思いますが、普通人はタイピングをするとき、あるキーを押したあと、そのキーを離す前に既に次のキーを押しています。例えば、「this」と打つ時、t を打下したあと、t を離す前からもう h を打下しています。さすがに一本指でタイピングしているような人には当てはまりませんが、タッチタイピングができる人なら大体当てはまります。

なので、例えば th の同時押しに _ を割り当てる場合、単純な同時押し判定だと this が毎回 _is になってしまいます。同時押しの判定に打下の時間の差をつかっているのはこのためです。

同時押しは万能ではない

しかし、このようなしきい値による同時押し判定をしていても、タイピングが速いと誤発火は発生します。ある程度までならしきい値を調整すれば良いのですが、あまりしきい値を短くしすぎると、逆に同時押しと判定されてほしいところで判定されなかったりするので難しいです。

なので、同時押しによるリマップを設定する際は英単語で横に並ぶことが少ないアルファベットのペアや、キーボード上で物理的に離れているペアを選ぶことが大切です。

例えば、私は最初 we のペアを " に、io ペアを ' に設定していたのですが、どちらもキーボード上で隣り合っており、なおかつ英単語の中でよく隣り合っているので(特に i と o は station や vacation など多くの単語に登場します)、意図せずにしきい値以下の速さで打ってしうことが多かったです。今の設定はそれぞれ wi のペア、eo のペアですが、キーボード上で離れているので、誤発火はめったにしなくなりました。

このように同時押しによるリマップには限界もありますが、それでも良いペアを選べば便利です。

で、どうやって設定するの?

これが一番大事だろ、という話ですが、時間がないのであまり多くのことは書けません。簡単にだけ紹介します。また来年アドカレで書くかもしれません。

Windows

使ってないのでわかりませんが、やまぶきRやDvorakJを使えばできると聞いたことがあります。「蜂蜜小梅配列」や「新下駄配列」などの、同時押しを使う日本語配列の名前で検索すればいい情報が見つかるかもしれません。

Mac

使ってないのでわかりませんが、「蜂蜜小梅配列」や「新下駄配列」などの同上。

(追記) Macでのやり方を教えていただきました:

https://twitter.com/totuus_tweet/status/1604627195129102337?s=20&t=Rzx14gOu7OgmJ_gR-f7McQ

追記ここまで

Linux

のような方法があります。

私のキーリマッパーはちゃんと動くのですが、ほとんどドキュメントを書いていないので使い方がわからないかもしれません。あと設定ファイルという概念がなく、プログラム中に直接設定を書くのでダサいです。まず xkeysnailフォークを試してみて、うまく動かなければ私のキーリマッパーを試すのが良いと思います。

来年のアドカレではxremapに手を加えて同時押しの判定をできるようにしてみた、みたいな記事が書けるといいな(多分忙しくて書けない(などと保険をかけておく))

おまけ: 同時押し判定の仕組み

自分でキーリマッパーを作る人のために、同時押し判定の仕組みをもう少し詳しく説明します。

kl のペアを _ にリマップしていて、しきい値をNミリ秒に設定している状況を例にして考えます。

まず、同時押しに関係ないキー、つまり k l 以外のキーが来た場合は、そのまま素通しします。

k または l が来た場合はそれを差し止め、Nミリ秒が経過してから通します。経過する前にペアとは関係ないキーが押されたときにも通します。Nミリ秒経過する前にペアのもう片方が来た場合にはそれを同時押しとして判定して、_ を送ります。

実装するやり方は色々あると思いますが、私は次のように複数のスレッドを使うやり方でやっています。

まずメインスレッドは、k または l が来た場合はそれを差し止め、Nミリ秒経過後に通知をもらうための通知用スレッドを立てて、そのスレッドに対して「差し止めたキーとそのキーが押された時刻」を送ります。また、自身でも「差し止めたキーとそのキーが押された時刻」を記録しておいて、通知用スレッドからの通知が来たときに、実際にそれが差し止めているキーとして記録されていた場合には、そのキーを放出します。通知が来る前に別のキーが来た場合には、現在待っているキーを放出してからその別のキーを通します。通知が来た際にそれが差し止めているキーとして記録されていなかった場合には、そのキーは既に放出済みであるという意味なので、通知は無視します。

通知用スレッドでは、Nミリ秒待ってからメインスレッドに対して「差し止めたキーとそのキーが押された時刻」をNミリ秒経過した通知として送り返します。

また、同時押し判定とは関係ないですが、日本語入力中に同時押しが発火しないようにする仕組みも欲しくなるかもしれません。

Discussion