[shotwell: 1/2] Replace nautilus-sendto with org.freedesktop.portal.Email Issue: #111



commit f803e7d2a10450f1ad8cf8d2495678eaa786b90a
Author: Niklas Srich <sirniklax gmail com>
Date:   Mon Feb 21 16:27:34 2022 +0100

    Replace nautilus-sendto with org.freedesktop.portal.Email Issue: #111

 meson.build                 |  1 +
 src/DesktopIntegration.vala | 64 +++++++++++++++++++++++-----------------
 src/Portal.vala             | 71 +++++++++++++++++++++++++++++++++++++++++++++
 src/meson.build             |  3 +-
 vapi/gdk-wayland-3.0.vapi   | 10 +++++++
 5 files changed, 121 insertions(+), 28 deletions(-)
---
diff --git a/meson.build b/meson.build
index 21634319..b3b7abb6 100644
--- a/meson.build
+++ b/meson.build
@@ -69,6 +69,7 @@ champlain = dependency('champlain-0.12', version: '>= 0.12.16')
 champlain_gtk = dependency('champlain-gtk-0.12')
 clutter = dependency('clutter-1.0')
 clutter_gtk = dependency('clutter-gtk-1.0')
+gdk = [ dependency('gdk-x11-3.0'), dependency('gdk-wayland-3.0') ]
  
 webpdemux = dependency('libwebpdemux')
 webp = dependency('libwebp')
