summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2023-02-26 21:25:26 +0000
committerMatthias Clasen <mclasen@redhat.com>2023-02-26 21:25:26 +0000
commita1f0d6428711f89e6846943485014c93ee22129d (patch)
tree9720d87571fdab048be965913825ccccdf1a57e4
parentbae6a37ed9613136a5519637fdfa4c2fdb19a243 (diff)
parent8562476872ae4568b85cc616269750a5b803f33a (diff)
downloadgtk+-a1f0d6428711f89e6846943485014c93ee22129d.tar.gz
Merge branch 'file-transfer-portal-gtk3' into 'gtk-3-24'
selection: Support the file transfer portal See merge request GNOME/gtk!5554
-rw-r--r--NEWS3
-rw-r--r--gtk/filetransferportal.c498
-rw-r--r--gtk/filetransferportalprivate.h49
-rw-r--r--gtk/gtkselection.c111
-rw-r--r--gtk/meson.build4
-rw-r--r--tests/meson.build3
-rw-r--r--tests/testfileportal.c130
7 files changed, 795 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index 58b073a096..c424b6b258 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,9 @@
Overview of Changes in GTK+ 3.24.37, xx-xx-xxxx
===============================================
+* Support the file transfer portal for copy-paste and DND
+
+
Overview of Changes in GTK+ 3.24.36, 12-22-2022
===============================================
diff --git a/gtk/filetransferportal.c b/gtk/filetransferportal.c
new file mode 100644
index 0000000000..30afaeed97
--- /dev/null
+++ b/gtk/filetransferportal.c
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2018 Matthias Clasen
+ *
+ * This library 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/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gio/gio.h>
+
+#ifdef G_OS_UNIX
+
+#include <gio/gunixfdlist.h>
+
+#ifndef O_PATH
+#define O_PATH 0
+#endif
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#else
+#define HAVE_O_CLOEXEC 1
+#endif
+
+#include "filetransferportalprivate.h"
+
+static GDBusProxy *file_transfer_proxy = NULL;
+
+typedef struct {
+ GTask *task;
+ char **files;
+ int len;
+ int start;
+} AddFileData;
+
+static void
+free_add_file_data (gpointer data)
+{
+ AddFileData *afd = data;
+
+ g_object_unref (afd->task);
+ g_free (afd->files);
+ g_free (afd);
+}
+
+static void add_files (GDBusProxy *proxy,
+ AddFileData *afd);
+
+static void
+add_files_done (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+ AddFileData *afd = data;
+ GError *error = NULL;
+ GVariant *ret;
+
+ ret = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, result, &error);
+ if (ret == NULL)
+ {
+ g_task_return_error (afd->task, error);
+ free_add_file_data (afd);
+ return;
+ }
+
+ g_variant_unref (ret);
+
+ if (afd->start >= afd->len)
+ {
+ g_task_return_boolean (afd->task, TRUE);
+ free_add_file_data (afd);
+ return;
+ }
+
+ add_files (proxy, afd);
+}
+
+/* We call AddFiles in chunks of 16 to avoid running into
+ * the per-message fd limit of the bus.
+ */
+static void
+add_files (GDBusProxy *proxy,
+ AddFileData *afd)
+{
+ GUnixFDList *fd_list;
+ GVariantBuilder fds, options;
+ int i;
+ char *key;
+
+ g_variant_builder_init (&fds, G_VARIANT_TYPE ("ah"));
+ fd_list = g_unix_fd_list_new ();
+
+ for (i = 0; afd->files[afd->start + i]; i++)
+ {
+ int fd;
+ int fd_in;
+ GError *error = NULL;
+
+ if (i == 16)
+ break;
+
+ fd = open (afd->files[afd->start + i], O_PATH | O_CLOEXEC);
+ if (fd == -1)
+ {
+ g_task_return_new_error (afd->task, G_IO_ERROR, g_io_error_from_errno (errno),
+ "Failed to open %s", afd->files[afd->start + i]);
+ free_add_file_data (afd);
+ g_object_unref (fd_list);
+ return;
+ }
+
+#ifndef HAVE_O_CLOEXEC
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+#endif
+ fd_in = g_unix_fd_list_append (fd_list, fd, &error);
+ close (fd);
+
+ if (fd_in == -1)
+ {
+ g_task_return_error (afd->task, error);
+ free_add_file_data (afd);
+ g_object_unref (fd_list);
+ return;
+ }
+
+ g_variant_builder_add (&fds, "h", fd_in);
+ }
+
+ afd->start += 16;
+
+ key = (char *)g_object_get_data (G_OBJECT (afd->task), "key");
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ g_dbus_proxy_call_with_unix_fd_list (proxy,
+ "AddFiles",
+ g_variant_new ("(saha{sv})", key, &fds, &options),
+ 0, -1,
+ fd_list,
+ NULL,
+ add_files_done, afd);
+
+ g_object_unref (fd_list);
+}
+
+static void
+start_session_done (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+ AddFileData *afd = data;
+ GError *error = NULL;
+ GVariant *ret;
+ const char *key;
+
+ ret = g_dbus_proxy_call_finish (proxy, result, &error);
+ if (ret == NULL)
+ {
+ g_task_return_error (afd->task, error);
+ free_add_file_data (afd);
+ return;
+ }
+
+ g_variant_get (ret, "(&s)", &key);
+
+ g_object_set_data_full (G_OBJECT (afd->task), "key", g_strdup (key), g_free);
+
+ g_variant_unref (ret);
+
+ add_files (proxy, afd);
+}
+
+void
+file_transfer_portal_register_files (const char **files,
+ gboolean writable,
+ GAsyncReadyCallback callback,
+ gpointer data)
+{
+ GTask *task;
+ AddFileData *afd;
+ GVariantBuilder options;
+
+ task = g_task_new (NULL, NULL, callback, data);
+
+ if (file_transfer_proxy == NULL)
+ {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "No portal found");
+ g_object_unref (task);
+ return;
+ }
+
+ afd = g_new (AddFileData, 1);
+ afd->task = task;
+ afd->files = g_strdupv ((char **)files);
+ afd->len = g_strv_length (afd->files);
+ afd->start = 0;
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add (&options, "{sv}", "writable", g_variant_new_boolean (writable));
+ g_variant_builder_add (&options, "{sv}", "autostop", g_variant_new_boolean (TRUE));
+
+ g_dbus_proxy_call (file_transfer_proxy, "StartTransfer",
+ g_variant_new ("(a{sv})", &options),
+ 0, -1, NULL, start_session_done, afd);
+}
+
+gboolean
+file_transfer_portal_register_files_finish (GAsyncResult *result,
+ char **key,
+ GError **error)
+{
+ if (g_task_propagate_boolean (G_TASK (result), error))
+ {
+ *key = g_strdup (g_object_get_data (G_OBJECT (result), "key"));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+char *
+file_transfer_portal_register_files_sync (const char **files,
+ gboolean writable,
+ GError **error)
+{
+ const char *value;
+ char *key;
+ GUnixFDList *fd_list;
+ GVariantBuilder fds, options;
+ int i;
+ GVariant *ret;
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_sync (file_transfer_proxy,
+ "StartTransfer",
+ g_variant_new ("(a{sv})", &options),
+ 0,
+ -1,
+ NULL,
+ error);
+ if (ret == NULL)
+ return NULL;
+
+ g_variant_get (ret, "(&s)", &value);
+ key = g_strdup (value);
+ g_variant_unref (ret);
+
+ fd_list = NULL;
+
+ for (i = 0; files[i]; i++)
+ {
+ int fd;
+ int fd_in;
+
+ if (fd_list == NULL)
+ {
+ g_variant_builder_init (&fds, G_VARIANT_TYPE ("ah"));
+ fd_list = g_unix_fd_list_new ();
+ }
+
+ fd = open (files[i], O_PATH | O_CLOEXEC);
+ if (fd == -1)
+ {
+ g_set_error (error,
+ G_IO_ERROR, g_io_error_from_errno (errno),
+ "Failed to open %s", files[i]);
+ g_variant_builder_clear (&fds);
+ g_object_unref (fd_list);
+ g_free (key);
+ return NULL;
+ }
+
+#ifndef HAVE_O_CLOEXEC
+ fcntl (fd, F_SETFD, FD_CLOEXEC);
+#endif
+ fd_in = g_unix_fd_list_append (fd_list, fd, error);
+ close (fd);
+
+ if (fd_in == -1)
+ {
+ g_variant_builder_clear (&fds);
+ g_object_unref (fd_list);
+ g_free (key);
+ return NULL;
+ }
+
+ g_variant_builder_add (&fds, "h", fd_in);
+
+ if ((i + 1) % 16 == 0 || files[i + 1] == NULL)
+ {
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_with_unix_fd_list_sync (file_transfer_proxy,
+ "AddFiles",
+ g_variant_new ("(saha{sv})",
+ key,
+ &fds,
+ &options),
+ 0,
+ -1,
+ fd_list,
+ NULL,
+ NULL,
+ error);
+ g_clear_object (&fd_list);
+
+ if (ret == NULL)
+ {
+ g_free (key);
+ return NULL;
+ }
+
+ g_variant_unref (ret);
+ }
+ }
+
+ return key;
+}
+
+static void
+retrieve_files_done (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ GDBusProxy *proxy = G_DBUS_PROXY (object);
+ GTask *task = data;
+ GError *error = NULL;
+ GVariant *ret;
+ char **files;
+
+ ret = g_dbus_proxy_call_finish (proxy, result, &error);
+ if (ret == NULL)
+ {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ g_variant_get (ret, "(^a&s)", &files);
+
+ g_object_set_data_full (G_OBJECT (task), "files", g_strdupv (files), (GDestroyNotify)g_strfreev);
+
+ g_variant_unref (ret);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+file_transfer_portal_retrieve_files (const char *key,
+ GAsyncReadyCallback callback,
+ gpointer data)
+{
+ GTask *task;
+ GVariantBuilder options;
+
+ task = g_task_new (NULL, NULL, callback, data);
+
+ if (file_transfer_proxy == NULL)
+ {
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ "No portal found");
+ g_object_unref (task);
+ return;
+ }
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ g_dbus_proxy_call (file_transfer_proxy,
+ "RetrieveFiles",
+ g_variant_new ("(sa{sv})", key, &options),
+ 0, -1, NULL,
+ retrieve_files_done, task);
+}
+
+gboolean
+file_transfer_portal_retrieve_files_finish (GAsyncResult *result,
+ char ***files,
+ GError **error)
+{
+ if (g_task_propagate_boolean (G_TASK (result), error))
+ {
+ *files = g_strdupv (g_object_get_data (G_OBJECT (result), "files"));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+char **
+file_transfer_portal_retrieve_files_sync (const char *key,
+ GError **error)
+{
+ GVariantBuilder options;
+ GVariant *ret;
+ char **files = NULL;
+
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
+ ret = g_dbus_proxy_call_sync (file_transfer_proxy,
+ "RetrieveFiles",
+ g_variant_new ("(sa{sv})", key, &options),
+ 0, -1, NULL,
+ error);
+ if (ret)
+ {
+ const char **value;
+ g_variant_get (ret, "(^a&s)", &value);
+ files = g_strdupv ((char **)value);
+ g_variant_unref (ret);
+ }
+
+ return files;
+}
+
+static void
+connection_closed (GDBusConnection *connection,
+ gboolean remote_peer_vanished,
+ GError *error)
+{
+ g_clear_object (&file_transfer_proxy);
+}
+
+static void
+finish_registration (void)
+{
+ /* Free the singleton when the connection closes, important for test */
+ g_signal_connect (g_dbus_proxy_get_connection (G_DBUS_PROXY (file_transfer_proxy)),
+ "closed", G_CALLBACK (connection_closed), NULL);
+}
+
+static gboolean
+proxy_has_owner (GDBusProxy *proxy)
+{
+ char *owner;
+
+ owner = g_dbus_proxy_get_name_owner (proxy);
+ if (owner)
+ {
+ g_free (owner);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+file_transfer_portal_register (void)
+{
+ static gboolean called;
+
+ if (!called)
+ {
+ called = TRUE;
+
+ file_transfer_proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
+ | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
+ | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
+ NULL,
+ "org.freedesktop.portal.Documents",
+ "/org/freedesktop/portal/documents",
+ "org.freedesktop.portal.FileTransfer",
+ NULL,
+ NULL);
+
+ if (file_transfer_proxy && !proxy_has_owner (file_transfer_proxy))
+ g_clear_object (&file_transfer_proxy);
+
+ if (file_transfer_proxy)
+ finish_registration ();
+ }
+}
+
+gboolean
+file_transfer_portal_supported (void)
+{
+ file_transfer_portal_register ();
+
+ return file_transfer_proxy != NULL;
+}
+
+#endif /* G_OS_UNIX */
diff --git a/gtk/filetransferportalprivate.h b/gtk/filetransferportalprivate.h
new file mode 100644
index 0000000000..d136b53345
--- /dev/null
+++ b/gtk/filetransferportalprivate.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 Matthias Clasen
+ *
+ * This library 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/>.
+ */
+
+#ifndef __FILE_TRANSFER_PROTOCOL_H__
+#define __FILE_TRANSFER_PROTOCOL_H__
+
+
+void file_transfer_portal_register (void);
+
+void file_transfer_portal_register_files (const char **files,
+ gboolean writable,
+ GAsyncReadyCallback callback,
+ gpointer data);
+gboolean file_transfer_portal_register_files_finish (GAsyncResult *result,
+ char **key,
+ GError **error);
+
+void file_transfer_portal_retrieve_files (const char *key,
+ GAsyncReadyCallback callback,
+ gpointer data);
+gboolean file_transfer_portal_retrieve_files_finish (GAsyncResult *result,
+ char ***files,
+ GError **error);
+
+
+char * file_transfer_portal_register_files_sync (const char **files,
+ gboolean writable,
+ GError **error);
+
+char ** file_transfer_portal_retrieve_files_sync (const char *key,
+ GError **error);
+
+gboolean file_transfer_portal_supported (void);
+
+#endif
diff --git a/gtk/gtkselection.c b/gtk/gtkselection.c
index 32d9de88bf..048b4ad496 100644
--- a/gtk/gtkselection.c
+++ b/gtk/gtkselection.c
@@ -116,6 +116,10 @@
#include "broadway/gdkbroadway.h"
#endif
+#ifndef G_OS_WIN32
+#include "filetransferportalprivate.h"
+#endif
+
#undef DEBUG_SELECTION
/* Maximum size of a sent chunk, in bytes. Also the default size of
@@ -338,6 +342,7 @@ static GdkAtom text_plain_atom;
static GdkAtom text_plain_utf8_atom;
static GdkAtom text_plain_locale_atom;
static GdkAtom text_uri_list_atom;
+static GdkAtom portal_files_atom;
static void
init_atoms (void)
@@ -358,6 +363,7 @@ init_atoms (void)
g_free (tmp);
text_uri_list_atom = gdk_atom_intern_static_string ("text/uri-list");
+ portal_files_atom = gdk_atom_intern_static_string ("application/vnd.portal.files");
}
}
@@ -502,6 +508,10 @@ gtk_target_list_add_image_targets (GtkTargetList *list,
* Appends the URI targets supported by #GtkSelectionData to
* the target list. All targets are added with the same @info.
*
+ * Since 3.24.37, this includes the application/vnd.portal.files
+ * target when possible, to allow sending files between sandboxed
+ * apps via the FileTransfer portal.
+ *
* Since: 2.6
**/
void
@@ -512,7 +522,12 @@ gtk_target_list_add_uri_targets (GtkTargetList *list,
init_atoms ();
- gtk_target_list_add (list, text_uri_list_atom, 0, info);
+ gtk_target_list_add (list, text_uri_list_atom, 0, info);
+
+#ifndef G_OS_WIN32
+ if (file_transfer_portal_supported ())
+ gtk_target_list_add (list, portal_files_atom, 0, info);
+#endif
}
/**
@@ -1835,6 +1850,9 @@ gtk_selection_data_get_pixbuf (const GtkSelectionData *selection_data)
* Sets the contents of the selection from a list of URIs.
* The string is converted to the form determined by
* @selection_data->target.
+ *
+ * Since 3.24.37, this may involve using the FileTransfer
+ * portal to send files between sandboxed apps.
*
* Returns: %TRUE if the selection was successfully set,
* otherwise %FALSE.
@@ -1880,6 +1898,57 @@ gtk_selection_data_set_uris (GtkSelectionData *selection_data,
return TRUE;
}
}
+#ifndef G_OS_WIN32
+ else if (selection_data->target == portal_files_atom &&
+ file_transfer_portal_supported ())
+ {
+ GPtrArray *a;
+ char **files;
+ char *key;
+ GError *error = NULL;
+
+ a = g_ptr_array_new ();
+
+ for (int i = 0; uris[i]; i++)
+ {
+ GFile *file;
+ char *path;
+
+ file = g_file_new_for_uri (uris[i]);
+ path = g_file_get_path (file);
+ g_object_unref (file);
+
+ if (path == NULL)
+ {
+ g_ptr_array_unref (a);
+ return FALSE;
+ }
+
+ g_ptr_array_add (a, path);
+ }
+
+ g_ptr_array_add (a, NULL);
+ files = (char **) g_ptr_array_free (a, FALSE);
+
+ key = file_transfer_portal_register_files_sync ((const char **)files, TRUE, &error);
+ if (key == NULL)
+ {
+ g_strfreev (files);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ gtk_selection_data_set (selection_data,
+ portal_files_atom,
+ 8, (guchar *)key, strlen (key));
+
+ g_strfreev (files);
+ g_free (key);
+
+ return TRUE;
+ }
+#endif
return FALSE;
}
@@ -1890,6 +1959,9 @@ gtk_selection_data_set_uris (GtkSelectionData *selection_data,
*
* Gets the contents of the selection data as array of URIs.
*
+ * Since 3.24.37, this may involve using the FileTransfer
+ * portal to send files between sandboxed apps.
+ *
* Returns: (array zero-terminated=1) (element-type utf8) (transfer full): if
* the selection data contains a list of
* URIs, a newly allocated %NULL-terminated string array
@@ -1922,6 +1994,40 @@ gtk_selection_data_get_uris (const GtkSelectionData *selection_data)
g_strfreev (list);
}
+#ifndef G_OS_WIN32
+ else if (selection_data->length >= 0 &&
+ selection_data->type == portal_files_atom &&
+ file_transfer_portal_supported ())
+ {
+ char *key;
+ GError *error = NULL;
+ char **files;
+
+ key = g_strndup ((char *) selection_data->data, selection_data->length);
+ files = file_transfer_portal_retrieve_files_sync (key, &error);
+ if (error)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ g_free (key);
+
+ if (files)
+ {
+ GPtrArray *uris = g_ptr_array_new ();
+
+ for (int i = 0; files[i]; i++)
+ {
+ GFile *file = g_file_new_for_path (files[i]);
+ g_ptr_array_add (uris, g_file_get_uri (file));
+ g_object_unref (file);
+ }
+
+ g_ptr_array_add (uris, NULL);
+ result = (char **) g_ptr_array_free (uris, FALSE);
+ }
+ }
+#endif
return result;
}
@@ -2246,7 +2352,8 @@ gtk_targets_include_uri (GdkAtom *targets,
for (i = 0; i < n_targets; i++)
{
- if (targets[i] == text_uri_list_atom)
+ if (targets[i] == text_uri_list_atom ||
+ targets[i] == portal_files_atom)
{
result = TRUE;
break;
diff --git a/gtk/meson.build b/gtk/meson.build
index 21540dd93b..4b7a453e11 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -660,6 +660,10 @@ if os_unix and tracker3_enabled
endif
if os_unix
+ gtk_unix_sources += 'filetransferportal.c'
+endif
+
+if os_unix
gtk_sources += gtk_unix_sources
endif
diff --git a/tests/meson.build b/tests/meson.build
index 586fe2f45e..6ff249f8a1 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -36,7 +36,7 @@ gtk_tests = [
['testcombo'],
['testcombochange'],
['testdialog'],
- ['testdnd'],
+ ['testdnd2'],
['testellipsise'],
['testemblems'],
['testentrycompletion'],
@@ -45,6 +45,7 @@ gtk_tests = [
['testexpander'],
['testfilechooserbutton'],
['testfilechooser'],
+ ['testfileportal'],
['testflowbox'],
['testfontchooser'],
['testfontoptions'],
diff --git a/tests/testfileportal.c b/tests/testfileportal.c
new file mode 100644
index 0000000000..0918af1d1b
--- /dev/null
+++ b/tests/testfileportal.c
@@ -0,0 +1,130 @@
+/* simple.c
+ * Copyright (C) 1997 Red Hat, Inc
+ * Author: Elliot Lee
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "config.h"
+#include <gtk/gtk.h>
+
+static void
+drag_begin_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ char **uris;
+ char *cwd;
+
+ cwd = g_get_current_dir ();
+ uris = g_new0 (char *, 2);
+ uris[0] = g_strconcat ("file://", cwd, "/README.md", NULL);
+ g_free (cwd);
+
+ g_signal_handlers_disconnect_by_func (widget, drag_begin_cb, NULL);
+ gtk_drag_set_icon_default (context);
+
+ g_object_set_data_full (G_OBJECT (widget), "uris", g_strdupv ((char **)uris), (GDestroyNotify) g_strfreev);
+}
+
+static void
+drag_data_get (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection,
+ unsigned int target_info,
+ unsigned int time,
+ gpointer data)
+{
+ char **uris = (char **)g_object_get_data (G_OBJECT (widget), "uris");
+
+ gtk_selection_data_set_uris (selection, uris);
+
+ g_object_set_data (G_OBJECT (widget), "uris", NULL);
+}
+
+static void
+drag_data_received (GtkWidget *widget,
+ GdkDragContext *context,
+ int x,
+ int y,
+ GtkSelectionData *selection_data,
+ unsigned int info,
+ unsigned int time,
+ gpointer user_data)
+{
+ GtkLabel *label = user_data;
+ char **uris;
+
+ uris = gtk_selection_data_get_uris (selection_data);
+
+ if (uris)
+ {
+ gtk_label_set_label (label, uris[0]);
+ g_strfreev (uris);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window, *label, *eventbox, *box;
+ GtkTargetEntry targets[] = {
+ { "application/vnd.portal.files", 0, 0 },
+ };
+
+ gtk_init (&argc, &argv);
+
+ window = g_object_connect (g_object_new (gtk_window_get_type (),
+ "type", GTK_WINDOW_TOPLEVEL,
+ "title", "hello world",
+ "resizable", FALSE,
+ "border_width", 10,
+ NULL),
+ "signal::destroy", gtk_main_quit, NULL,
+ NULL);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_show (box);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ eventbox = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (box), eventbox);
+ gtk_widget_show (eventbox);
+ gtk_event_box_set_above_child (GTK_EVENT_BOX (eventbox), TRUE);
+
+ label = gtk_label_new ("drag me");
+ gtk_container_add (GTK_CONTAINER (eventbox), label);
+
+ gtk_drag_source_set (eventbox, GDK_BUTTON1_MASK, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY);
+ g_signal_connect (eventbox, "drag-begin", G_CALLBACK (drag_begin_cb), NULL);
+ g_signal_connect (eventbox, "drag-data-get", G_CALLBACK (drag_data_get), NULL);
+ gtk_widget_show (label);
+
+ eventbox = gtk_event_box_new ();
+ gtk_container_add (GTK_CONTAINER (box), eventbox);
+ gtk_widget_show (eventbox);
+ gtk_event_box_set_above_child (GTK_EVENT_BOX (eventbox), TRUE);
+
+ label = gtk_label_new ("drop here");
+ gtk_widget_show (label);
+ gtk_container_add (GTK_CONTAINER (eventbox), label);
+ gtk_drag_dest_set (eventbox, GTK_DEST_DEFAULT_ALL, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY);
+
+ g_signal_connect (eventbox, "drag-data-received", G_CALLBACK (drag_data_received), label);
+
+ gtk_widget_show (window);
+
+ gtk_main ();
+
+ return 0;
+}