diff --git a/README.md b/README.md index 08d6f02c01..25507132e1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ You'll need the following dependencies: * libgtkspell3-3-dev * libgranite-dev >= 6.0.0 * libhandy-1-dev >= 0.90.0 +* libportal-dev * libpeas-dev * libsoup2.4-dev * libvala-0.34-dev (or higher) diff --git a/io.elementary.code.yml b/io.elementary.code.yml index 29cdbc54c2..6632f0e519 100644 --- a/io.elementary.code.yml +++ b/io.elementary.code.yml @@ -119,6 +119,15 @@ modules: url: https://github.com/universal-ctags/ctags.git tag: p5.9.20201101.0 + - name: libportal + buildsystem: meson + config-opts: + - '-Dgtk_doc=false' + sources: + - type: git + url: https://github.com/flatpak/libportal.git + tag: '0.4' + - name: code buildsystem: meson config-opts: diff --git a/meson.build b/meson.build index f624507a65..a804c83e68 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,7 @@ gee_dep = dependency('gee-0.8', version: '>=0.8.5') gtk_dep = dependency('gtk+-3.0', version: '>=3.6.0') granite_dep = dependency('granite', version: '>=6.0.0') handy_dep = dependency('libhandy-1', version: '>=0.90.0') +libportal_dep = dependency ('libportal') gtksourceview_dep = dependency('gtksourceview-4') peas_dep = dependency('libpeas-1.0') peasgtk_dep = dependency('libpeas-gtk-1.0') @@ -51,6 +52,7 @@ dependencies = [ gtk_dep, granite_dep, handy_dep, + libportal_dep, gtksourceview_dep, peas_dep, peasgtk_dep, diff --git a/src/Application.vala b/src/Application.vala index 98a7980bee..5ef4343b74 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -25,11 +25,13 @@ namespace Scratch { public GLib.Settings settings; public GLib.Settings service_settings; public GLib.Settings privacy_settings; + public bool in_sandbox; public class Application : Gtk.Application { public string app_cmd_name { get { return _app_cmd_name; } } public string data_home_folder_unsaved { get { return _data_home_folder_unsaved; } } public string default_font { get; set; } + public Xdp.Portal portal; private static string _app_cmd_name; private static string _data_home_folder_unsaved; private static bool create_new_tab = false; @@ -67,6 +69,11 @@ namespace Scratch { settings = new GLib.Settings (Constants.PROJECT_NAME + ".settings"); service_settings = new GLib.Settings (Constants.PROJECT_NAME + ".services"); privacy_settings = new GLib.Settings ("org.gnome.desktop.privacy"); + in_sandbox = FileUtils.test ("/.flatpak-info", FileTest.EXISTS); + + if (in_sandbox) { + portal = new Xdp.Portal (); + } GLib.Intl.setlocale (LocaleCategory.ALL, ""); GLib.Intl.bindtextdomain (Constants.GETTEXT_PACKAGE, Constants.LOCALEDIR); diff --git a/src/FolderManager/FileItem.vala b/src/FolderManager/FileItem.vala index 44f862e2cb..fda509e667 100644 --- a/src/FolderManager/FileItem.vala +++ b/src/FolderManager/FileItem.vala @@ -36,18 +36,24 @@ namespace Scratch.FolderManager { new_window.open_document (doc, true); }); - var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); + Gtk.MenuItem files_menuitem = null; - var files_item_icon = new Gtk.Image.from_gicon (files_appinfo.get_icon (), Gtk.IconSize.MENU); - files_item_icon.pixel_size = 16; + if (in_sandbox) { + files_menuitem = new Gtk.MenuItem.with_label (_("File Manager")); + files_menuitem.activate.connect (() => open_on_file_manager (file)); + } else { + var files_appinfo = AppInfo.get_default_for_type ("inode/directory", true); - 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_item_icon = new Gtk.Image.from_gicon (files_appinfo.get_icon (), Gtk.IconSize.MENU); + files_item_icon.pixel_size = 16; - 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_item_grid = new Gtk.Grid (); + files_item_grid.add (files_item_icon); + files_item_grid.add (new Gtk.Label (files_appinfo.get_name ())); + + files_menuitem = new Gtk.MenuItem (); + files_menuitem.add (files_item_grid); + } var other_menuitem = new Gtk.MenuItem.with_label (_("Other Application…")); other_menuitem.activate.connect (() => show_app_chooser (file)); @@ -135,5 +141,18 @@ namespace Scratch.FolderManager { return menu; } + + public void open_on_file_manager (File file) { + var portal = ((Scratch.Application) GLib.Application.get_default ()).portal; + var parent = new Xdp.Gtk3.Parent (((Gtk.Application) GLib.Application.get_default ()).active_window); + + portal.open_directory.begin (parent, file.file.get_uri (), Xdp.OpenUriFlags.NONE, null, (obj, res) => { + try { + portal.open_directory.end (res); + } catch (Error e) { + warning (e.message); + } + }); + } } } diff --git a/src/FolderManager/FolderItem.vala b/src/FolderManager/FolderItem.vala index e2a4c1cbe2..b0d492645e 100644 --- a/src/FolderManager/FolderItem.vala +++ b/src/FolderManager/FolderItem.vala @@ -109,7 +109,7 @@ namespace Scratch.FolderManager { }; var menu = new Gtk.Menu (); - menu.append (create_submenu_for_open_in (info, file_type)); + menu.append (create_open_in_menuitem (info, file_type)); menu.append (contractor_item); menu.append (new Gtk.SeparatorMenuItem ()); menu.append (create_submenu_for_new ()); @@ -122,49 +122,56 @@ 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)); + protected Gtk.MenuItem create_open_in_menuitem (GLib.FileInfo? info, string? file_type) { + Gtk.MenuItem open_in_item = null; - file_type = file_type ?? "inode/directory"; + if (in_sandbox) { + open_in_item = new Gtk.MenuItem.with_label (_("Open In…")); + open_in_item.activate.connect (() => show_app_chooser (file)); + } else { + var other_menuitem = new Gtk.MenuItem.with_label (_("Other Application…")); + other_menuitem.activate.connect (() => show_app_chooser (file)); - var open_in_menu = new Gtk.Menu (); + file_type = file_type ?? "inode/directory"; - if (info != null) { - List external_apps = GLib.AppInfo.get_all_for_type (file_type); + var open_in_menu = new Gtk.Menu (); - string this_id = GLib.Application.get_default ().application_id + ".desktop"; + if (info != null) { + List external_apps = GLib.AppInfo.get_all_for_type (file_type); - foreach (AppInfo app_info in external_apps) { - if (app_info.get_id () == this_id) { - continue; - } + 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_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 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); + 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); + 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 ()); - } + if (open_in_menu.get_children ().length () > 0) { + open_in_menu.add (new Gtk.SeparatorMenuItem ()); + } - open_in_menu.add (other_menuitem); + open_in_menu.add (other_menuitem); - var open_in_item = new Gtk.MenuItem.with_label (_("Open In")); - open_in_item.submenu = open_in_menu; + open_in_item = new Gtk.MenuItem.with_label (_("Open In")); + open_in_item.submenu = open_in_menu; + } return open_in_item; } diff --git a/src/FolderManager/Item.vala b/src/FolderManager/Item.vala index a4169b0000..f638d9573e 100644 --- a/src/FolderManager/Item.vala +++ b/src/FolderManager/Item.vala @@ -76,17 +76,30 @@ 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); + if (in_sandbox) { + var portal = ((Scratch.Application) GLib.Application.get_default ()).portal; + var parent = new Xdp.Gtk3.Parent (((Gtk.Application) GLib.Application.get_default ()).active_window); + + portal.open_uri.begin (parent, file.file.get_uri (), Xdp.OpenUriFlags.ASK, null, (obj, res) => { + try { + portal.open_uri.end (res); + } catch (Error e) { + warning (e.message); + } + }); + } else { + 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); + } } - } - dialog.destroy (); + dialog.destroy (); + } } public void launch_app_with_file (AppInfo app_info, GLib.File file) { diff --git a/src/FolderManager/ProjectFolderItem.vala b/src/FolderManager/ProjectFolderItem.vala index edd216c8ea..22105b8dae 100644 --- a/src/FolderManager/ProjectFolderItem.vala +++ b/src/FolderManager/ProjectFolderItem.vala @@ -140,7 +140,7 @@ namespace Scratch.FolderManager { } var menu = new Gtk.Menu (); - menu.append (create_submenu_for_open_in (info, file_type)); + menu.append (create_open_in_menuitem (info, file_type)); menu.append (new Gtk.SeparatorMenuItem ()); menu.append (create_submenu_for_new ()); diff --git a/vapi/libportal.vapi b/vapi/libportal.vapi new file mode 100644 index 0000000000..a8bf1b5f8e --- /dev/null +++ b/vapi/libportal.vapi @@ -0,0 +1,338 @@ +[CCode (cheader_filename = "libportal/portal.h")] +namespace Xdp { + [Flags] + [CCode (cprefix = "XDP_USER_INFORMATION_FLAG_")] + public enum UserInformationFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_BACKGROUND_FLAG_")] + public enum BackgroundFlags { + NONE, + AUTOSTART, + ACTIVATABLE; + } + + [Flags] + [CCode (cprefix = "XDP_CAMERA_FLAG_")] + public enum CameraFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_EMAIL_FLAG_")] + public enum EmailFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_OPEN_FILE_FLAG_")] + public enum OpenFileFlags { + NONE, + MULTIPLE; + } + + [Flags] + [CCode (cprefix = "XDP_SAVE_FILE_FLAG_")] + public enum SaveFileFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_INHIBIT_FLAG_")] + public enum InhibitFlags { + LOGOUT, + USER_SWITCH, + SUSPEND, + IDLE; + } + + [CCode (cprefix = "XDP_LOGIN_SESSION_")] + public enum LoginSessionState { + RUNNING, + QUERY_END, + ENDING; + } + + [Flags] + [CCode (cprefix = "XDP_SESSION_MONITOR_FLAG_")] + public enum SessionMonitorFlags { + NONE; + } + + public enum LocationAcurracy { + NONE, + COUNTRY, + CITY, + NEIGHBORHOOD, + STREET, + EXACT; + } + + [Flags] + [CCode (cprefix = "XDP_LOCATION_MONITOR_FLAG_")] + public enum LocationMonitorFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_NOTIFICATION_FLAG_")] + public enum NotificationFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_OPEN_URI_FLAG_")] + public enum OpenUriFlags { + NONE, + ASK, + WRITABLE; + } + + [Flags] + [CCode (cprefix = "XDP_PRINT_FLAG_")] + public enum PrintFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_OUTPUT_")] + public enum OutputType { + MONITOR, + WINDOW; + } + + [Flags] + [CCode (cprefix = "XDP_DEVICE_")] + public enum DeviceType { + NONE, + KEYBOARD, + POINTER, + TOUCHSCREEN; + } + + [CCode (cprefix = "XDP_SESSION_")] + public enum SessionType { + SCREENCAST, + REMOTE_DESKTOP; + } + + [CCode (cprefix = "XDP_SESSION_")] + public enum SessionState { + INITIAL, + ACTIVE, + CLOSED; + } + + [Flags] + [CCode (cprefix = "XDP_SCREENCAST_FLAG_")] + public enum ScreencastFlags { + NONE, + MULTIPLE; + } + + [Flags] + [CCode (cprefix = "XDP_REMOTE_DESKTOP_FLAG_")] + public enum RemoteDesktopFlags { + NONE, + MULTIPLE; + } + + [CCode (cprefix = "XDP_BUTTON_")] + public enum ButtonState { + RELEASED, + PRESSED; + } + + [CCode (cprefix = "XDP_AXIS_")] + public enum DiscreteAxis { + HORIZONTAL_SCROLL, + VERTICAL_SCROLL; + } + + [CCode (cprefix = "XDP_KEY_")] + public enum KeyState { + RELEASED, + PRESSED; + } + + [Flags] + [CCode (cprefix = "XDP_SCREENSHOT_FLAG_")] + public enum ScreenshotFlags { + NONE, + INTERACTIVE; + } + + [Flags] + [CCode (cprefix = "XDP_SPAWN_FLAG_")] + public enum SpawnFlags { + NONE, + CLEARENV, + LATEST, + SANDBOX, + NO_NETWORK, + WATCH; + } + + public enum UpdateStatus { + RUNNING, + EMPTY, + DONE, + FAILED; + } + + [Flags] + [CCode (cprefix = "XDP_UPDATE_MONITOR_FLAG_")] + public enum UpdateMonitorFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_UPDATE_INSTALL_FLAG_")] + public enum UpdateInstallFlags { + NONE; + } + + [Flags] + [CCode (cprefix = "XDP_WALLPAPER_FLAG_")] + public enum WallpaperFlags { + NONE, + BACKGROUND, + LOCKSCREEN, + PREVIEW, + BOTH = BACKGROUND | LOCKSCREEN; + } + + public sealed class Portal : GLib.Object { + public Portal (); + + public async GLib.Variant get_user_information (Xdp.Parent? parent, string? reason, Xdp.UserInformationFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async bool request_background (Xdp.Parent? parent, string? reason, Xdp.BackgroundFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public bool is_camera_present (); + public async bool access_camera (Xdp.Parent? parent, string? reason, Xdp.CameraFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public int open_pipeware_remote_for_camera (); + + public async bool compose_email (Xdp.Parent? parent, string[]? addresses, string[]? cc, string[]? bcc, string? subject, string? body, string[]? attachments, Xdp.EmailFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async GLib.Variant open_file (Xdp.Parent? parent, string title, GLib.Variant? filters, GLib.Variant? currrent_filter, GLib.Variant? choices, Xdp.OpenFileFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async GLib.Variant save_file (Xdp.Parent? parent, string title, string? current_name, string? current_folder, string? current_file, GLib.Variant? filters, GLib.Variant? currrent_filter, GLib.Variant? choices, Xdp.SaveFileFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async GLib.Variant save_files (Xdp.Parent? parent, string title, string? current_name, string? current_folder, GLib.Variant files, GLib.Variant? choices, Xdp.SaveFileFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async int session_inhibit (Xdp.Parent? parent, string? reason, Xdp.InhibitFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public void session_uninhibit (int id); + public async bool session_monitor_start (Xdp.Parent? parent, Xdp.SessionMonitorFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public void session_monitor_stop (); + public void session_monitor_query_end_response (); + + public async bool location_monitor_start (Xdp.Parent? parent, uint distance_threshold, uint time_threshold, Xdp.LocationAcurracy accuracy, Xdp.LocationMonitorFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public void location_monitor_stop (); + + public async bool add_notification (string id, GLib.Variant notification, Xdp.NotificationFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public void remove_notification (string id); + + public async bool open_uri (Xdp.Parent? parent, string uri, Xdp.OpenUriFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async bool open_directory (Xdp.Parent? parent, string uri, Xdp.OpenUriFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async GLib.Variant prepare_print (Xdp.Parent? parent, string title, GLib.Variant? settings, GLib.Variant? page_setup, Xdp.PrintFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async bool print_file (Xdp.Parent? parent, string title, uint token, string file, Xdp.PrintFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async Xdp.Session create_screencast_session (Xdp.OutputType outputs, Xdp.ScreencastFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async Xdp.Session create_remote_desktop_session (Xdp.DeviceType devices, Xdp.OutputType outputs, Xdp.RemoteDesktopFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async string? take_screenshot (Xdp.Parent? parent, Xdp.ScreenshotFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public async GLib.Variant pick_color (Xdp.Parent? parent, GLib.Cancellable? cancellable) throws GLib.Error; + + public async Posix.pid_t spaw ( + string cwd, + [CCode (array_null_terminated = true )] + string[] argv, + [CCode (array_lenght_pos = 4.1)] + int[]? fds, + [CCode (array_lenght_pos = 4.1)] + int[]? map_to, + [CCode (array_null_terminated = true)] + string[]? envs, + Xdp.SpawnFlags flags, + [CCode (array_null_terminated = true)] + string[]? sandbox_expose, + [CCode (array_null_terminated = true)] + string[]? sandbox_expose_ro, + GLib.Cancellable? cancellable + ) throws GLib.Error; + public void spaw_signal (Posix.pid_t pid, int @signal, bool to_process_group); + + public async bool trash_file (string path, GLib.Cancellable? cancellable) throws GLib.Error; + + public async bool update_monitor_start (Xdp.UpdateMonitorFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + public void update_monitor_stop (); + public async bool update_install (Xdp.Parent parent, Xdp.UpdateInstallFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public async bool set_wallpaper (Xdp.Parent parent, string uri, Xdp.WallpaperFlags flags, GLib.Cancellable? cancellable) throws GLib.Error; + + public signal void spawn_exited (uint id, uint exit_status); + public signal void session_state_changed (bool screensaver_active, Xdp.LoginSessionState session_state); + public signal void update_available (string running_commit, string local_commit, string remote_commit); + public signal void update_progress (uint n_ops, uint op, uint progress, Xdp.UpdateStatus status, string error, string error_message); + public signal void location_updated (int latitude, int longitude, int altitude, int accuracy, int speed, int heading, int64 timestamp_s, int64 timestamp_ms); + public signal void notification_action_invoked (string id, string action, GLib.Variant? parameter); + } + + [Compact] + public class Parent { + [CCode (has_emitter = false)] + protected Parent (); + } + + public delegate bool ParentExport (Xdp.Parent parent, Xdp.ParentExported callback); + public delegate void ParentExported (Xdp.Parent parent, string handle); + public delegate void ParentUnexport (Xdp.Parent parent); + + [CCode (cheader_filename = "libportal/portal-gtk3.h")] + namespace Gtk3 { + [Compact] + [CCode (cname = "XdpParent", unref_function = "xdp_parent_free")] + public class Parent : Xdp.Parent { + [CCode (cname = "xdp_parent_new_gtk")] + public Parent (Gtk.Window window); + } + } + + [CCode (cheader_filename = "libportal/portal-gtk4.h")] + namespace Gtk4 { + [Compact] + [CCode (cname = "XdpParent", unref_function = "xdp_parent_free")] + public class Parent : Xdp.Parent { + [CCode (cname = "xdp_parent_new_gtk")] + public Parent (Gtk.Window window); + } + } + public sealed class Session : GLib.Object { + public Xdp.SessionType session_type { get; } + public Xdp.SessionState session_state { get; } + public Xdp.DeviceType devices { get; } + public GLib.Variant streams { get; } + + [CCode (has_emitter = false)] + protected Session (); + + public async bool start (Xdp.Parent? parent, GLib.Cancellable? cancellable) throws GLib.Error; + public void close (); + public int open_pipeware_remote (); + + public void pointer_motion (double dx, double dy); + public void pointer_position (uint stream, double x, double y); + public void pointer_button (int button, Xdp.ButtonState state); + public void pointer_axis (bool finish, double dx, double dy); + public void pointer_axis_discrete (Xdp.DiscreteAxis axis, int steps); + + public void keyboard_key (bool keysym, int key, Xdp.KeyState state); + public void touch_down (uint stream, uint slot, double x, double y); + public void touch_position (uint stream, uint slot, double x, double y); + public void touch_up (uint slot); + + public signal void closed (); + } +}