[xdg-desktop-portal-gnome/mwleeds/implement-dynamic-launcher] Implement dynamic launcher portal (gtk4)




commit 20078ae55f14e9ac5fb3ac14a38ba64721b3b955
Author: Phaedrus Leeds <mwleeds protonmail com>
Date:   Fri Feb 4 12:33:28 2022 -0800

    Implement dynamic launcher portal (gtk4)

 data/gnome.portal              |   2 +-
 src/dynamic-launcher.c         | 326 +++++++++++++++++++++++++++++++++++++++++
 src/dynamic-launcher.h         |  26 ++++
 src/meson.build                |   2 +
 src/xdg-desktop-portal-gnome.c |   7 +
 5 files changed, 362 insertions(+), 1 deletion(-)
---
diff --git a/data/gnome.portal b/data/gnome.portal
index 38a6f85..1933857 100644
--- a/data/gnome.portal
+++ b/data/gnome.portal
@@ -1,4 +1,4 @@
 [portal]
 DBusName=org.freedesktop.impl.portal.desktop.gnome
-Interfaces=org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Lockdown;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Print;
+Interfaces=org.freedesktop.impl.portal.Account;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;org.freedesktop.impl.portal.Lockdown;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Settings;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.FileChooser;org.freedesktop.impl.portal.Print;org.freedesktop.impl.portal.DynamicLauncher;
 UseIn=gnome
diff --git a/src/dynamic-launcher.c b/src/dynamic-launcher.c
new file mode 100644
index 0000000..ecf1066
--- /dev/null
+++ b/src/dynamic-launcher.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright © 2022 Matthew Leeds
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Matthew Leeds <mwleeds protonmail com>
+ */
+
+#define _GNU_SOURCE 1
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+#include <gio/gunixinputstream.h>
+
+#include "xdg-desktop-portal-dbus.h"
+
+#include "dynamic-launcher.h"
+#include "request.h"
+#include "utils.h"
+#include "externalwindow.h"
+
+#define DEFAULT_ICON_SIZE 192
+
+typedef struct {
+  XdpImplDynamicLauncher *impl;
+  GDBusMethodInvocation *invocation;
+  Request *request;
+  GtkWidget *dialog;
+  ExternalWindow *external_parent;
+
+  int response;
+} InstallDialogHandle;
+
+static void
+install_dialog_handle_free (gpointer data)
+{
+  InstallDialogHandle *handle = data;
+
+  g_clear_object (&handle->external_parent);
+  g_object_unref (handle->request);
+  g_object_unref (handle->dialog);
+
+  g_free (handle);
+}
+
+static void
+install_dialog_handle_close (InstallDialogHandle *handle)
+{
+  gtk_window_destroy (GTK_WINDOW (handle->dialog));
+  install_dialog_handle_free (handle);
+}
+
+static gboolean
+handle_close (XdpImplRequest        *object,
+              GDBusMethodInvocation *invocation,
+              PrintDialogHandle     *handle)
+{
+  GVariantBuilder opt_builder;
+
+  g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
+  xdp_impl_dynamic_launcher_complete_prepare_install (handle->impl,
+                                                      handle->invocation,
+                                                      NULL,
+                                                      2,
+                                                      g_variant_builder_end (&opt_builder));
+  install_dialog_handle_close (handle);
+
+  if (handle->request->exported)
+    request_unexport (handle->request);
+
+  xdp_impl_request_complete_close (object, invocation);
+
+  return TRUE;
+}
+
+static void
+send_prepare_install_response (InstallDialogHandle *handle)
+{
+  GVariantBuilder opt_builder;
+
+  g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
+
+  if (handle->response == 0)
+    {
+      const char *chosen_name = gtk_entry_get_text (GTK_ENTRY (handle->entry));
+      g_variant_builder_add (&opt_builder, "{sv}", "name", g_variant_new_string (chosen_name));
+    }
+
+  if (handle->request->exported)
+    request_unexport (handle->request);
+
+  xdp_impl_dynamic_launcher_complete_prepare_install (handle->impl,
+                                                      handle->invocation,
+                                                      handle->response,
+                                                      g_variant_builder_end (&opt_builder));
+
+  install_dialog_handle_close (handle);
+}
+
+static void
+handle_prepare_install_response (GtkDialog *dialog,
+                                 gint       response,
+                                 gpointer   data)
+{
+  InstallDialogHandle *handle = data;
+
+  switch (response)
+    {
+    default:
+      g_warning ("Unexpected response: %d", response);
+      /* Fall through */
+    case GTK_RESPONSE_DELETE_EVENT:
+      handle->response = 2;
+      break;
+
+    case GTK_RESPONSE_CANCEL:
+      handle->response = 1;
+      break;
+
+    case GTK_RESPONSE_APPLY:
+      preview = TRUE;
+      /* fall thru */
+
+    case GTK_RESPONSE_OK:
+      handle->response = 0;
+      break;
+    }
+
+  send_prepare_install_response (handle);
+}
+
+static gboolean
+handle_prepare_install (XdpImplPrint          *object,
+                        GDBusMethodInvocation *invocation,
+                        GUnixFDList           *fd_list,
+                        const char            *arg_handle,
+                        const char            *arg_app_id,
+                        const char            *arg_parent_window,
+                        const char            *arg_name,
+                        GVariant              *arg_icon_fd,
+                        GVariant              *arg_options)
+{
+  g_autoptr(Request) request = NULL;
+  const char *sender;
+  GtkWidget *dialog;
+  GtkDialogFlags dialog_flags
+  GdkDisplay *display;
+  GdkSurface *surface;
+  ExternalWindow *external_parent = NULL;
+  GtkWidget *fake_parent;
+  const char *url;
+  const char *title;
+  gboolean modal, editable_name, editable_icon;
+  int fd, fd_id;
+  g_autoptr(GInputStream) icon_stream = NULL;
+  g_autoptr(GdkPixbuf) pixbuf = NULL;
+  GtkWidget *entry;
+  InstallDialogHandle *handle;
+
+  sender = g_dbus_method_invocation_get_sender (invocation);
+
+  request = request_new (sender, arg_app_id, arg_handle);
+
+  if (arg_parent_window)
+    {
+      external_parent = create_external_window_from_handle (arg_parent_window);
+      if (!external_parent)
+        g_warning ("Failed to associate portal window with parent window %s",
+                   arg_parent_window);
+    }
+
+  if (external_parent)
+    display = external_window_get_display (external_parent);
+  else
+    display = gdk_display_get_default ();
+
+  fake_parent = g_object_new (GTK_TYPE_WINDOW,
+                              "display", display,
+                              NULL);
+  g_object_ref_sink (fake_parent);
+
+  g_variant_get (arg_icon_fd, "h", &fd_id);
+  fd = g_unix_fd_list_get (fd_list, fd_id, &error);
+  if (fd == -1)
+    {
+      g_dbus_method_invocation_return_gerror (invocation, error);
+      return TRUE;
+    }
+  icon_stream = g_unix_input_stream_new (fd, TRUE);
+
+  if (!g_variant_lookup (arg_options, "url", "&s", &url))
+    url = NULL;
+  if (!g_variant_lookup (arg_options, "modal", "b", &modal))
+    modal = TRUE;
+  if (!g_variant_lookup (arg_options, "editable-name", "b", &editable_name))
+    editable_name = TRUE;
+  if (!g_variant_lookup (arg_options, "editable-icon", "b", &editable_icon))
+    editable_icon = FALSE;
+
+  if (modal)
+    dialog_flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR;
+  else
+    dialog_flags = GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR;
+
+  if (url)
+    title = _("Create Web Application");
+  else
+    title = _("Create Application");
+
+  /* Show dialog with icon, title. */
+  dialog = gtk_dialog_new_with_buttons (title,
+                                        GTK_WINDOW (fake_window),
+                                        dialog_flags,
+                                        _("_Cancel"),
+                                        GTK_RESPONSE_CANCEL,
+                                        _("C_reate"),
+                                        GTK_RESPONSE_OK,
+                                        NULL);
+
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
+  gtk_widget_set_margin_top (box, 15);
+  gtk_widget_set_margin_bottom (box, 15);
+  gtk_widget_set_margin_start (box, 15);
+  gtk_widget_set_margin_end (box, 15);
+  gtk_container_add (GTK_CONTAINER (content_area), box);
+
+  image = gtk_image_new ();
+  gtk_widget_set_vexpand (image, TRUE);
+  gtk_widget_set_size_request (image, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE);
+  gtk_widget_set_margin_bottom (image, 10);
+  gtk_container_add (GTK_CONTAINER (box), image);
+  pixbuf = gdk_pixbuf_new_from_stream_at_scale (icon_stream,
+                                                DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE,
+                                                TRUE, NULL, &error);
+  gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+
+  entry = gtk_entry_new ();
+  gtk_entry_set_text (GTK_ENTRY (entry), arg_name);
+  gtk_widget_set_sensitive (entry, editable_name);
+  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
+  gtk_box_pack_start (GTK_BOX (box), entry, FALSE, TRUE, 0);
+
+  if (url)
+    {
+      g_autofree char *escaped_address = NULL;
+      g_autofree char *markup = NULL;
+      escaped_address = g_markup_escape_text (url, -1);
+      markup = g_strdup_printf ("<small>%s</small>", escaped_address);
+
+      label = gtk_label_new (NULL);
+      gtk_label_set_markup (GTK_LABEL (label), markup);
+      gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
+      gtk_label_set_max_width_chars (GTK_LABEL (label), 40);
+
+      gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0);
+      context = gtk_widget_get_style_context (label);
+      gtk_style_context_add_class (context, "dim-label");
+    }
+
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+  handle = g_new0 (InstallDialogHandle, 1);
+  handle->impl = object;
+  handle->invocation = invocation;
+  handle->request = g_object_ref (request);
+  handle->dialog = g_object_ref (dialog);
+  handle->external_parent = external_parent;
+  handle->entry = entry;
+
+  g_signal_connect (request, "handle-close", G_CALLBACK (handle_close), handle);
+
+  g_signal_connect (dialog, "response", G_CALLBACK (handle_prepare_install_response), handle);
+
+  gtk_widget_realize (dialog);
+
+  surface = gtk_native_get_surface (GTK_NATIVE (dialog));
+  if (external_parent)
+    external_window_set_parent_of (external_parent, surface);
+
+  request_export (request, g_dbus_method_invocation_get_connection (invocation));
+
+  gtk_window_present (GTK_WINDOW (dialog));
+
+  return TRUE;
+}
+
+gboolean
+dynamic_launcher_init (GDBusConnection  *bus,
+                       GError          **error)
+{
+  GDBusInterfaceSkeleton *helper;
+
+  helper = G_DBUS_INTERFACE_SKELETON (xdp_impl_print_skeleton_new ());
+
+  g_signal_connect (helper, "handle-prepare-install", G_CALLBACK (handle_prepare_install), NULL);
+
+  if (!g_dbus_interface_skeleton_export (helper,
+                                         bus,
+                                         DESKTOP_PORTAL_OBJECT_PATH,
+                                         error))
+    return FALSE;
+
+  g_debug ("providing %s", g_dbus_interface_skeleton_get_info (helper)->name);
+
+  return TRUE;
+}
diff --git a/src/dynamic-launcher.h b/src/dynamic-launcher.h
new file mode 100644
index 0000000..a34af0c
--- /dev/null
+++ b/src/dynamic-launcher.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright © 2022 Matthew Leeds
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *       Matthew Leeds <mwleeds protonmail com>
+ */
+
+#pragma once
+
+#include <gio/gio.h>
+
+gboolean dynamic_launcher_init (GDBusConnection  *bus,
+                                GError          **error);
diff --git a/src/meson.build b/src/meson.build
index 2492b32..105056f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -19,6 +19,7 @@ desktop_portal_dbus_interfaces = [
   desktop_portal_interfaces_dir / 'org.freedesktop.impl.portal.Settings.xml',
   desktop_portal_interfaces_dir / 'org.freedesktop.impl.portal.Wallpaper.xml',
   desktop_portal_interfaces_dir / 'org.freedesktop.impl.portal.FileChooser.xml',
+  desktop_portal_interfaces_dir / 'org.freedesktop.impl.portal.DynamicLauncher.xml',
 ]
 
 built_sources = gnome.gdbus_codegen(
@@ -90,6 +91,7 @@ sources = built_sources + files(
   'appchooserdialog.c',
   'background.c',
   'displaystatetracker.c',
+  'dynamic-launcher.c',
   'externalwindow.c',
   'fc-monitor.c',
   'filechooser.c',
diff --git a/src/xdg-desktop-portal-gnome.c b/src/xdg-desktop-portal-gnome.c
index d4f1c0a..f7ef7f9 100644
--- a/src/xdg-desktop-portal-gnome.c
+++ b/src/xdg-desktop-portal-gnome.c
@@ -54,6 +54,7 @@
 #include "request.h"
 #include "settings.h"
 #include "wallpaper.h"
+#include "dynamic-launcher.h"
 
 
 static GMainLoop *loop = NULL;
@@ -170,6 +171,12 @@ on_bus_acquired (GDBusConnection *connection,
       g_warning ("error: %s\n", error->message);
       g_clear_error (&error);
     }
+
+  if (!dynamic_launcher_init (connection, &error))
+    {
+      g_warning ("error: %s\n", error->message);
+      g_clear_error (&error);
+    }
 }
 
 static void


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