Windows10のタッチパッドジェスチャ、最高ですよね!
Windows10の標準機能として、ノートPCのタッチパッドを3本指で右にスワイプすると、フォアグラウンドウィンドウを選択できるという機能があります。
やってることはショートカットキーでAlt+Tabと入力するのと同じなんですが、これがまあ使いやすい!何が使いやすいって、
- ウィンドウは最近使った順に並んでいるので、3本指でスワイプ直後に指を離すことで直前のウィンドウに戻れる
- Alt+Tabではお目当てのウィンドウに至るまで何回もTabキーを押さなきゃいけないところ、非常に高速かつ直感的にウィンドウを選択できる
- 基本的にウィンドウ選択をこのジェスチャで行うようにすれば、タスクバーを限界まで小さくしたり自動的に隠したりしてもウィンドウ選択に困らない
というわけで、非常に便利で愛用しておりました。ところが、これに慣れすぎてしまった結果、「ブラウザとかエディタのタブもこんな感じのタッチパッドジェスチャで切り替えたいなあ……」 という思いが年々増していくのでした。
なので、自分で作りました。タッチパッドジェスチャでタブを切り替えるソフトウェアを!!
TouchpadGestures Advanced
こんな感じで動きます。OSはWindows10のみ対応、ブラウザはFirefoxとChromium系(ChromeとVivaldiは動作確認済)に対応しています。アプリケーション本体はGitHubのリリースページからダウンロードでき、また専用のブラウザアドオンはChrome用がここ、そしてFirefox用がここからインストールできます。
インストール方法
- インストーラーをGitHubのリリースページからダウンロードします。
-
setup.zip
を解凍します。 -
setup.exe
を実行します。.Net Core 3.1ランタイムがPCにインストールされていない場合、管理者権限が必要になります。 - Windowsのデフォルトのタッチパッドジェスチャ設定を変更します。スタートボタンを押し、歯車マークの設定を開き、「デバイス」に行き、「タッチパッド」タブを開き、「関連設定」にある「ジェスチャの詳細な構成」を開き、「三本指ジェスチャの構成」で「上」と「下」を「何もしない」にします。 「右」は「アプリの切り替え」にしておくことをオススメします。
- 専用のブラウザアドオンをChrome用はここ、Firefox用はここからインストールします。
使い方
ブラウザの場合
3本指で下にスワイプすると……
タブ選択画面が出現!3本指を動かして選択、指を離すと確定・タブ遷移
エディタなどの場合
3本指で上下にスワイプすると……
仮想的にCtrl (+ Shift) + Tab を入力しタブ切り替え!
開発中の機能
- 設定画面
- 設定画面を作るのが面倒くさくて未作成なのですが、
%APPDATA%/local/TouchpadGestures_Advanced
内のjsonファイルを書き換えたり追加することで将来的な設定変更を可能にしています。
- 設定画面を作るのが面倒くさくて未作成なのですが、
開発までの道のり
最初はブラウザのタブ選択機能をブラウザのアドオンとして開発しようと試みました。ところが、僕のメインブラウザであるFirefoxではアドオンからタッチパッドの詳細なデータを取得できない ということが判明しました(ChromeはHID APIがあるので取得できるかも)。非常に困った。
そんなとき、ローカルにインストールしたプログラムとブラウザアドオン間で通信するためのNativeMessaging APIなるAPIが存在すると知りました。これを使えばいけるかもしれない……
こうして、ウィンドウ表示及びタッチパッド情報取得を行うメインプログラム、ブラウザとメインプログラム間のインターフェイスとなるサブプログラム、そして専用のブラウザアドオン というソフトウェア構造を考え、実装しました。
ソフトウェアの構造
以上の画像の通り、C#・C++・TypeScriptと3つもの言語を用いる こととなりました。特にメインのプログラムで使用したWPFは未経験のライブラリであったため、学習にかなり時間を費やしました。
さて、なぜローカル側のプログラムが2つ必要だったのか。それは、NativeMessaging APIではブラウザから新規プロセスとしてローカルのプログラムが立ち上がり、ブラウザを閉じるとローカルのプログラムも強制終了される という制約があったからです。どうせならブラウザだけでなくエディタ等のアプリケーションにおいてもタブ切り替えをしたかったし、またChromeとFirefoxの両方とも起動しているような状況にも対応したい。よって、ブラウザとNativeMessagingするサブのプログラム(以下NMC.exe1)はインターフェイスとしての役割に徹し、他にメインのプログラム(以下TGA.exe)を実行しておくというのが最適だろうと判断しました。
タッチパッド詳細データの取得
さて、どうにかしてタッチパッドの詳細データを取得しなくては何も始まらないのですが、これが大変でした。情報が少ない中、Raw Inputとhidpi.hなるガチガチのWindows32 APIを利用すればよいということが分かりました。自プロセスのウィンドウをRaw Inputに登録すると、そのウィンドウのWindowsイベントループにHIDデバイス入力の発生とその入力のIDが通知されるようになり、そのHID入力IDをhidpi.h
内の関数に渡すことで、詳細なHID情報が取得できるというものです。
ところが、TGA.exeはC#で開発されており、C言語ベースであるhidpi.h
内の複雑怪奇なWindows32 APIをPInvokeを利用して叩くのは困難極まります。よって、C++でこれらのAPIを叩いて取得したHID情報をJSONデータにして返してくれる関数を含むDLLを自作し、メインプログラムのC#ではそれをDLL Importして利用することにしました。
プロセス間のデータ受け渡し
まずNMC.exeがブラウザから立ち上げられるたびにTGA.exeを起動させ2、立ち上がったTGA.exeでは他にTGA.exeが立ち上がっているかどうかMutexにより判断し、立ち上がっている場合そのTGA.exeは即終了します。次にNMC.exeは256bit乱数→Base32エンコードにより生成されたKeyと自分のPIDをレジストリに書き込みます。また %APPDATA%/local/TouchpadGestures_Advanced/NMC
ディレクトリにKeyを名前とするフォルダを作成し、その中にTGA.exeに渡す用のJSONファイルとTGA.exeから受け取る用のJSONファイルを生成します。その後NMC.exeは初期化終了を伝えるOSイベントを発行し、それをTGA.exeは購読していて、すぐさまそのNMC.exeを監視下に加えます。
実際のレジストリ。
実際のフォルダ。sending_object.json
がブラウザ→NMC.exe→TGA.exeと渡す用のJSON、for_receiving.json
がTGA.exe→NMC.exe→ブラウザと受け取る用のJSON。
ブラウザからウィンドウ・タブ状況を示すJSONがNMC.exeに送られてくると、NMC.exeはそれを渡す用のJSONファイルに上書きします。TGA.exeではそのJSONファイルの書き込みイベントを監視していて、変更があれば読み取ってJSONをデシリアライズします。同様にTGA.exeではタブ選択データ等を受け取る用JSONファイルに書き込み、NMC.exeはその書き込みイベントを監視していて、変更があればすぐさまブラウザにそれを送信します。
ブラウザからNMC.exeにはJSONファイルの他にも、スクリーンショット画像データ・ファビコン画像データ・ファビコンURL・スクリーンショット撮影エラーなどが送られてきます。画像データはBase64でエンコードされており、これをバイナリにデコードしてフォルダ内に画像ファイルとして生成します。Chromeではファビコンは画像データではなくURLで送られてくるので、仕方なくCurlでデータを取得します。さらにファビコン画像は多種多様な形式が存在し、WPFで表示できる形式ではなかったりもするので、ImageMagickLibraryですべてpngに変換しています。またChromeではスタートページやアドオン管理ページなどがスクリーンショットできない制限があるので、その場合Windows32 APIでChromeウィンドウごとスクリーンショットの撮影を行います。Chrome対応は本当に大変でした。
TGA.exeでは1分に一度、NMC.exeの起動状況をPIDから判別し、すでにプロセスが終了していた場合、 %APPDATA%/local/TouchpadGestures_Advanced/NMC/%KEY%
ディレクトリを消去、レジストリも消去し、監視を終了します。
NativeMessagingの標準入出力通信における再現性のないバグ
NativeMessaging通信では標準入出力で通信を行うのですが、ごくまれにブラウザからのデータの読み取りに失敗し、以降通信が不可能になるというバグが発生しました。このバグは再現性に乏しく、かなり悩まされました。
NativeMessagingでは、ブラウザから送られる標準入力の最初の4バイトが続けて送られてくるデータのバイト数のバイナリとなっています。このバグの原因は、この4バイトの中にASCIIでEOFを示す0x03等の特殊文字が含まれると標準入力の読み取りを終了してしまうというものでした。
解決策は、C言語関数の_setmode( _fileno( stdin ), _O_BINARY )
を一度実行しておくことで、標準入力をバイナリとして扱うというものでした。参考になれば幸いです。
Q. ところでEdgeでタッチパッドジェスチャによるタブ選択、できるよね??
あらかた開発し終わってから気付きました。やられた、と思いました。
-
NativeMessaging_Cliant.exe
の略。どうやらNativeMessagingでは本当はブラウザがCliantでローカル側がHostと呼ばれるらしいですが、その誤りに気づいた頃にはだいぶ開発が進んでいて、名称変更が面倒臭すぎたのでそのままにしています。 ↩ -
Job Objectsという仕組みにより本来ブラウザからNMC.exeが強制終了されると自動的に子プロセスであるTGA.exeも終了させられてしまうのですが、CreateProcessW関数の
dwCreationFlags
引数にCREATE_BREAKAWAY_FROM_JOB
定数を指定することで、これを回避しています。 ↩