OpenURI portal always presenting app chooser

In a GTK application, I’m using Gtk.FileLauncher to open (launch) a file, since that’s the modern & recommended API to do that. I’m seeing that an app chooser dialog (“Choose an app to open the file”) is presented to the user every time, instead of the default application automatically getting launched.

This can be easily reproduced in the GTK 4 Demo app at the “Pickers and Launchers” demo page.

As a user, I would of course expect e.g. image files to get directly opened in Image Viewer (Loupe) without me having to confirm every time. And as an app developer, that is also the experience I want my users to have. If I use a more classic API like Gio.AppInfo.launch_default_for_uri() or Gtk.show_uri(), that is the behavior I get. This is giving me an incentive to stay on these older APIs.

By peeking under the hood, I see that GTK deicdes to use the OpenURI portal from xdg-desktop-portal, whose implementation only allows a URL to be opened without presenting the explicit app chooser dialog in some specific cases (such as http links), but not generally for opening files. Moreover, this sounds like it was an intentional choice. It sounds like this might have had something to do with security concerns.

However, if we want Gtk.FileLauncher to be the recommended way of launching files going forward in our platform (for sandboxed and unsandboxed apps alike), surely we wouldn’t want users encountering these dialogs each and every time?

1 Like

Thanks for the tip. I remember seeing appchooser got deprecated and thought that was that. So I tested the new filelauncher now and it seems to set always-ask false as default but with some file formats you have to choose four times before the default app opens directly.

import sys, gi
gi.require_version('Gtk', '4.0')
from gi.repository import Gtk
gi.require_version('Gdk', '4.0')
from gi.repository import Gio

class Main:
  def __init__(self, app):
    self.mainwin = Gtk.ApplicationWindow.new(app)
    self.headerbar = Gtk.HeaderBar.new()
    self.mainwin.set_titlebar(self.headerbar)
    button = Gtk.Button.new_from_icon_name("open-menu")
    button.set_action_name("win.open-menu")
    action = Gio.SimpleAction.new("open-menu", None)
    action.connect("activate", self.menu_open)
    self.mainwin.add_action(action)
    self.menu = Gio.Menu.new()
    self.popover_menu = Gtk.PopoverMenu.new_from_model(self.menu)
    self.popover_menu.set_parent(button)
    self.headerbar.pack_end(button)
    action = Gio.SimpleAction.new("open-file", None)
    action.connect("activate", self.open_file)
    self.mainwin.add_action(action)
    self.menu.append("Open File", "win.open-file")
    self.file_dialog = Gtk.FileDialog.new()
    self.file_launcher = Gtk.FileLauncher.new()
    self.mainwin.set_default_size(600, 400)
    self.mainwin.present()

  def menu_open(self, _action, _data):
    self.popover_menu.popup()

  def open_file(self, _action, _data):
    self.file_dialog.open(self.mainwin, None, self.open_file_finish)

  def open_file_finish(self, _dialog, _task):
    if not _task.had_error():
      gfile = Gio.File.new_for_path(_dialog.open_finish(_task).get_path())
      self.file_launcher.set_file(gfile)
      self.file_launcher.launch(self.mainwin, None, None)

app = Gtk.Application()
app.connect('activate', Main)
app.run(None)