rust-windowing / winit

Window handling library in pure Rust
https://docs.rs/winit/
Apache License 2.0
4.81k stars 903 forks source link

Support for activating already running app #3964

Open Enyium opened 21 hours ago

Enyium commented 21 hours ago

Description

To ensure only a single instance of your app can run, you can make use of a crate like single-instance. But an end user expects that, when trying to open the app by running its executable, the app is unconditionally brought into the foreground, no matter whether its relevant window is in the background, arranged, minimized, or hidden, because the app works with a tray icon.

I don't know whether Windows support for such functionality would fit into winit's existing ActivationToken API. In any case, an easy way to implement this functionality on Windows on a low level, is to retrieve a window message via RegisterWindowMessageW() in every app instance using the same app-specific string, and, if the app instance is a follow-up instance, use PostMessageW(HWND_BROADCAST, ...) to request the original app instance to activate its relevant window.

Note: You will know that Windows' SetForegroundWindow() is subject to certain restrictions. In my winit-using Slint app, though, I didn't have any trouble with activating the original app instance's window. The file manager will be the foreground process. Then this from the Microsoft docs will apply: "A process can set the foreground window...only if: ...The calling process was started by the foreground process..." Then, from my anecdotal evidence, Windows seems to allow this follow-up process of my app to transfer the right to successfully call SetForegroundWindow() to my original app instance (maybe because its the same executable path). (Alternatively, there would also be AllowSetForegroundWindow(), which requires retrieving the original app instance's process ID, though.)

However, as far as I can see, winit currently doesn't even support bringing a window into the foreground unconditionally. Window::focus_window() doesn't work for minimized and invisible windows, e.g., and Window::set_visible() probably also doesn't unminimize. Even though at least Windows 10 has a bug regarding arranged windows, there's a way to unconditionally bring a window into the foreground in its correct state: See this code (in another case, I only had success when calling SetForegroundWindow() before ShowWindowAsync(), in a branch where IsWindowVisible() returned true).

I think the functionality of unconditionally bringing a window to the foreground isn't only useful in the context of follow-up app instances, but also, e.g., when your app works with a tray icon, its window was minimized before hiding it in the tray, and the end user wants to show the app's window by clicking on the tray icon.

Somewhat similar issue regarding Wayland: #3633.

Relevant platforms

Windows

kchibisov commented 19 hours ago

Wayland/X11 usually activate with dbus in case of IPC happening, so you pass some token to your main app, and then it acts based on the message you've passed, etc.

So no cross platform thing can not be done, really.

ActivationToken is just a string you pass via IPC to e.g. activate yourself and in some cases you can not really focus with it, like it should be passed to you via some sort of launcher for that to happen, etc.

What you describe sounds like you have IPC + some activation right in the windows API without a need for tokens, etc, since you basically do everything yourself, unlike like on Wayland where you get your token from somewhere else in cases of being activated.