Skip to content

Hook up file/URI launching to ShellExecute

Sergey Bugaev requested to merge bugaevc/gtk:gtkshowwin32 into main

The idea here is to hook up the higher-level helpers to Windows's native ShellExecuteEx () and SHOpenWithDialog () APIs, which (in my limited understanding) are the moral equivalent of the Freedesktop OpenURI portal (though largely implemented in-process) — high-level system APIs to open the given file or URI in the appropriate app.

The advantages this has over using the win32 implementation of g_app_info_launch_default_for_uri ():

  • the implementation here is fairly simple;
  • it doesn't involve trying to grok the registry for app / file type registrations (at least not inside GLib/GTK side, the implementations of ShellExecuteEx/SHOpenWithDialog presumably do that internally);
  • it doesn't require convoluted formatting / escaping of invocation command lines that GWin32AppInfo / gspawn-win32 has to do otherwise (again, presumably the Windows libraries implement this internally);
  • it's certain to end up opening the file/URI the same way other apps in the system would;
  • it can/will open the native system UI for picking an app in case there are multiple options (or when so requested programmatically with the always-ask flag), or if there is no app installed that can handle the URI scheme / file type;
  • it lets us pass the parent window handle, much like the portal APIs; presumably Windows would use this for positioning the picking UI, or placing the launched app's window;
  • presumably, this will properly elevate privileges with a User Access Control (UAC) prompt if the app being launched requires administrator access; this presumably is impossible with the wspawn* APIs that gspawn-win32 uses;
  • this has a much better chance to work properly with the win32 app isolation (AppContainer) technology.

I have tested this on Windows 10 and 11, with various files and URLs, including files that have whitespace and non-ASCII characters in their names, and specifically characters which don't fit into Windows's own idea of single-byte encoding. g_file_get_path () returns the path encoded in UTF-8 (not the Windows single-byte encoding), and we convert that to UTF-16 and pass to the W versions of Windows APIs, so all the characters are preserved.

I put the whole thing into a background thread and passed SEE_MASK_NOASYNC to ShellExecuteEx () to prevent it from doing its own asynchrony; this felt like the right thing to do.

I put this into GTK instead of GIO because:

  • g_app_info_launch_default_for_uri () is about GAppInfo after all, launching the default app for a file type is one feature of the larger framework of collecting and exposing knowledge about apps and file types;
  • we have to pass the HWND;
  • GTK is also the layer where the xdg-portal backend is hooked up.

That being said, it should be possible to lower this down to GIO if desired.

@lb90 mentions some drawbacks of using ShellExecute () in glib!2760 (comment 1497800). Regarding an error dialog being shown by Windows on errors, that doesn't sound very problematic to me — if anything, perhaps it's actually useful. I have no idea about COM and the implications for COM initialization, but it seems to be working nicely at least in the GTK Demo.

Related to: glib#2088 (closed) glib#292 glib!2760 (merged)

cc @lrn @lb90

Merge request reports