diff --git a/libportal/portal-helpers.h b/libportal/portal-helpers.h index ac754e69..8900d730 100644 --- a/libportal/portal-helpers.h +++ b/libportal/portal-helpers.h @@ -45,6 +45,18 @@ XdpPortal *xdp_portal_new (void); XDP_PUBLIC XdpPortal *xdp_portal_initable_new (GError **error); +XDP_PUBLIC +void xdp_portal_register (XdpPortal *portal, + const char *app_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +XDP_PUBLIC +gboolean xdp_portal_register_finish (XdpPortal *portal, + GAsyncResult *result, + GError **error); + XDP_PUBLIC gboolean xdp_portal_running_under_flatpak (void); diff --git a/libportal/portal.c b/libportal/portal.c index 4a6add89..aab87342 100644 --- a/libportal/portal.c +++ b/libportal/portal.c @@ -386,6 +386,117 @@ xdp_portal_new (void) return portal; } +static void +register_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = user_data; + g_autoptr(GVariant) ret = NULL; + g_autoptr(GError) error = NULL; + + ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object), + result, + &error); + if (error) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_boolean (task, TRUE); +} + +/** + * xdp_portal_register: + * @portal: a [class@Portal] + * @app_id: an application ID matching the basename of a desktop file + * @cancellable: (nullable): optional [class@Gio.Cancellable] + * @callback: (scope async): a callback to call when the request is done + * @data: (closure): data to pass to @callback + * + * Registers the application ID with the org.freedesktop.host.Registry portal. + * Doing this when sandboxed is a no-op, but when running on the host, it will + * help associating portal requests with an application. + * + * When the request is done, @callback will be called. You can then + * call [method@Portal.register_finish] to get the results. + */ +void +xdp_portal_register (XdpPortal *portal, + const char *app_id, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + GVariantBuilder options; + g_autoptr(GError) error = NULL; + + g_return_if_fail (XDP_IS_PORTAL (portal)); + g_return_if_fail (app_id); + g_return_if_fail (g_strcmp0 (app_id, "") != 0); + + task = g_task_new (portal, cancellable, callback, user_data); + g_task_set_source_tag (task, xdp_portal_register); + + if (xdp_portal_running_under_flatpak ()) + { + g_task_return_boolean (task, TRUE); + return; + } + + if (xdp_portal_running_under_snap (&error)) + { + g_task_return_boolean (task, TRUE); + return; + } + + if (error) + { + g_task_return_error (task, error); + return; + } + + g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); + + g_dbus_connection_call (portal->bus, + PORTAL_BUS_NAME, + PORTAL_OBJECT_PATH, + "org.freedesktop.host.portal.Registry", + "Register", + g_variant_new ("(sa{sv})", app_id, &options), + NULL, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + register_cb, + g_object_ref (task)); +} + +/** + * xdp_portal_register_finish: + * @portal: a [class@Portal] + * @result: a [iface@Gio.AsyncResult] + * @error: return location for an error + * + * Finishes the registry registration request. + * + * Returns %TRUE if the registration succeeded, or app was sandboxed, in which + * case, the operation was a no-op. + * + * Returns: %TRUE on success + */ +gboolean +xdp_portal_register_finish (XdpPortal *portal, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE); + g_return_val_if_fail (g_task_is_valid (result, portal), FALSE); + g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == + xdp_portal_register, FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + /* This function is copied from xdg-desktop-portal */ static int _xdp_parse_cgroup_file (FILE *f, gboolean *is_snap) diff --git a/portal-test/gtk3/portal-test-win.c b/portal-test/gtk3/portal-test-win.c index e95c1c13..4fb45f35 100644 --- a/portal-test/gtk3/portal-test-win.c +++ b/portal-test/gtk3/portal-test-win.c @@ -299,8 +299,28 @@ settings_changed (XdpSettings* settings, const gchar *namespace, const gchar *ke } static void -portal_test_win_init (PortalTestWin *win) +register_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + XdpPortal *portal = XDP_PORTAL (source_object); + g_autoptr(GError) error = NULL; + + if (!xdp_portal_register_finish (portal, result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to register application ID: %s", error->message); + return; + } + + g_debug ("Registered application ID"); +} + +static void +portal_test_win_realize (GtkWidget *widget) { + PortalTestWin *win = (PortalTestWin *) widget; + GtkApplication *app = gtk_window_get_application (GTK_WINDOW (win)); const char *status; g_auto(GStrv) proxies = NULL; g_autofree char *proxy = NULL; @@ -312,6 +332,11 @@ portal_test_win_init (PortalTestWin *win) g_autoptr(GFile) src = NULL; win->portal = xdp_portal_new (); + xdp_portal_register (win->portal, + g_application_get_application_id (G_APPLICATION (app)), + NULL, + register_cb, + NULL); gtk_widget_init_template (GTK_WIDGET (win)); @@ -357,6 +382,13 @@ portal_test_win_init (PortalTestWin *win) win->settings = xdp_portal_get_settings (win->portal); g_signal_connect (win->settings, "changed", G_CALLBACK (settings_changed), win); + + GTK_WIDGET_CLASS (portal_test_win_parent_class)->realize (widget); +} + +static void +portal_test_win_init (PortalTestWin *win) +{ } static void @@ -1394,6 +1426,8 @@ portal_test_win_class_init (PortalTestWinClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + widget_class->realize = portal_test_win_realize; + gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/portal-test/portal-test-win.ui"); diff --git a/portal-test/gtk4/application.js b/portal-test/gtk4/application.js index 55554752..80b24ec9 100644 --- a/portal-test/gtk4/application.js +++ b/portal-test/gtk4/application.js @@ -1,4 +1,4 @@ -const { Gio, GLib, GObject, Gtk } = imports.gi; +const { Gio, GLib, GObject, Gtk, Xdp } = imports.gi; const { PortalTestWindow } = imports.window; const ByteArray = imports.byteArray; @@ -31,12 +31,21 @@ var Application = GObject.registerClass({ } vfunc_startup() { + this._portal = new Xdp.Portal(); + this._portal.register(this.get_application_id(), null, (portal, result) => { + this._portal.register_finish(result); + }); + super.vfunc_startup(); if (!this._window) this._window = new PortalTestWindow(this); } + getPortal() { + return this._portal; + } + restart() { const bus = this.get_dbus_connection(); diff --git a/portal-test/gtk4/window.js b/portal-test/gtk4/window.js index 64c97e8d..05c879b2 100644 --- a/portal-test/gtk4/window.js +++ b/portal-test/gtk4/window.js @@ -37,7 +37,7 @@ var PortalTestWindow = GObject.registerClass({ super._init({ application }); this._app = application; - this._portal = new Xdp.Portal(); + this._portal = application.getPortal(); this._restoreToken = null; diff --git a/portal-test/qt5/main.cpp b/portal-test/qt5/main.cpp index b7c1f2aa..225bbf12 100644 --- a/portal-test/qt5/main.cpp +++ b/portal-test/qt5/main.cpp @@ -3,11 +3,15 @@ #include "portal-test-qt.h" +#define APP_ID "org.freedesktop.PortalTest.Qt5" + int main(int argc, char *argv[]) { QApplication a(argc, argv); - PortalTestQt *portalTest = new PortalTestQt(nullptr); + a.setDesktopFileName(APP_ID); + + PortalTestQt *portalTest = new PortalTestQt(&a, nullptr); portalTest->show(); return a.exec(); diff --git a/portal-test/qt5/meson.build b/portal-test/qt5/meson.build index 1a47664b..f0e97625 100644 --- a/portal-test/qt5/meson.build +++ b/portal-test/qt5/meson.build @@ -14,6 +14,8 @@ prep = qt5.preprocess( dependencies: libportal_qt5_dep, ) +install_data('org.freedesktop.PortalTest.Qt5.desktop', install_dir: 'share/applications') + executable('portal-test-qt5', [src, prep], include_directories: [top_inc, libportal_inc], diff --git a/portal-test/qt5/org.freedesktop.PortalTest.Qt5.desktop b/portal-test/qt5/org.freedesktop.PortalTest.Qt5.desktop new file mode 100644 index 00000000..64fb48d0 --- /dev/null +++ b/portal-test/qt5/org.freedesktop.PortalTest.Qt5.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Portal test (GTK3) +Exec=portal-test-gtk3 +Type=Application +DBusActivatable=true diff --git a/portal-test/qt5/portal-test-qt.cpp b/portal-test/qt5/portal-test-qt.cpp index 170aab02..38212473 100644 --- a/portal-test/qt5/portal-test-qt.cpp +++ b/portal-test/qt5/portal-test-qt.cpp @@ -4,11 +4,30 @@ #include -PortalTestQt::PortalTestQt(QWidget *parent, Qt::WindowFlags f) +PortalTestQt::PortalTestQt(QApplication *app, QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f) , m_mainWindow(new Ui_PortalTestQt) + , m_app(app) , m_portal(xdp_portal_new()) { + QString app_id = app->desktopFileName(); + xdp_portal_register(m_portal, app_id.toLocal8Bit().constData(), nullptr /*cancellable*/, + [](GObject *source_object, GAsyncResult *result, gpointer user_data) + { + Q_UNUSED(user_data) + + XdpPortal *portal = XDP_PORTAL (source_object); + g_autoptr(GError) error = NULL; + if (!xdp_portal_register_finish (portal, result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + qWarning ("Failed to register application ID: %s", error->message); + return; + } + qInfo ("Registered application ID"); + }, + nullptr); + m_mainWindow->setupUi(this); connect(m_mainWindow->openFileButton, &QPushButton::clicked, [=] (G_GNUC_UNUSED bool clicked) { diff --git a/portal-test/qt5/portal-test-qt.h b/portal-test/qt5/portal-test-qt.h index 8c0b8a2e..19a3f5aa 100644 --- a/portal-test/qt5/portal-test-qt.h +++ b/portal-test/qt5/portal-test-qt.h @@ -14,7 +14,7 @@ class PortalTestQt : public QMainWindow { Q_OBJECT public: - PortalTestQt(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + PortalTestQt(QApplication *app, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~PortalTestQt(); void updateLastOpenedFile(const QString &file); @@ -22,6 +22,7 @@ class PortalTestQt : public QMainWindow static void openedFile(GObject *object, GAsyncResult *result, gpointer data); Ui_PortalTestQt *m_mainWindow; + QApplication *m_app; XdpPortal *m_portal; }; diff --git a/portal-test/qt6/main.cpp b/portal-test/qt6/main.cpp index b7c1f2aa..bdd24992 100644 --- a/portal-test/qt6/main.cpp +++ b/portal-test/qt6/main.cpp @@ -3,11 +3,33 @@ #include "portal-test-qt.h" +#define APP_ID "org.gnome.PortalTest.Qt6" + + int main(int argc, char *argv[]) { - QApplication a(argc, argv); + XdpPortal *portal = xdp_portal_new(); + xdp_portal_register(portal, APP_ID, nullptr /*cancellable*/, + [](GObject *source_object, GAsyncResult *result, gpointer user_data) + { + Q_UNUSED(user_data) + + XdpPortal *portal = XDP_PORTAL (source_object); + g_autoptr(GError) error = NULL; + if (!xdp_portal_register_finish (portal, result, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + qWarning ("Failed to register application ID: %s", error->message); + return; + } - PortalTestQt *portalTest = new PortalTestQt(nullptr); + qInfo ("Registered application ID"); + }, + nullptr); + + + QApplication a(argc, argv); + PortalTestQt *portalTest = new PortalTestQt(portal, nullptr); portalTest->show(); return a.exec(); diff --git a/portal-test/qt6/meson.build b/portal-test/qt6/meson.build index 5d9a2889..d7b8c184 100644 --- a/portal-test/qt6/meson.build +++ b/portal-test/qt6/meson.build @@ -14,6 +14,8 @@ prep = qt6.preprocess( dependencies: libportal_qt6_dep, ) +install_data('org.freedesktop.PortalTest.Qt6.desktop', install_dir: 'share/applications') + executable('portal-test-qt6', [src, prep], include_directories: [top_inc, libportal_inc], diff --git a/portal-test/qt6/org.freedesktop.PortalTest.Qt6.desktop b/portal-test/qt6/org.freedesktop.PortalTest.Qt6.desktop new file mode 100644 index 00000000..64fb48d0 --- /dev/null +++ b/portal-test/qt6/org.freedesktop.PortalTest.Qt6.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Portal test (GTK3) +Exec=portal-test-gtk3 +Type=Application +DBusActivatable=true diff --git a/portal-test/qt6/portal-test-qt.cpp b/portal-test/qt6/portal-test-qt.cpp index 170aab02..7b076a51 100644 --- a/portal-test/qt6/portal-test-qt.cpp +++ b/portal-test/qt6/portal-test-qt.cpp @@ -4,10 +4,10 @@ #include -PortalTestQt::PortalTestQt(QWidget *parent, Qt::WindowFlags f) +PortalTestQt::PortalTestQt(XdpPortal *portal, QWidget *parent, Qt::WindowFlags f) : QMainWindow(parent, f) , m_mainWindow(new Ui_PortalTestQt) - , m_portal(xdp_portal_new()) + , m_portal(portal) { m_mainWindow->setupUi(this); diff --git a/portal-test/qt6/portal-test-qt.h b/portal-test/qt6/portal-test-qt.h index b50e8ea4..5fd1acea 100644 --- a/portal-test/qt6/portal-test-qt.h +++ b/portal-test/qt6/portal-test-qt.h @@ -14,7 +14,7 @@ class PortalTestQt : public QMainWindow { Q_OBJECT public: - PortalTestQt(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); + PortalTestQt(XdpPortal *portal, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); ~PortalTestQt(); void updateLastOpenedFile(const QString &file);