【Tauri】Windowsのウィンドウズをぐるぐるするソフトを作った話
お初にお目にかかります、Rustで自己満足アプリを作るのが趣味なnamniumと申します!普段はQiitaでの投稿が多いのですが、Zennにもお試しで投稿してみたいと思い今回書かせていただきました
今回作成したwin-win-mapは、Windowsのウィンドウ及びマウスカーソルを召喚・移動させるユーティリティアプリです!
リポジトリ: https://github.com/anotherhollow1125/win-win-map
ダウンロードページ: https://github.com/anotherhollow1125/win-win-map/releases
動画の通り、本アプリケーションを使うことでマウスポインタ、ウィンドウをショートカットキー等で特定座標に召喚したり、マップでドラッグすることでウィンドウを動かしたりできます
制作した動機
本アプリを制作した動機は、筆者の作業環境にあります。
筆者は普段ロフトベットの机で作業しています。しかし、たまにはベッドで寝ながら作業したいので、ベッドにもモニタを設置しています。こちらのモニタには、下の階のメインモニタをミラーリングしています。
ベッドでは、常に全モニタが見えているわけではありません。こうなると、ベッドに行くたびに、下の大きいモニタで作業したウィンドウやマウスカーソルを階上モニタに持って行く必要があります。
この作業が手間だったのが本アプリ制作の動機となりました。DIYみたいなノリです。
機能
- ショートカットによりマウスカーソルを設定座標に召喚
- ショートカット及びボタンによりウィンドウを設定座標に召喚
- 閾値外(ベッド上モニタじゃない領域)にあるウィンドウの自動召喚
- マップ上ドラッグによるウィンドウ移動
偏った機能しかありませんが自己満足アプリなのでご愛嬌。要望があればissueを下さると幸いです
自分しか使わないために設定項目も偏っていますが、テキストファイルを編集したりといった面倒な方法を避けています。
構成と使用したWin32API紹介
以前作成したaudio-bookmarkと同様に、Win32API(windows-rs)を利用してウィンドウを動かしています。そのため構成も似通っています。
大まかな制作手順としては
- Rust側だけでウィンドウやマウスカーソルを移動させるCLIアプリケーションをお試しで作り、APIとして盤石化する
- フロント側からRustで作った機能を呼び出すようにしてMUIでGUIを作る
という順に作成しました。
Qiita記事では主にTauri自体の説明をしました(そして疲れました)ので、こちらの記事では本アプリを作成するのに使用したWin32APIをゆる~く列挙していきます。
コード類は解説しないため気になったAPIの使い方は適宜リポジトリの方を参照していただけると幸いです。
イベント監視
SetWinEventHookという関数にコールバックを渡すことで、
- 最前面ウィンドウが変わった時(
EVENT_SYSTEM_FOREGROUND
) - ウィンドウの位置または大きさが変わった時(
EVENT_SYSTEM_MOVESIZEEND
) - ウィンドウが消された時(
EVENT_OBJECT_DESTROY if id_object == OBJID_WINDOW.0
)
を検出し全体更新のために通知しています。
本フックを有効にし続けるには、どこかのスレッドでGetMessageWによるループが必要になります。
Rustのドキュメント
- SetWinEventHook in windows::Win32::UI::Accessibility - Rust
- GetMessageW in windows::Win32::UI::WindowsAndMessaging - Rust
モニタ情報
EnumDisplayMonitorsという関数を使用しました。SetWinEventHook同様、コールバックを渡して各モニタのRECT
ごとに実行してもらう形になります。第4引数を経由して配列のポインタを渡せるので、配列にプッシュさせるように書きました。
Rustのドキュメント
マウス移動
SetCursorPosで座標を指定することで可能でした。シンプル
Rustのドキュメント
プロセス名取得
プロセス名を取得したいだけだったのにも関わらず結構大掛かりになってしまいました。
参考: https://qiita.com/kerupani129/items/e5cb4e6eece9956896a0
流れは以下のような感じです。
- GetWindowThreadProcessIdでウィンドウハンドルからプロセスIDを取得
- OpenProcessにプロセスIDを渡してプロセスハンドルを取得
- QueryFullProcessImageNameWでプロセスハンドルからプロセス名取得
- (Dropトレイトを利用し)CloseHandleでプロセスハンドルを閉じる
Rustのドキュメント
- GetWindowThreadProcessId in windows::Win32::UI::WindowsAndMessaging - Rust
- OpenProcess in windows::Win32::System::Threading - Rust
- QueryFullProcessImageNameW in windows::Win32::System::Threading - Rust
- CloseHandle in windows::Win32::Foundation - Rust
ウィンドウ
基本情報の取得
EnumWindowsは画面上のウィンドウのハンドルを順にコールバックに渡す関数になります。すべて見終わるかコールバックがfalse
を返すまでイテレーションが続きます。
ハンドルに対して、GetWindowInfoでサイズを、GetWindowTextWでウィンドウのタイトルを取得できます。
ついでにコールバック内で前述のプロセス名の取得も行っています。
Rustのドキュメント
- EnumWindows in windows::Win32::UI::WindowsAndMessaging - Rust
- GetWindowInfo in windows::Win32::UI::WindowsAndMessaging - Rust
- GetWindowTextW in windows::Win32::UI::WindowsAndMessaging - Rust
移動・リサイズ
MoveWindow関数にウィンドウハンドルと座標、サイズを渡すことで移動とリサイズが可能です。シンプルです。
ただし絶対にうまくいく関数ではなく、モニタの境界線付近だとウィンドウがバグってマウスで動かせなくなったりもしたので、取り扱いには注意が要るかもしれません。
Rustのドキュメント
前面にあるウィンドウ
GetForegroundWindowで現在前面に出てアクティブとなっているウィンドウのハンドルを取得でき、SetForegroundWindow関数で渡したハンドルのウィンドウが前面になります。
とてもどうでも良いですが"foreground"の"e"忘れがちなので注意です。(n敗)
Rustのドキュメント
- GetForegroundWindow in windows::Win32::UI::WindowsAndMessaging - Rust
- SetForegroundWindow in windows::Win32::UI::WindowsAndMessaging - Rust
現在居る仮想デスクトップ上にあるかの判定
筆者自身が仮想デスクトップを最近使い分けるようになってきたため付けた機能になります。本機能はWin32APIにデフォルトでは準備されていないため、COMオブジェクトを使用する必要があります。COMをwindows-rsから使う方法は次の記事様が詳しいです。
仮想デスクトップ上にあるかの判定にはVirtualDesktopManagerを使用します。幸いインターフェースはWin32APIに定義されているのでそちらを使いました。
IsWindowOnCurrentVirtualDesktopというドンピシャな関数があるので、これにウィンドウハンドルと出力用ポインタを渡して結果を受け取ります。
Rustのドキュメント
参考
本アプリ(win-win-map)に関連したウィンドウの取得
アプリ自体に結びつくウィンドウを取得する必要が出てきたため、関連する関数を使用しました。
GetCurrentThreadIdでアプリのスレッドIDを、EnumThreadWindows関数でスレッドIDに紐づくウィンドウハンドルを取得できるので、その連携でアプリ所有のウィンドウ情報を取得しました。
Rustのドキュメント
- GetCurrentThreadId in windows::Win32::System::Threading - Rust
- EnumThreadWindows in windows::Win32::UI::WindowsAndMessaging - Rust
エラーの取得
全体に共通のものとしてエラーの取得にはGetLastErrorを使用しました。
Rustのドキュメント
おわりに
こちらの記事にはまとめ等は無いので続きを欲する方はQiita記事の方を読んでいただけると幸いです。
ここまで読んでいただきありがとうございました、またZennに記事投稿してみたいです。
Discussion