Skip to content

[feat] Windows: a way to get the native window handle on creation #15841

Description

Describe the problem

On Windows there is no supported point at which an application can act on its own window between CreateWindowExW and the moment the window first appears on screen. WebviewWindowBuilder::build() creates the HWND, applies the requested state, shows the window, and only then hands back a WebviewWindow. Every hook Tauri does expose - RunEvent::WindowEvent, on_window_event, the returned handle - is reached after the window has already been shown at least once.

Some launch-time details I needed to apply have to land before the first frame, and none of them can be expressed on the builder. Currently I must resort to using SetWindowsHookExW.

The things I need

Just mentioning for completeness what I encountered:

  1. Exact placement in physical pixels. The builder only takes logical coordinates. tao resolves those to a monitor with a search of its own - the first monitor on which the position, scaled by that monitor's factor, lands. On a mixed-DPI setup that can be a different monitor than the position was derived from, and for a window remembered half off an edge it can be no monitor at all, which falls back to the default cascade position. A window restoring its saved geometry therefore opens in the wrong place. A rectangle in physical pixels has no such ambiguity, but there is no way to pass one.

  2. The caption/taskbar icon. tao gives the window a single icon - the largest image decoded out of icon.ico - which Windows then squeezes into the ~16px caption slot, so the small icon is a badly downscaled 256x256. Applying proper WM_SETICON ICON_SMALL/ICON_BIG images after build() returns means the frame and the taskbar button have already been drawn with tao's.

  3. Activation. We must build with .focused(false): wry calls MoveFocus as the last step of a focused webview build and propagates the result. But Tauri passes that same flag to the window, so tao shows it with SW_SHOWNOACTIVATE and the launch never comes to the foreground. There is no way to say "show the window activated, but do not move focus into the webview": one flag controls both.

  4. maximized: true. As observed with tao 0.35, a window asked to open maximized is maximized while hidden, then shown with SW_SHOWNOACTIVATE - which carries SW_SHOWNORMAL's "most recent size and position" meaning and therefore restores it - and only then maximized again. What the user sees is the window arriving at its restored size and being animated up to fill the screen on every single launch.

Describe the solution you'd like

A creation-time escape hatch on the window builder, plus (ideally) fixes that remove the need to reach for it.

A callback that runs while the window exists but is not yet visible:

WebviewWindowBuilder::new(app, "main", url)
    // Called on the window's own thread after CreateWindowExW has produced the
    // HWND (WM_CREATE would be ideal) and before the window is first shown.
    .on_window_created(|window: &Window| { /* raw HWND available here */ })

Alternatives considered

  • Build hidden, fix up, then show (.visible(false) + set_position + icons + show()). This is the obvious answer and the one we would prefer, but build() also creates the WebView2 environment and controller, which takes the better part of a second. Deferring the first appearance until after build() returns means half a second of nothing on screen after the user launched the app. The window has to be visible during the build, which means its geometry, icon and activation have to be right before the build.
  • SetWindowSubclass after build() returns. Too late for everything above; the window has been shown.
  • RunEvent::WindowEvent / on_window_event / the returned WebviewWindow. All post-show. Correcting position or maximized state from there is visible as a jump or an animation, which is what we are trying to eliminate.

Additional context

Versions: tauri 2.11.5, tauri-runtime-wry 2.11.4, tao 0.35.3, wry 0.55.1, Windows 11.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions