Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use org.freedesktop.portal.OpenURI for Providing the AppChooser #1162

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions data/io.elementary.code.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@
</ul>
</description>
<releases>
<release version="6.3.0" date="2022-04-01" urgency="medium">
<description>
<p>Improvements:</p>
<ul>
<li>Use the OpenURI portal for launching external applications</li>
</ul>
<p>Minor updates:</p>
<ul>
<li>Updated translations</li>
</ul>
</description>
</release>
<release version="6.2.0" date="2022-04-01" urgency="medium">
<description>
<p>Improvements:</p>
Expand Down
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ glib_dep = dependency('glib-2.0', version: '>=2.30.0')
gio_unix_dep = dependency('gio-unix-2.0', version: '>=2.20')
gee_dep = dependency('gee-0.8', version: '>=0.8.5')
gtk_dep = dependency('gtk+-3.0', version: '>=3.6.0')
gdk_dep = [ dependency('gdk-x11-3.0'), dependency('gdk-wayland-3.0') ]
granite_dep = dependency('granite', version: '>=6.0.0')
handy_dep = dependency('libhandy-1', version: '>=0.90.0')
gtksourceview_dep = dependency('gtksourceview-4')
Expand All @@ -49,6 +50,7 @@ dependencies = [
gio_unix_dep,
gee_dep,
gtk_dep,
gdk_dep,
granite_dep,
handy_dep,
gtksourceview_dep,
Expand Down
10 changes: 10 additions & 0 deletions src/FolderManager/File.vala
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ namespace Scratch.FolderManager {
}
}

public bool is_writable {
get {
try {
return get_boolean_file_attribute (GLib.FileAttribute.ACCESS_CAN_WRITE);
} catch (GLib.Error e) {
return false;
}
}
}

// returns a list of all children of a directory
private bool children_valid = false;
private Gee.ArrayList <File> _children = new Gee.ArrayList <File> ();
Expand Down
38 changes: 3 additions & 35 deletions src/FolderManager/FileItem.vala
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,8 @@ namespace Scratch.FolderManager {
new_window.open_document (doc, true);
});

var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true);

var files_item_icon = new Gtk.Image.from_gicon (files_appinfo.get_icon (), Gtk.IconSize.MENU);
files_item_icon.pixel_size = 16;

var files_item_grid = new Gtk.Grid ();
files_item_grid.add (files_item_icon);
files_item_grid.add (new Gtk.Label (files_appinfo.get_name ()));

var files_menuitem = new Gtk.MenuItem ();
files_menuitem.add (files_item_grid);
files_menuitem.activate.connect (() => launch_app_with_file (files_appinfo, file.file));
var files_menuitem = new Gtk.MenuItem.with_label (_("File Manager"));
files_menuitem.activate.connect (() => launch_in_file_manager (file));

var other_menuitem = new Gtk.MenuItem.with_label (_("Other Application…"));
other_menuitem.activate.connect (() => show_app_chooser (file));
Expand All @@ -57,6 +47,7 @@ namespace Scratch.FolderManager {
open_in_menu.add (new_window_menuitem);
open_in_menu.add (new Gtk.SeparatorMenuItem ());
}

open_in_menu.add (files_menuitem);

var contractor_menu = new Gtk.Menu ();
Expand All @@ -72,29 +63,6 @@ namespace Scratch.FolderManager {
if (info != null) {
var file_type = info.get_attribute_string (GLib.FileAttribute.STANDARD_CONTENT_TYPE);

List<AppInfo> external_apps = GLib.AppInfo.get_all_for_type (file_type);

foreach (AppInfo app_info in external_apps) {
if (app_info.get_id () == GLib.Application.get_default ().application_id + ".desktop") {
continue;
}

var menuitem_icon = new Gtk.Image.from_gicon (app_info.get_icon (), Gtk.IconSize.MENU);
menuitem_icon.pixel_size = 16;

var menuitem_grid = new Gtk.Grid ();
menuitem_grid.add (menuitem_icon);
menuitem_grid.add (new Gtk.Label (app_info.get_name ()));

var item_app = new Gtk.MenuItem ();
item_app.add (menuitem_grid);

item_app.activate.connect (() => {
launch_app_with_file (app_info, file.file);
});
open_in_menu.add (item_app);
}

try {
var contracts = Granite.Services.ContractorProxy.get_contracts_by_mime (file_type);
foreach (var contract in contracts) {
Expand Down
52 changes: 4 additions & 48 deletions src/FolderManager/FolderItem.vala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ namespace Scratch.FolderManager {
}

public override Gtk.Menu? get_context_menu () {
var open_in_item = new Gtk.MenuItem.with_label (_("Open In…"));
Marukesu marked this conversation as resolved.
Show resolved Hide resolved
open_in_item.activate.connect (() => show_app_chooser (file));

var contractor_menu = new Gtk.Menu ();

GLib.FileInfo info = null;
Expand Down Expand Up @@ -109,7 +112,7 @@ namespace Scratch.FolderManager {
};

var menu = new Gtk.Menu ();
menu.append (create_submenu_for_open_in (info, file_type));
menu.append (open_in_item);
menu.append (contractor_item);
menu.append (new Gtk.SeparatorMenuItem ());
menu.append (create_submenu_for_new ());
Expand All @@ -122,53 +125,6 @@ namespace Scratch.FolderManager {
return menu;
}

protected Gtk.MenuItem create_submenu_for_open_in (GLib.FileInfo? info, string? file_type) {
var other_menuitem = new Gtk.MenuItem.with_label (_("Other Application…"));
other_menuitem.activate.connect (() => show_app_chooser (file));

file_type = file_type ?? "inode/directory";

var open_in_menu = new Gtk.Menu ();

if (info != null) {
List<AppInfo> external_apps = GLib.AppInfo.get_all_for_type (file_type);

string this_id = GLib.Application.get_default ().application_id + ".desktop";

foreach (AppInfo app_info in external_apps) {
if (app_info.get_id () == this_id) {
continue;
}

var menuitem_icon = new Gtk.Image.from_gicon (app_info.get_icon (), Gtk.IconSize.MENU);
menuitem_icon.pixel_size = 16;

var menuitem_grid = new Gtk.Grid ();
menuitem_grid.add (menuitem_icon);
menuitem_grid.add (new Gtk.Label (app_info.get_name ()));

var item_app = new Gtk.MenuItem ();
item_app.add (menuitem_grid);

item_app.activate.connect (() => {
launch_app_with_file (app_info, file.file);
});
open_in_menu.add (item_app);
}
}

if (open_in_menu.get_children ().length () > 0) {
open_in_menu.add (new Gtk.SeparatorMenuItem ());
}

open_in_menu.add (other_menuitem);

var open_in_item = new Gtk.MenuItem.with_label (_("Open In"));
open_in_item.submenu = open_in_menu;

return open_in_item;
}

protected Gtk.MenuItem create_submenu_for_new () {
var new_folder_item = new Gtk.MenuItem.with_label (_("Folder"));
new_folder_item.activate.connect (() => on_add_new (true));
Expand Down
74 changes: 59 additions & 15 deletions src/FolderManager/Item.vala
Original file line number Diff line number Diff line change
Expand Up @@ -76,27 +76,71 @@ namespace Scratch.FolderManager {
}

public void show_app_chooser (File file) {
var dialog = new Gtk.AppChooserDialog (new Gtk.Window (), Gtk.DialogFlags.MODAL, file.file);
dialog.deletable = false;

if (dialog.run () == Gtk.ResponseType.OK) {
var app_info = dialog.get_app_info ();
if (app_info != null) {
launch_app_with_file (app_info, file.file);
var window = (MainWindow) ((Gtk.Application) GLib.Application.get_default ()).active_window;
try {
bool writable = !file.is_valid_directory () && file.is_writable;
var portal = Portal.OpenURI.get ();

var options = new HashTable<string, Variant> (null, null);
options["token_handler"] = Portal.generate_token ();
options["writable"] = writable;
options["ask"] = true;

var fd = Posix.open (file.path, (writable ? Posix.O_RDWR : Posix.O_RDONLY) | Posix.O_CLOEXEC);
if (fd == -1) {
critical ("OpenURI: cannot open file descriptor for '%s'", file.path);
return;
}
}

dialog.destroy ();
window.export.begin ((obj, res) => {
var handle = window.export.end (res);
if (portal.version > 2) {
try {
portal.open_file (handle, new UnixInputStream (fd, true), options);
} catch (Error e) {
warning ("error calling portal: %s", e.message);
}
} else {
warning ("OpenURI: portal version is too old");
}

window.unexport ();
});
} catch (Error e) {
warning ("cannot connect to portal: %s", e.message);
}
}

public void launch_app_with_file (AppInfo app_info, GLib.File file) {
var file_list = new List<GLib.File> ();
file_list.append (file);

public void launch_in_file_manager (File file) {
var window = (MainWindow) ((Gtk.Application) GLib.Application.get_default ()).active_window;
try {
app_info.launch (file_list, null);
var portal = Portal.OpenURI.get ();
var options = new HashTable<string, Variant> (null, null);
options["token_handler"] = Portal.generate_token ();

var fd = Posix.open (file.path, Posix.O_RDONLY | Posix.O_CLOEXEC);
if (fd == -1) {
critical ("OpenURI: cannot open file descriptor for '%s'", file.path);
return;
}

// the OpenDirectory method was added in version 3 of the portal
window.export.begin ((obj, res) => {
try {
var handle = window.export.end (res);
if (portal.version > 2) {
portal.open_directory (handle, new UnixInputStream (fd, true), options);
} else {
portal.open_file (handle, new UnixInputStream (fd, true), options);
}
} catch (Error e) {
warning ("error calling portal: %s", e.message);
}

window.unexport ();
});
} catch (Error e) {
warning (e.message);
warning ("cannot connect to portal: %s", e.message);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/FolderManager/ProjectFolderItem.vala
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ namespace Scratch.FolderManager {
}

public override Gtk.Menu? get_context_menu () {
var open_in_item = new Gtk.MenuItem.with_label (_("Open In…"));
open_in_item.activate.connect (() => show_app_chooser (file));

var close_item = new Gtk.MenuItem.with_label (_("Close Folder"));
close_item.activate.connect (() => {
closed ();
Expand Down Expand Up @@ -145,7 +148,7 @@ namespace Scratch.FolderManager {
}

var menu = new Gtk.Menu ();
menu.append (create_submenu_for_open_in (info, file_type));
menu.append (open_in_item);
menu.append (new Gtk.SeparatorMenuItem ());
menu.append (create_submenu_for_new ());

Expand Down
31 changes: 31 additions & 0 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ namespace Scratch {
// Plugins
private Scratch.Services.PluginsManager plugins;

private bool exported;

// Widgets for Plugins
public Gtk.Notebook bottombar;
public Code.Sidebar sidebar;
Expand Down Expand Up @@ -1030,5 +1032,34 @@ namespace Scratch {

return path;
}

public async string export () {
var window = get_window ();

if (window is Gdk.X11.Window) {
var xid = ((Gdk.X11.Window) window).get_xid ();
return "x11:%x".printf ((uint) xid);
} else if (window is Gdk.Wayland.Window) {
var handle = "wayland:";
((Gdk.Wayland.Window) window).export_handle ((w, h) => {
handle += h;
export.callback ();
});
yield;

exported = handle != "wayland:";
return exported ? handle : "";
} else {
warning ("Unknown windowing system, not exporting window");
return "";
}
}

public void unexport () {
if (exported) {
((Gdk.Wayland.Window) get_window ()).unexport_handle ();
exported = false;
}
}
}
}
49 changes: 49 additions & 0 deletions src/Services/Portal.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2021 elementary, Inc. (https://elementary.io)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace Portal {
const string DESKTOP_BUS_NAME = "org.freedesktop.portal.Desktop";
const string DESKTOP_BUS_PATH = "/org/freedesktop/portal/desktop";
OpenURI? open_uri_portal = null;

public static string generate_token () {
return "%s_%i".printf (
GLib.Application.get_default ().application_id.replace (".", "_"),
Random.int_range (0, int32.MAX)
);
}

[DBus (name = "org.freedesktop.portal.OpenURI")]
interface OpenURI : Object {
[DBus (name = "version")]
public abstract uint32 version { get; }

public static OpenURI @get () throws Error {
if (open_uri_portal == null) {
var connection = GLib.Application.get_default ().get_dbus_connection ();
open_uri_portal = connection.get_proxy_sync<OpenURI> (DESKTOP_BUS_NAME, DESKTOP_BUS_PATH);
}

return open_uri_portal;
}

public abstract ObjectPath open_uri (string parent_window, string uri, HashTable<string, Variant> options) throws DBusError, IOError;
public abstract ObjectPath open_file (string parent_window, UnixInputStream fd, HashTable<string, Variant> options) throws DBusError, IOError;
public abstract ObjectPath open_directory (string parent_window, UnixInputStream fd, HashTable<string, Variant> options) throws DBusError, IOError;
}
}
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ code_files = files(
'Services/GitManager.vala',
'Services/MonitoredRepository.vala',
'Services/PluginManager.vala',
'Services/Portal.vala',
'Services/Settings.vala',
'Services/TemplateManager.vala',
'Widgets/ChooseProjectButton.vala',
Expand Down
Loading