diff --git a/src/DesktopIntegration.vala b/src/DesktopIntegration.vala
index 80e8b2ea..124b9a43 100644
--- a/src/DesktopIntegration.vala
+++ b/src/DesktopIntegration.vala
@@ -6,7 +6,6 @@
 
 namespace DesktopIntegration {
 
-private const string SENDTO_EXEC = "nautilus-sendto";
 private const string DESKTOP_SLIDESHOW_XML_FILENAME = "wallpaper.xml";
 
 private int init_count = 0;
@@ -22,8 +21,6 @@ private bool set_screensaver = false;
 public void init() {
     if (init_count++ != 0)
         return;
-    
-    send_to_installed = Environment.find_program_in_path(SENDTO_EXEC) != null;
 }
 
 public void terminate() {
@@ -93,33 +90,46 @@ public bool is_send_to_installed() {
     return send_to_installed;
 }
 
-public void files_send_to(File[] files) {
+public async void files_send_to(File[] files) {
     if (files.length == 0)
         return;
     
-    string[] argv = new string[files.length + 1];
-    argv[0] = SENDTO_EXEC;
-    
-    for (int ctr = 0; ctr < files.length; ctr++)
-        argv[ctr + 1] = files[ctr].get_path();
-    
-    try {
-        AppWindow.get_instance().set_busy_cursor();
-        
-        Pid child_pid;
-        Process.spawn_async(
-            "/",
-            argv,
-            null, // environment
-            SpawnFlags.SEARCH_PATH,
-            null, // child setup
-            out child_pid);
-        
-        AppWindow.get_instance().set_normal_cursor();
-    } catch (Error err) {
-        AppWindow.get_instance().set_normal_cursor();
-        AppWindow.error_message(_("Unable to launch Nautilus Send-To: %s").printf(err.message));
+    var file_names = new StringBuilder();
+    var files_builder = new VariantBuilder (new VariantType ("ah"));
+    var file_descriptors = new UnixFDList ();
+    for (int i=0; i<files.length; i++){
+        var fd = Posix.open (files[i].get_path (), Posix.O_RDONLY | Posix.O_CLOEXEC);
+        if (fd == -1) {
+            warning ("Send to: cannot open file: '%s'", files[i].get_path ());
+            continue;
+        }
+        try {
+            files_builder.add ("h", file_descriptors.append (fd));
+        } catch (Error e) {
+            warning ("Send to: cannot append file %s to file descriptor list: %s",
+            files[i].get_path(), e.message);
+        }
+        file_names.append(files[i].get_basename());
+        if(i<files.length-1){
+            file_names.append(", ");
+        }
+    }
+
+    var options = new HashTable<string, Variant> (str_hash, str_equal);
+    options.insert ("subject", _("Send files per Mail: ") + file_names.str);
+    options.insert ("attachment_fds", files_builder.end());
+    options.insert ("addresses", new Variant ("as", null));
+    AppWindow.get_instance().set_busy_cursor();
+    try{
+        var response = yield Portal.get_instance().compose_email (options, file_descriptors);
+        if (response == null){
+            throw new DBusError.FAILED("Did not get response");
+        }
+    } catch (Error e){
+        AppWindow.error_message(_("Unable to send file %s, %s").printf(
+        file_names.str, e.message));
     }
+    AppWindow.get_instance().set_normal_cursor();
 }
 
 public void send_to(Gee.Collection<MediaSource> media) {
@@ -150,7 +160,7 @@ public void send_to(Gee.Collection<MediaSource> media) {
 
 private void on_send_to_export_completed(Exporter exporter, bool is_cancelled) {
     if (!is_cancelled)
-        files_send_to(exporter.get_exported_files());
+        files_send_to.begin(exporter.get_exported_files());
     
     send_to_exporter = null;
 }
diff --git a/src/Portal.vala b/src/Portal.vala
new file mode 100644
index 00000000..24a98de9
--- /dev/null
+++ b/src/Portal.vala
@@ -0,0 +1,71 @@
+[DBus (name="org.freedesktop.portal.Email")]
+private interface PortalEmail : DBusProxy {
+    [DBus (name = "version")]
+    public abstract uint version { get; }
+}
+
+public class Portal : GLib.Object {
+    private static Portal portal;
+    public static Portal get_instance () {
+        if (portal == null){
+            portal = new Portal ();
+        }
+        return portal;
+    }
+
+    private const string BUS_NAME = "org.freedesktop.portal.Desktop";
+    private const string OBJECT_PATH = "/org/freedesktop/portal/desktop";
+
+    private GLib.DBusConnection bus;
+
+    public async Variant compose_email (HashTable<string, Variant> options,
+      UnixFDList attachments) throws Error{
+        if (bus == null){
+            bus = yield Bus.get(BusType.SESSION);
+        }
+
+        options.insert ("handle_token", Portal.generate_handle());
+
+        var options_builder = new VariantBuilder (VariantType.VARDICT);
+        options.foreach ((key, val) => {
+            options_builder.add ("{sv}", key, val);
+        });
+
+        PortalEmail? email = yield bus.get_proxy(BUS_NAME, OBJECT_PATH);
+
+        var response = email.call_with_unix_fd_list_sync (
+            "ComposeEmail",
+            new Variant ("(sa{sv})", yield Portal.get_parent_window(), options_builder),
+            DBusCallFlags.NONE,
+            -1,
+            attachments
+        );
+        return response;
+    }
+
+    private static string generate_handle () {
+        return "%s_%i".printf (
+            GLib.Application.get_default ().application_id.replace (".", "_"),
+            Random.int_range (0, int32.MAX)
+        );
+    }
+
+    private static async string get_parent_window () {
+        var window = AppWindow.get_instance().get_window ();
+
+        if (window is Gdk.Wayland.Window) {
+            var handle = "wayland:";
+            ((Gdk.Wayland.Window) window).export_handle ((w, h) => {
+                handle += h;
+                get_parent_window.callback ();
+            });
+            yield;
+            return handle;
+        } else if (window is Gdk.X11.Window) {
+            return "x11:%x".printf ((uint) ((Gdk.X11.Window) window).get_xid ());
+        } else {
+            warning ("Could not get parent window");
+            return "";
+        }
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index 7b7e92a4..8b1ab4b5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -38,7 +38,7 @@ endif
 shotwell_deps = [gio, gee, sqlite, gtk, sqlite, posix, gphoto2,
                  gstreamer_pbu, gio_unix, gudev, gexiv2, gmodule, unity,
                  libraw, libexif, sw_plugin, webpdemux, webp, version,
-                 clutter, clutter_gtk, champlain, champlain_gtk]
+                 clutter, clutter_gtk, champlain, champlain_gtk, gdk]
 
 subdir('metadata')
 subdir('publishing')
@@ -220,6 +220,7 @@ executable(
         'MediaPage.vala',
         'MediaDataRepresentation.vala',
         'DesktopIntegration.vala',
+        'Portal.vala',
         'MediaInterfaces.vala',
         'MediaMonitor.vala',
         'PhotoMonitor.vala',
diff --git a/vapi/gdk-wayland-3.0.vapi b/vapi/gdk-wayland-3.0.vapi
new file mode 100644
index 00000000..81e0a25b
--- /dev/null
+++ b/vapi/gdk-wayland-3.0.vapi
@@ -0,0 +1,10 @@
+[CCode (cheader_filename = "gdk/gdkwayland.h")]
+namespace Gdk.Wayland {
+    [CCode (type_id = "GDK_TYPE_WAYLAND_WINDOW", type_check_function = "GDK_IS_WAYLAND_WINDOW")]
+    public class Window : Gdk.Window {
+        public bool export_handle (owned WindowExported callback);
+    }
+
+    [CCode (instance_pos = 2.9)]
+    public delegate void WindowExported (Gdk.Window window, string handle);
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]