Aug 01, 2025
4 min read
Rust,
Tauri,

Rust Tauri v2 Desktop App Development: Unconventional Windows and System Permissions Handling

This article delves into the technical details of developing unconventional window applications (such as screenshot tools, floating windows, desktop lyrics, etc.) using Tauri v2 and Rust, including multi-window management, system permission requests (especially macOS ScreenRecording permission), and permission-window association configuration.

I’ve always been curious about how unconventional window applications (screenshot tools, floating windows, desktop lyrics, etc.) are implemented.

Taking Tauri as an example, we need to hide window borders, window shadows, and the taskbar at minimum. It’s best to disable the maximize, minimize, and resizable window functions as well. Then we need to make the content transparent (semi-transparent). Here’s an example record: Tauri 2 Transparent Window Implementation

Multi-window

These unconventional window applications are inevitably multi-window applications because they always need to provide configuration or settings functionality. For multi-window applications, Tauri provides 2 creation methods: one is static, configurable directly in tauri.conf.json. Although the static approach is convenient, it’s too rigid and not user-friendly. Therefore, there’s also a dynamic creation method.

The dynamic approach can be further divided into 2 types: Rust layer creation and frontend layer (js) creation. Essentially, it’s still Rust doing the creation, with the frontend layer being exported.

The implementation is also quite simple:

fn create_setting_window(app: &tauri::AppHandle) {
    tauri::WebviewWindowBuilder::new(
        app,
        "setting",
        tauri::WebviewUrl::App("/settings".parse().unwrap()),
    )
    .title("Settings")
    .position(100.0, 100.0)
    .build()
    .unwrap();
}

We usually call it directly after some event, for example, when clicking a menu to enter settings, we create the settings window:

app.on_menu_event(move |app_handle: &tauri::AppHandle, event| {
	match event.id().0.as_str() {
		"open_settings" => create_setting_window(app_handle),
		"close" => {
			println!("close event");
		}
		_ => {
			println!("unexpected menu event");
		}
	}
});

System Permissions

I haven’t done in-depth development on Windows, so I’m not very familiar. Linux generally doesn’t require permission requests. But macOS is quite different. For example, taking the screen capture function as an example, unless you’re only taking screenshots of your own application itself and the empty desktop, your application cannot get the interface of other applications.

To get system-level screenshots, you must request a permission called ScreenRecording. On the user side, the usage flow might be like this:

  1. Open the application
  2. Detect that there’s no ScreenRecording permission
  3. Click to authorize
  4. Jump to open Settings -> Privacy & Security -> Screen Recording & System Audio -> Turn on the permission item for the relevant application
  5. Restart the application

If you’re developing with macOS’s official framework system, this isn’t difficult. But here we’re discussing Tauri and Rust.

Tauri can use this plugin to handle related permissions: tauri-plugin-macos-permissions. However, this plugin seems to have some bugs and sometimes can’t obtain permissions.

Permissions and Multi-window

Tauri’s permission system has some association with multi-window functionality, meaning its permissions can be allocated per window:

{
"windows": [
    "main",
    "setting"
  ],
  "permissions": [
    "core:default",
    ]
}

Above, 2 windows are defined: one is the main window main, and one is the setting window.

Development Mode

In macOS development mode, how do we quickly obtain system permissions? This is divided into two cases: without GUI and with GUI.

Without GUI, the default is command line, so the terminal is running by default. We just need to add the terminal app to the authorization list according to the authorization steps above.

With GUI, we need to add authorization for the application itself. In development mode, when Rust runs cargo run, it first compiles to generate the target file and then runs it, so we just need to add the target file to the list according to the authorization steps above.