🪟

【Tauri】Windowsのウィンドウズをぐるぐるするソフトを作った話

2023/03/23に公開

お初にお目にかかります、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)を利用してウィンドウを動かしています。そのため構成も似通っています。

win-win-map構成

大まかな制作手順としては

  1. Rust側だけでウィンドウやマウスカーソルを移動させるCLIアプリケーションをお試しで作り、APIとして盤石化する
  2. フロント側から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のドキュメント

モニタ情報

EnumDisplayMonitorsという関数を使用しました。SetWinEventHook同様、コールバックを渡して各モニタのRECTごとに実行してもらう形になります。第4引数を経由して配列のポインタを渡せるので、配列にプッシュさせるように書きました。

Rustのドキュメント

マウス移動

SetCursorPosで座標を指定することで可能でした。シンプル

Rustのドキュメント

プロセス名取得

プロセス名を取得したいだけだったのにも関わらず結構大掛かりになってしまいました。

参考: https://qiita.com/kerupani129/items/e5cb4e6eece9956896a0

流れは以下のような感じです。

  1. GetWindowThreadProcessIdでウィンドウハンドルからプロセスIDを取得
  2. OpenProcessにプロセスIDを渡してプロセスハンドルを取得
  3. QueryFullProcessImageNameWでプロセスハンドルからプロセス名取得
  4. (Dropトレイトを利用し)CloseHandleでプロセスハンドルを閉じる

Rustのドキュメント

ウィンドウ

基本情報の取得

EnumWindowsは画面上のウィンドウのハンドルを順にコールバックに渡す関数になります。すべて見終わるかコールバックがfalseを返すまでイテレーションが続きます。

ハンドルに対して、GetWindowInfoでサイズを、GetWindowTextWでウィンドウのタイトルを取得できます。

ついでにコールバック内で前述のプロセス名の取得も行っています。

Rustのドキュメント

移動・リサイズ

MoveWindow関数にウィンドウハンドルと座標、サイズを渡すことで移動とリサイズが可能です。シンプルです。

ただし絶対にうまくいく関数ではなく、モニタの境界線付近だとウィンドウがバグってマウスで動かせなくなったりもしたので、取り扱いには注意が要るかもしれません。

Rustのドキュメント

前面にあるウィンドウ

GetForegroundWindowで現在前面に出てアクティブとなっているウィンドウのハンドルを取得でき、SetForegroundWindow関数で渡したハンドルのウィンドウが前面になります。

とてもどうでも良いですが"foreground"の"e"忘れがちなので注意です。(n敗)

Rustのドキュメント

現在居る仮想デスクトップ上にあるかの判定

筆者自身が仮想デスクトップを最近使い分けるようになってきたため付けた機能になります。本機能はWin32APIにデフォルトでは準備されていないため、COMオブジェクトを使用する必要があります。COMをwindows-rsから使う方法は次の記事様が詳しいです。

https://qiita.com/benki/items/635867b654783da0322f

仮想デスクトップ上にあるかの判定にはVirtualDesktopManagerを使用します。幸いインターフェースはWin32APIに定義されているのでそちらを使いました。

IsWindowOnCurrentVirtualDesktopというドンピシャな関数があるので、これにウィンドウハンドルと出力用ポインタを渡して結果を受け取ります。

Rustのドキュメント

参考

本アプリ(win-win-map)に関連したウィンドウの取得

アプリ自体に結びつくウィンドウを取得する必要が出てきたため、関連する関数を使用しました。

GetCurrentThreadIdでアプリのスレッドIDを、EnumThreadWindows関数でスレッドIDに紐づくウィンドウハンドルを取得できるので、その連携でアプリ所有のウィンドウ情報を取得しました。

Rustのドキュメント

エラーの取得

全体に共通のものとしてエラーの取得にはGetLastErrorを使用しました。

Rustのドキュメント

おわりに

こちらの記事にはまとめ等は無いので続きを欲する方はQiita記事の方を読んでいただけると幸いです。

https://qiita.com/namn1125/items/8ed4d91d3d00af8750f8

ここまで読んでいただきありがとうございました、またZennに記事投稿してみたいです。

Discussion