WIP: Add Fake openuri portal implementation for testing

This also includes tests to make sure that the activation token is given
correctly. But unfortunately since the tests around portals are really
really limited it's much more work then I expected. And i couldn't
finish it during GUADEC.
This commit is contained in:
Julian Sparber 2024-07-29 11:02:06 +02:00
parent 0190894690
commit f3c0bb66e2
8 changed files with 489 additions and 7 deletions

View File

@ -1261,9 +1261,11 @@ launch_default_for_uri_portal_open_uri_cb (GObject *object,
GTask *task = G_TASK (user_data);
GError *error = NULL;
if (g_openuri_portal_open_file_finish (result, &error))
g_print ("Finish portal\n");
if (g_openuri_portal_open_file_finish (result, &error)) {
g_task_return_boolean (task, TRUE);
else
g_print ("Success\n");
} else
g_task_return_error (task, g_steal_pointer (&error));
g_object_unref (task);
}
@ -1276,14 +1278,16 @@ launch_default_for_uri_portal_open_uri (GTask *task, GError *error)
LaunchDefaultForUriData *data = g_task_get_task_data (task);
GCancellable *cancellable = g_task_get_cancellable (task);
g_print ("Call here\n");
if (glib_should_use_portal ())
{
GFile *file;
const char *parent_window = NULL;
char *startup_id = NULL;
g_print ("Use portal here\n");
/* Reset any error previously set by launch_default_for_uri */
g_error_free (error);
g_clear_error (&error);
file = g_file_new_for_uri (data->uri);
@ -1303,6 +1307,7 @@ launch_default_for_uri_portal_open_uri (GTask *task, GError *error)
g_list_free (file_list);
}
g_print ("Call the portal\n");
g_openuri_portal_open_file_async (file,
parent_window,
startup_id,
@ -1329,8 +1334,10 @@ launch_default_for_uri_launch_uris_cb (GObject *object,
GTask *task = G_TASK (user_data);
GError *error = NULL;
g_print ("Portal or done?\n");
if (g_app_info_launch_uris_finish (app_info, result, &error))
{
g_print ("Is done\n");
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
@ -1367,7 +1374,7 @@ launch_default_for_uri_default_handler_cb (GObject *object,
GAppInfo *app_info = NULL;
GError *error = NULL;
app_info = g_file_query_default_handler_finish (file, result, &error);
//app_info = g_file_query_default_handler_finish (file, result, &error);
if (app_info)
launch_default_for_uri_launch_uris (g_steal_pointer (&task), g_steal_pointer (&app_info));
else

View File

@ -177,6 +177,7 @@ response_received (GDBusConnection *connection,
GTask *task = user_data;
guint32 response;
guint signal_id;
g_print ("Got response");
signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
g_dbus_connection_signal_unsubscribe (connection, signal_id);
@ -215,6 +216,7 @@ open_call_done (GObject *source,
const char *handle;
guint signal_id;
g_print ("done open call\n");
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
open_file = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "open-file"));
@ -232,8 +234,10 @@ open_call_done (GObject *source,
}
handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
g_print ("Hanlde %s path %s\n", handle, path);
if (g_strcmp0 (handle, path) != 0)
{
g_print ("The path != handle");
signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
g_dbus_connection_signal_unsubscribe (connection, signal_id);
@ -275,6 +279,7 @@ g_openuri_portal_open_file_async (GFile *file,
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
g_print ("Should subscripbe\n");
if (callback)
{
GVariantBuilder opt_builder;
@ -294,6 +299,8 @@ g_openuri_portal_open_file_async (GFile *file,
g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
g_free (sender);
g_print ("Have callback %s\n", handle);
signal_id = g_dbus_connection_signal_subscribe (connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request",

View File

@ -0,0 +1,91 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2015 Red Hat, Inc.
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/>.
Author: Alexander Larsson <alexl@redhat.com>
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.portal.Request:
@short_description: Shared request interface
The Request interface is shared by all portal interfaces. When a
portal method is called, the reply includes a handle (i.e. object path)
for a Request object, which will stay alive for the duration of the
user interaction related to the method call.
The portal indicates that a portal request interaction is over by
emitting the #org.freedesktop.portal.Request::Response signal on the
Request object.
The application can abort the interaction calling
org.freedesktop.portal.Request.Close() on the Request object.
Since version 0.9 of xdg-desktop-portal, the handle will be of the form
::
/org/freedesktop/portal/desktop/request/SENDER/TOKEN
where ``SENDER`` is the callers unique name, with the initial ``':'`` removed and
all ``'.'`` replaced by ``'_'``, and ``TOKEN`` is a unique token that the caller provided
with the handle_token key in the options vardict.
This change was made to let applications subscribe to the Response signal before
making the initial portal call, thereby avoiding a race condition. It is recommended
that the caller should verify that the returned handle is what it expected, and update
its signal subscription if it isn't. This ensures that applications will work with both
old and new versions of xdg-desktop-portal.
The token that the caller provides should be unique and not guessable. To avoid clashes
with calls made from unrelated libraries, it is a good idea to use a per-library prefix
combined with a random number.
-->
<interface name="org.freedesktop.portal.Request">
<!--
Close:
Closes the portal request to which this object refers and ends all
related user interaction (dialogs, etc).
A Response signal will not be emitted in this case.
-->
<method name="Close">
</method>
<!--
Response:
@response: Numeric response
@results: Vardict with results. The keys and values in the vardict depend on the request.
Emitted when the user interaction for a portal request is over.
The @response indicates how the user interaction ended:
- 0: Success, the request is carried out
- 1: The user cancelled the interaction
- 2: The user interaction was ended in some other way
-->
<signal name="Response">
<arg type="u" name="response"/>
<arg type="a{sv}" name="results"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/>
</signal>
</interface>
</node>

View File

@ -230,7 +230,7 @@ test_application_before_emit (GApplication *application,
g_assert (!saw_startup_id);
g_print ("Run before emit %s\n", g_variant_print (platform_data, TRUE));
const gchar *startup_id_keys[] = {
"desktop-startup-id",
"activation-token",
@ -466,13 +466,107 @@ test_flatpak_missing_doc_export (void)
g_free (desktop_file);
}
static void
on_flatpak_launch_default_for_uri_finish (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GApplication *app = user_data;
GError *error = NULL;
g_app_info_launch_default_for_uri_finish (result, &error);
g_assert_no_error (error);
g_application_release (app);
}
static void
on_flatpak_launch_default_for_uri_async_activate (GApplication *app,
gpointer user_data)
{
GAppLaunchContext *ctx;
GDesktopAppInfo *flatpak_appinfo = user_data;
char *uri;
/* The app will be released in on_flatpak_launch_default_for_uri_finish */
g_application_hold (app);
/*
uri = g_filename_to_uri (g_desktop_app_info_get_filename (flatpak_appinfo), NULL, NULL);
g_assert_nonnull (uri);
*/
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
g_print ("Launch >>>>\n");
uri = "https://something";
g_app_info_launch_default_for_uri_async (uri, ctx,
NULL, on_flatpak_launch_default_for_uri_finish, app);
g_object_unref (ctx);
// g_free (uri);
}
static void
on_flatpak_launch_default_for_uri_async_open (GApplication *app,
GFile **files,
gint n_files,
const char *hint)
{
GFile *f;
g_assert_cmpint (n_files, ==, 1);
g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0]));
/* The file has been exported via the document portal */
f = g_file_new_for_uri ("unsupported-xxxx:///document-portal/document-id/org.gtk.test.dbusappinfo.flatpak.desktop");
g_assert_true (g_file_equal (files[0], f));
g_object_unref (f);
}
static void
test_flatpak_launch_default_for_uri_async (void)
{
const gchar *argv[] = { "myapp", NULL };
gchar *desktop_file = NULL;
GError *error = NULL;
GDesktopAppInfo *flatpak_appinfo;
GApplication *app;
int status;
g_test_summary ("Test that launch default for uri async gets the correct startup-id/activation token.");
desktop_file = g_test_build_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.flatpak.desktop",
NULL);
flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file);
//g_assert_nonnull (flatpak_appinfo);
g_free (desktop_file);
//g_app_info_set_as_default_for_extension (G_APP_INFO (flatpak_appinfo), "desktop", &error);
g_assert_no_error (error);
app = test_application_new ("org.gtk.test.dbusappinfo.flatpak",
G_APPLICATION_HANDLES_OPEN);
g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_launch_default_for_uri_async_activate),
flatpak_appinfo);
g_signal_connect (app, "open", G_CALLBACK (on_flatpak_launch_default_for_uri_async_open), NULL);
status = g_application_run (app, 1, (gchar **) argv);
g_assert_cmpint (status, ==, 0);
g_assert_true (requested_startup_id);
g_assert_true (saw_startup_id);
g_object_unref (app);
g_object_unref (flatpak_appinfo);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, NULL);
//g_test_add_func ("/appinfo/dbusappinfo", test_dbus_appinfo);
g_test_add_func ("/appinfo/flatpak-doc-export", test_flatpak_doc_export);
//g_test_add_func ("/appinfo/flatpak-doc-export", test_flatpak_doc_export);
g_test_add_func ("/appinfo/flatpak-launch-default-for-uri-async", test_flatpak_launch_default_for_uri_async);
//g_test_add_func ("/appinfo/flatpak-missing-doc-export", test_flatpak_missing_doc_export);
return session_bus_run ();

View File

@ -0,0 +1,250 @@
/*
* Copyright © 2024 GNOME Foundation Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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.1 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: Julian Sparber <jsparber@gnome.org>
*/
/* A stub implementation of xdg-dekstop-portal */
#include <glib.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "fake-openuri-portal-generated.h"
#include "fake-request-portal-generated.h"
static gboolean
on_handle_close (FakeRequest *object,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
g_test_message ("Got request close");
fake_request_complete_close (object, invocation);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static char*
get_request_path (GDBusMethodInvocation *invocation,
const char *token) {
char *request_obj_path;
char *sender;
int i;
sender = g_strdup (g_dbus_method_invocation_get_sender (invocation) + 1);
for (i = 0; sender[i]; i++)
if (sender[i] == '.')
sender[i] = '_';
request_obj_path = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
g_free (sender);
return request_obj_path;
}
static gboolean
on_handle_open_file (FakeOpenURI *object,
GDBusMethodInvocation *invocation,
GUnixFDList *fd_list,
const gchar *arg_parent_window,
GVariant *arg_fd,
GVariant *arg_options)
{
char *request_obj_path;
GVariantBuilder opt_builder;
GError *error = NULL;
FakeRequest *interfaceRequest;
const char *activation_token;
const char *token;
g_variant_lookup (arg_options, "activation_token", "&s", &activation_token);
//TODO: make sure that activation-token and window is expected and other stuff
// Write the data to a file so it can be read by the test
g_variant_lookup (arg_options, "handle_token", "&s", &token);
g_test_message ("Got open file request");
request_obj_path = get_request_path (invocation, token);
interfaceRequest = fake_request_skeleton_new ();
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
g_signal_connect (interfaceRequest,
"handle-close",
G_CALLBACK (on_handle_close),
NULL);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interfaceRequest),
g_dbus_method_invocation_get_connection (invocation),
request_obj_path,
&error);
g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (interfaceRequest),
G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
g_assert_no_error (error);
g_test_message ("Request skeleton exported at %s", request_obj_path);
fake_open_uri_complete_open_file (object,
invocation,
NULL,
request_obj_path);
fake_request_emit_response (interfaceRequest,
0, /* Success */
g_variant_builder_end (&opt_builder));
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (interfaceRequest));
g_test_message ("Response emitted");
g_free (request_obj_path);
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static gboolean
on_handle_open_uri (FakeOpenURI *object,
GDBusMethodInvocation *invocation,
const gchar *arg_parent_window,
GVariant *arg_uri,
GVariant *arg_options)
{
char *request_obj_path;
GVariantBuilder opt_builder;
FakeRequest *interfaceRequest;
GError *error = NULL;
const char *activation_token;
const char *token;
g_variant_lookup (arg_options, "activation_token", "&s", &activation_token);
//TODO: make sure that activation-token and window is expected and other stuff
// Write the data to a file so it can be read by the test
g_variant_lookup (arg_options, "handle_token", "&s", &token);
g_test_message ("Got open uri request");
request_obj_path = get_request_path (invocation, token);
interfaceRequest = fake_request_skeleton_new ();
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
g_signal_connect (interfaceRequest,
"handle-close",
G_CALLBACK (on_handle_close),
NULL);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interfaceRequest),
g_dbus_method_invocation_get_connection (invocation),
request_obj_path,
&error);
g_assert_no_error (error);
g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (interfaceRequest),
G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD);
g_test_message ("Request skeleton exported at %s", request_obj_path);
fake_open_uri_complete_open_uri (object,
invocation,
request_obj_path);
fake_request_emit_response (interfaceRequest,
0, /* Success */
g_variant_builder_end (&opt_builder));
g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (interfaceRequest));
g_free (request_obj_path);
g_test_message ("Response emitted");
return G_DBUS_METHOD_INVOCATION_HANDLED;
}
static void
on_bus_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
FakeOpenURI *interfaceOpenURI;
GError *error = NULL;
g_test_message ("Acquired a message bus connection");
interfaceOpenURI = fake_open_uri_skeleton_new ();
g_signal_connect (interfaceOpenURI,
"handle-open-file",
G_CALLBACK (on_handle_open_file),
NULL);
g_signal_connect (interfaceOpenURI,
"handle-open-uri",
G_CALLBACK (on_handle_open_uri),
NULL);
g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (interfaceOpenURI),
connection,
"/org/freedesktop/portal/desktop",
&error);
g_assert_no_error (error);
}
static void
on_name_acquired (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_test_message ("Acquired the name %s", name);
}
static void
on_name_lost (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_test_message ("Lost the name %s", name);
}
gint
main (gint argc, gchar *argv[])
{
GMainLoop *loop;
guint id;
g_log_writer_default_set_use_stderr (TRUE);
g_print ("Addre: %s\n", g_getenv ("DBUS_SESSION_BUS_ADDRESS"));
loop = g_main_loop_new (NULL, FALSE);
id = g_bus_own_name (G_BUS_TYPE_SESSION,
"org.freedesktop.portal.Desktop",
G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT |
G_BUS_NAME_OWNER_FLAGS_REPLACE,
on_bus_acquired,
on_name_acquired,
on_name_lost,
loop,
NULL);
g_main_loop_run (loop);
g_bus_unown_name (id);
g_main_loop_unref (loop);
return 0;
}

