iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🪟

[Tauri] Building a utility to move Windows windows around

に公開

Nice to meet you! I'm namnium, and my hobby is creating personal apps with Rust. I usually post on Qiita, but I wanted to try posting on Zenn as well, so I'm writing this article today.

The win-win-map I created this time is a utility app for summoning and moving Windows windows and mouse cursors!

Caption

Repository: https://github.com/anotherhollow1125/win-win-map
Download page: https://github.com/anotherhollow1125/win-win-map/releases

As seen in the video, this application allows you to summon the mouse pointer or windows to specific coordinates using shortcut keys, or move windows by dragging them on a map.

Motivation

The motivation for creating this app lies in my workspace environment.

Environment

I usually work at a desk under a loft bed. However, sometimes I want to work while lying in bed, so I have a monitor installed there as well. This monitor mirrors the main monitor on the lower level.

When I'm in bed, I can't always see all the monitors. Because of this, every time I move to the bed, I have to bring the windows and mouse cursor I was working with on the large monitor below up to the bedside monitor.

This tedious process was the motivation for building this app. It's like a DIY project mindset.

Features

  • Summon the mouse cursor to set coordinates via shortcut
  • Summon windows to set coordinates via shortcut or button
  • Automatic summoning of windows outside the threshold (regions that are not the bedside monitor)
  • Move windows by dragging on the map

The features are quite specific, but please bear with me as it's a hobby project for my own satisfaction. If you have any requests, I'd appreciate it if you could open an issue.

Since I'm the primary user, the settings are also specialized, but I've avoided tedious methods like editing text files.

Architecture and Win32 APIs Used

Similar to the audio-bookmark I created previously, I use the Win32 API (windows-rs) to move windows. As a result, the architecture is quite similar.

win-win-map architecture

The general development steps were as follows:

  1. Create a prototype CLI application in Rust alone to move windows and the mouse cursor, and solidify it as an API.
  2. Build a GUI with MUI, calling the features developed in Rust from the frontend.

That was the order of development.

In the Qiita article, I mainly focused on explaining Tauri itself (which was quite exhausting). Therefore, in this article, I will casually list the Win32 APIs I used to build this app.

I won't be explaining the code here, so if you're interested in how to use the APIs, please refer to the repository as needed.

Event Monitoring

By passing a callback to the SetWinEventHook function, I detect the following events and notify for a global update:

  • When the foreground window changes (EVENT_SYSTEM_FOREGROUND)
  • When a window's position or size changes (EVENT_SYSTEM_MOVESIZEEND)
  • When a window is destroyed (EVENT_OBJECT_DESTROY if id_object == OBJID_WINDOW.0)

To keep this hook active, a loop using GetMessageW is required in one of the threads.

Rust Documentation:

Monitor Information

I used the EnumDisplayMonitors function. Similar to SetWinEventHook, this involves passing a callback that is executed for each monitor's RECT. Since you can pass an array pointer through the fourth argument, I implemented it to push the data into the array.

Rust Documentation:

Mouse Movement

This was possible by simply specifying coordinates with SetCursorPos. Simple!

Rust Documentation:

Process Name Acquisition

Despite only wanting to get the process name, it ended up being quite a large undertaking.

Reference: https://qiita.com/kerupani129/items/e5cb4e6eece9956896a0

The flow is as follows:

  1. Get the process ID from the window handle with GetWindowThreadProcessId.
  2. Pass the process ID to OpenProcess to get a process handle.
  3. Get the process name from the process handle with QueryFullProcessImageNameW.
  4. (Using the Drop trait) Close the process handle with CloseHandle.

Rust Documentation:

Windows

Basic Information Acquisition

EnumWindows is a function that passes handles of windows on the screen to a callback in sequence. The iteration continues until all windows are processed or the callback returns false.

For the handle, you can get the size with GetWindowInfo and the window title with GetWindowTextW.

Additionally, the process name acquisition mentioned above is also performed within the callback.

Rust Documentation:

Moving and Resizing

By passing the window handle, coordinates, and size to the MoveWindow function, you can move and resize windows. It's quite simple.

However, it's not a function that works perfectly every time. For instance, when a window is near monitor boundaries, it occasionally glitches and becomes unmovable with a mouse, so it might require careful handling.

Rust Documentation:

Foreground Window

You can get the handle of the window that is currently in the foreground and active with GetForegroundWindow. Conversely, the SetForegroundWindow function brings the window associated with the passed handle to the foreground.

On a side note, it's very easy to forget the "e" in "foreground" (speaking from many failed attempts).

Rust Documentation:

Determining if a window is on the current Virtual Desktop

This is a feature I added because I've recently started using multiple virtual desktops. Since this functionality is not provided by default in the Win32 API, you need to use COM objects. For details on using COM from windows-rs, the following article is very helpful:

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

I used VirtualDesktopManager to determine if a window is on the current virtual desktop. Fortunately, the interface is defined in the Win32 API, so I used that.

There is a perfectly suited function called IsWindowOnCurrentVirtualDesktop. You pass the window handle and an output pointer to it to receive the result.

Rust Documentation:

References:

I used related functions because I needed to retrieve the windows associated with the application itself.

By using GetCurrentThreadId to get the thread ID of the app and the EnumThreadWindows function to get the window handles linked to that thread ID, I was able to retrieve information about the windows owned by the application.

Rust Documentation:

Error Acquisition

I used GetLastError for error acquisition as a common practice throughout the application.

Rust Documentation:

Closing

This article doesn't have a summary, so if you'd like to read more, please check out the Qiita article.

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

Thank you for reading this far. I look forward to posting on Zenn again.

Discussion