View File

@ -542,7 +542,7 @@ if host_machine.system() != 'windows'
gio_tests += {
'dbus-appinfo' : {
'extra_sources' : extra_sources,
'extra_programs' : ['fake-document-portal'],
'extra_programs' : ['fake-document-portal', 'fake-desktop-portal'],
},
}
endif
@ -560,10 +560,39 @@ if host_machine.system() != 'windows'
'--c-namespace', 'Fake',
'@INPUT@'])
fake_openuri_portal_generated = custom_target('fake-openuri-portal-generated',
input : ['../org.freedesktop.portal.OpenURI.xml'],
output : ['fake-openuri-portal-generated.h',
'fake-openuri-portal-generated.c'],
depend_files : gdbus_codegen_built_files,
depends : gdbus_codegen_built_targets,
command : [python, gdbus_codegen,
'--interface-prefix', 'org.freedesktop.portal.',
'--output-directory', '@OUTDIR@',
'--generate-c-code', 'fake-openuri-portal-generated',
'--c-namespace', 'Fake',
'@INPUT@'])
fake_request_portal_generated = custom_target('fake-request-portal-generated',
input : ['../org.freedesktop.portal.Request.xml'],
output : ['fake-request-portal-generated.h',
'fake-request-portal-generated.c'],
depend_files : gdbus_codegen_built_files,
depends : gdbus_codegen_built_targets,
command : [python, gdbus_codegen,
'--interface-prefix', 'org.freedesktop.portal.',
'--output-directory', '@OUTDIR@',
'--generate-c-code', 'fake-request-portal-generated',
'--c-namespace', 'Fake',
'@INPUT@'])
test_extra_programs += {
'fake-document-portal' : {
'extra_sources': fake_document_portal_generated,
},
'fake-desktop-portal' : {
'extra_sources': [fake_openuri_portal_generated, fake_request_portal_generated]
},
'fake-service-name' : {}
}
endif # have_dbus_daemon

View File

@ -1,5 +1,6 @@
dbus_service_files = [
'org.freedesktop.portal.Documents.service',
'org.freedesktop.portal.Desktop.service',
'org.gtk.GDBus.FakeService.service'
]

View File

@ -0,0 +1,3 @@
[D-BUS Service]
Name=org.freedesktop.portal.Desktop
Exec=@installed_tests_dir@/fake-desktop-portal