1006 lines
33 KiB
Diff
1006 lines
33 KiB
Diff
From 6a52f680cf4ceda9feb8724793c090cd2258f6f7 Mon Sep 17 00:00:00 2001
|
|
From: Billy <billyaraujo@gmail.com>
|
|
Date: Tue, 24 May 2022 17:45:59 +0100
|
|
Subject: [PATCH 1/7] Fixed issue where y was used instead of h.
|
|
|
|
---
|
|
portal-test/gtk3/portal-test-win.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/portal-test/gtk3/portal-test-win.c b/portal-test/gtk3/portal-test-win.c
|
|
index 9d50708..e2432c6 100644
|
|
--- a/portal-test/gtk3/portal-test-win.c
|
|
+++ b/portal-test/gtk3/portal-test-win.c
|
|
@@ -594,7 +594,7 @@ session_started (GObject *source,
|
|
g_variant_lookup (props, "size", "(ii)", &w, &h);
|
|
if (s->len > 0)
|
|
g_string_append (s, "\n");
|
|
- g_string_append_printf (s, "Stream %d: %dx%d @ %d,%d", id, w, y, x, y);
|
|
+ g_string_append_printf (s, "Stream %d: %dx%d @ %d,%d", id, w, h, x, y);
|
|
g_variant_unref (props);
|
|
}
|
|
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From a22753772a28e225e4e91b65add10c23ad106243 Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
Date: Fri, 24 Jun 2022 12:58:32 +1000
|
|
Subject: [PATCH 2/7] remote: call the right DBus method for TouchUp
|
|
|
|
---
|
|
libportal/remote.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/libportal/remote.c b/libportal/remote.c
|
|
index e7fb115..ebdffe0 100644
|
|
--- a/libportal/remote.c
|
|
+++ b/libportal/remote.c
|
|
@@ -1160,7 +1160,7 @@ xdp_session_touch_up (XdpSession *session,
|
|
PORTAL_BUS_NAME,
|
|
PORTAL_OBJECT_PATH,
|
|
"org.freedesktop.portal.RemoteDesktop",
|
|
- "NotifyTouchMotion",
|
|
+ "NotifyTouchUp",
|
|
g_variant_new ("(oa{sv}u)", session->id, &options, slot),
|
|
NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
|
|
}
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From 6e25d5cb28412e6a4df553e9f798200b19f1c410 Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
Date: Thu, 30 Jun 2022 14:00:39 +1000
|
|
Subject: [PATCH 3/7] spawn: initialize the option builder
|
|
|
|
../libportal/spawn.c:176:60: warning: variable 'opt_builder' is uninitialized when used here [-Wuninitialized]
|
|
opt_builder),
|
|
---
|
|
libportal/spawn.c | 2 ++
|
|
1 file changed, 2 insertions(+)
|
|
|
|
diff --git a/libportal/spawn.c b/libportal/spawn.c
|
|
index 20ef005..81a03af 100644
|
|
--- a/libportal/spawn.c
|
|
+++ b/libportal/spawn.c
|
|
@@ -131,6 +131,8 @@ do_spawn (SpawnCall *call)
|
|
|
|
ensure_spawn_exited_connection (call->portal);
|
|
|
|
+ g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
|
|
+
|
|
g_variant_builder_init (&fds_builder, G_VARIANT_TYPE ("a{uh}"));
|
|
if (call->n_fds > 0)
|
|
{
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From 030a6164a94c6c173caabcf5a3377189be951474 Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
Date: Thu, 30 Jun 2022 14:06:32 +1000
|
|
Subject: [PATCH 4/7] portal: fix the strcmps on the cgroup hierarchies
|
|
|
|
Fixes
|
|
|
|
../libportal/portal.c:344:12: warning: logical not is only applied
|
|
to the left hand side of this comparison [-Wlogical-not-parentheses]
|
|
!strcmp (controller, ":") != 0) &&
|
|
---
|
|
libportal/portal.c | 7 ++++---
|
|
1 file changed, 4 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/libportal/portal.c b/libportal/portal.c
|
|
index 5e72089..32a34d7 100644
|
|
--- a/libportal/portal.c
|
|
+++ b/libportal/portal.c
|
|
@@ -304,9 +304,10 @@ _xdp_parse_cgroup_file (FILE *f, gboolean *is_snap)
|
|
|
|
/* Only consider the freezer, systemd group or unified cgroup
|
|
* hierarchies */
|
|
- if ((!strcmp (controller, "freezer:") != 0 ||
|
|
- !strcmp (controller, "name=systemd:") != 0 ||
|
|
- !strcmp (controller, ":") != 0) &&
|
|
+ if (controller != NULL &&
|
|
+ (g_str_equal (controller, "freezer:") ||
|
|
+ g_str_equal (controller, "name=systemd:") ||
|
|
+ g_str_equal (controller, ":")) &&
|
|
strstr (cgroup, "/snap.") != NULL)
|
|
{
|
|
*is_snap = TRUE;
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From 953dd354211d70482d9efc54654176ed6bf3bf4e Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
Date: Wed, 29 Jun 2022 15:10:35 +1000
|
|
Subject: [PATCH 5/7] session: replace g_free with g_clear_pointer
|
|
|
|
---
|
|
libportal/session.c | 4 ++--
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/libportal/session.c b/libportal/session.c
|
|
index b505d0b..0b1f02a 100644
|
|
--- a/libportal/session.c
|
|
+++ b/libportal/session.c
|
|
@@ -55,8 +55,8 @@ xdp_session_finalize (GObject *object)
|
|
g_dbus_connection_signal_unsubscribe (session->portal->bus, session->signal_id);
|
|
|
|
g_clear_object (&session->portal);
|
|
- g_free (session->restore_token);
|
|
- g_free (session->id);
|
|
+ g_clear_pointer (&session->restore_token, g_free);
|
|
+ g_clear_pointer (&session->id, g_free);
|
|
g_clear_pointer (&session->streams, g_variant_unref);
|
|
|
|
G_OBJECT_CLASS (xdp_session_parent_class)->finalize (object);
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From f56281857dce8e6515fab6030406112a251ff1e7 Mon Sep 17 00:00:00 2001
|
|
From: Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
|
|
Date: Wed, 12 Oct 2022 13:15:18 -0300
|
|
Subject: [PATCH 6/7] background: Add background status
|
|
|
|
Add the correspondent background status API.
|
|
|
|
See https://github.com/flatpak/xdg-desktop-portal/pull/901
|
|
---
|
|
libportal/background.c | 163 +++++++++++++++++++++++++++++++++++++
|
|
libportal/background.h | 11 +++
|
|
libportal/portal-private.h | 3 +
|
|
3 files changed, 177 insertions(+)
|
|
|
|
diff --git a/libportal/background.c b/libportal/background.c
|
|
index d6c8348..f47570f 100644
|
|
--- a/libportal/background.c
|
|
+++ b/libportal/background.c
|
|
@@ -20,9 +20,116 @@
|
|
|
|
#include "config.h"
|
|
|
|
+#include "session-private.h"
|
|
#include "background.h"
|
|
#include "portal-private.h"
|
|
|
|
+typedef struct {
|
|
+ XdpPortal *portal;
|
|
+ GTask *task;
|
|
+ char *status_message;
|
|
+} SetStatusCall;
|
|
+
|
|
+static void
|
|
+set_status_call_free (SetStatusCall *call)
|
|
+{
|
|
+ g_clear_pointer (&call->status_message, g_free);
|
|
+ g_clear_object (&call->portal);
|
|
+ g_clear_object (&call->task);
|
|
+ g_free (call);
|
|
+}
|
|
+
|
|
+static void
|
|
+set_status_returned (GObject *object,
|
|
+ GAsyncResult *result,
|
|
+ gpointer data)
|
|
+{
|
|
+ SetStatusCall *call = data;
|
|
+ GError *error = NULL;
|
|
+ g_autoptr(GVariant) ret = NULL;
|
|
+
|
|
+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
|
|
+ if (error)
|
|
+ g_task_return_error (call->task, error);
|
|
+ else
|
|
+ g_task_return_boolean (call->task, TRUE);
|
|
+
|
|
+ set_status_call_free (call);
|
|
+}
|
|
+
|
|
+static void
|
|
+set_status (SetStatusCall *call)
|
|
+{
|
|
+ GVariantBuilder options;
|
|
+
|
|
+ g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
|
|
+
|
|
+ if (call->status_message)
|
|
+ g_variant_builder_add (&options, "{sv}", "message", g_variant_new_string (call->status_message));
|
|
+
|
|
+ g_dbus_connection_call (call->portal->bus,
|
|
+ PORTAL_BUS_NAME,
|
|
+ PORTAL_OBJECT_PATH,
|
|
+ "org.freedesktop.portal.Background",
|
|
+ "SetStatus",
|
|
+ g_variant_new ("(a{sv})", &options),
|
|
+ NULL,
|
|
+ G_DBUS_CALL_FLAGS_NONE,
|
|
+ -1,
|
|
+ g_task_get_cancellable (call->task),
|
|
+ set_status_returned,
|
|
+ call);
|
|
+}
|
|
+
|
|
+static void
|
|
+get_background_version_returned (GObject *object,
|
|
+ GAsyncResult *result,
|
|
+ gpointer data)
|
|
+{
|
|
+ g_autoptr(GVariant) version_variant = NULL;
|
|
+ g_autoptr(GVariant) ret = NULL;
|
|
+ SetStatusCall *call = data;
|
|
+ GError *error = NULL;
|
|
+
|
|
+ ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
|
|
+ if (error)
|
|
+ {
|
|
+ g_task_return_error (call->task, error);
|
|
+ set_status_call_free (call);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ g_variant_get_child (ret, 0, "v", &version_variant);
|
|
+ call->portal->background_interface_version = g_variant_get_uint32 (version_variant);
|
|
+
|
|
+ if (call->portal->background_interface_version < 2)
|
|
+ {
|
|
+ g_task_return_new_error (call->task, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
|
|
+ "Background portal does not implement version 2 of the interface");
|
|
+ set_status_call_free (call);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ set_status (call);
|
|
+}
|
|
+
|
|
+static void
|
|
+get_background_interface_version (SetStatusCall *call)
|
|
+{
|
|
+ g_dbus_connection_call (call->portal->bus,
|
|
+ PORTAL_BUS_NAME,
|
|
+ PORTAL_OBJECT_PATH,
|
|
+ "org.freedesktop.DBus.Properties",
|
|
+ "Get",
|
|
+ g_variant_new ("(ss)", "org.freedesktop.portal.Background", "version"),
|
|
+ NULL,
|
|
+ G_DBUS_CALL_FLAGS_NONE,
|
|
+ -1,
|
|
+ g_task_get_cancellable (call->task),
|
|
+ get_background_version_returned,
|
|
+ call);
|
|
+}
|
|
+
|
|
typedef struct {
|
|
XdpPortal *portal;
|
|
XdpParent *parent;
|
|
@@ -282,3 +389,59 @@ xdp_portal_request_background_finish (XdpPortal *portal,
|
|
|
|
return g_task_propagate_boolean (G_TASK (result), error);
|
|
}
|
|
+
|
|
+/**
|
|
+ * xdp_portal_set_background_status:
|
|
+ * @portal: a [class@Portal]
|
|
+ * @status_message: (nullable): status message when running in background
|
|
+ * @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
|
|
+ *
|
|
+ * Sets the status information of the application, for when it's running
|
|
+ * in background.
|
|
+ */
|
|
+void
|
|
+xdp_portal_set_background_status (XdpPortal *portal,
|
|
+ const char *status_message,
|
|
+ GCancellable *cancellable,
|
|
+ GAsyncReadyCallback callback,
|
|
+ gpointer data)
|
|
+{
|
|
+ SetStatusCall *call;
|
|
+
|
|
+ g_return_if_fail (XDP_IS_PORTAL (portal));
|
|
+
|
|
+ call = g_new0 (SetStatusCall, 1);
|
|
+ call->portal = g_object_ref (portal);
|
|
+ call->status_message = g_strdup (status_message);
|
|
+ call->task = g_task_new (portal, cancellable, callback, data);
|
|
+ g_task_set_source_tag (call->task, xdp_portal_set_background_status);
|
|
+
|
|
+ if (portal->background_interface_version == 0)
|
|
+ get_background_interface_version (call);
|
|
+ else
|
|
+ set_status (call);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * xdp_portal_set_background_status_finish:
|
|
+ * @portal: a [class@Portal]
|
|
+ * @result: a [iface@Gio.AsyncResult]
|
|
+ * @error: return location for an error
|
|
+ *
|
|
+ * Finishes setting the background status of the application.
|
|
+ *
|
|
+ * Returns: %TRUE if successfully set status, %FALSE otherwise
|
|
+ */
|
|
+gboolean
|
|
+xdp_portal_set_background_status_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_set_background_status, FALSE);
|
|
+
|
|
+ return g_task_propagate_boolean (G_TASK (result), error);
|
|
+}
|
|
diff --git a/libportal/background.h b/libportal/background.h
|
|
index a22090d..5ce1734 100644
|
|
--- a/libportal/background.h
|
|
+++ b/libportal/background.h
|
|
@@ -52,5 +52,16 @@ gboolean xdp_portal_request_background_finish (XdpPortal *portal,
|
|
GAsyncResult *result,
|
|
GError **error);
|
|
|
|
+XDP_PUBLIC
|
|
+void xdp_portal_set_background_status (XdpPortal *portal,
|
|
+ const char *status_message,
|
|
+ GCancellable *cancellable,
|
|
+ GAsyncReadyCallback callback,
|
|
+ gpointer data);
|
|
+
|
|
+XDP_PUBLIC
|
|
+gboolean xdp_portal_set_background_status_finish (XdpPortal *portal,
|
|
+ GAsyncResult *result,
|
|
+ GError **error);
|
|
|
|
G_END_DECLS
|
|
diff --git a/libportal/portal-private.h b/libportal/portal-private.h
|
|
index 6728055..542e1bb 100644
|
|
--- a/libportal/portal-private.h
|
|
+++ b/libportal/portal-private.h
|
|
@@ -51,6 +51,9 @@ struct _XdpPortal {
|
|
|
|
/* screencast */
|
|
guint screencast_interface_version;
|
|
+
|
|
+ /* background */
|
|
+ guint background_interface_version;
|
|
};
|
|
|
|
#define PORTAL_BUS_NAME "org.freedesktop.portal.Desktop"
|
|
--
|
|
2.39.0
|
|
|
|
|
|
From 631a16363236fba681ad848166619e14f0cf5637 Mon Sep 17 00:00:00 2001
|
|
From: Peter Hutterer <peter.hutterer@who-t.net>
|
|
Date: Thu, 26 May 2022 12:49:50 +1000
|
|
Subject: [PATCH 7/7] test: add a pytest/dbusmock-based test suite
|
|
|
|
Using python and dbusmock makes it trivial to add a large number of
|
|
tests for libportal only, without requiring an actual portal
|
|
implementation for the Portal interface to be tested.
|
|
|
|
Included here is the wallpaper portal as an example, hooked into meson test.
|
|
A helper script is provided too for those lacking meson devenv,
|
|
$ ./test/gir-testenv.sh
|
|
$ cd test
|
|
$ pytest --verbose --log-level=DEBUG [... other pytest arguments ...]
|
|
|
|
The test setup uses dbusmock interface templates (see
|
|
pyportaltest/templates) to handle the actual DBus calls.
|
|
|
|
Because DBus uses a singleton for the session bus, we need libportal to
|
|
specifically connect to the address given in the environment - otherwise
|
|
starting mock dbus services has no effect.
|
|
|
|
This test suite depends on dbusmock commit 4a191d8ba293:
|
|
"mockobject: allow sending signals with extra details" from
|
|
https://github.com/martinpitt/python-dbusmock/pull/129
|
|
|
|
Without this, the EmitSignalDetailed() method does not exist/work, but
|
|
without this method we cannot receive signals.
|
|
---
|
|
.github/workflows/build.yml | 6 +-
|
|
libportal/portal.c | 37 +++++-
|
|
tests/gir-testenv.sh | 31 +++++
|
|
tests/meson.build | 19 +++
|
|
tests/pyportaltest/__init__.py | 149 ++++++++++++++++++++++
|
|
tests/pyportaltest/templates/__init__.py | 94 ++++++++++++++
|
|
tests/pyportaltest/templates/wallpaper.py | 48 +++++++
|
|
tests/pyportaltest/test_wallpaper.py | 117 +++++++++++++++++
|
|
8 files changed, 497 insertions(+), 4 deletions(-)
|
|
create mode 100755 tests/gir-testenv.sh
|
|
create mode 100644 tests/pyportaltest/__init__.py
|
|
create mode 100644 tests/pyportaltest/templates/__init__.py
|
|
create mode 100644 tests/pyportaltest/templates/wallpaper.py
|
|
create mode 100644 tests/pyportaltest/test_wallpaper.py
|
|
|
|
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
|
|
index 66d9fb4..133a998 100644
|
|
--- a/.github/workflows/build.yml
|
|
+++ b/.github/workflows/build.yml
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
- name: Install dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
- sudo apt-get install -y libglib2.0 gettext dbus meson libgirepository1.0-dev libgtk-3-dev valac
|
|
+ sudo apt-get install -y libglib2.0 gettext dbus meson libgirepository1.0-dev libgtk-3-dev valac python3-pytest python3-dbusmock
|
|
- name: Check out libportal
|
|
uses: actions/checkout@v1
|
|
- name: Configure libportal
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
- name: Install dependencies
|
|
run: |
|
|
apt-get update
|
|
- apt-get install -y libglib2.0 gettext dbus meson libgirepository1.0-dev libgtk-3-dev libgtk-4-dev valac python3-pip
|
|
+ apt-get install -y libglib2.0 gettext dbus meson libgirepository1.0-dev libgtk-3-dev libgtk-4-dev valac python3-pip python3-dbusmock
|
|
pip3 install gi-docgen
|
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
|
- name: Check out libportal
|
|
@@ -73,7 +73,7 @@ jobs:
|
|
steps:
|
|
- name: Install dependencies
|
|
run: |
|
|
- dnf install -y meson gcc gobject-introspection-devel gtk3-devel gtk4-devel gi-docgen vala git
|
|
+ dnf install -y meson gcc gobject-introspection-devel gtk3-devel gtk4-devel gi-docgen vala git python3-pytest python3-dbusmock
|
|
- name: Check out libportal
|
|
uses: actions/checkout@v1
|
|
- name: Configure libportal
|
|
diff --git a/libportal/portal.c b/libportal/portal.c
|
|
index 32a34d7..7765bc7 100644
|
|
--- a/libportal/portal.c
|
|
+++ b/libportal/portal.c
|
|
@@ -254,12 +254,47 @@ xdp_portal_class_init (XdpPortalClass *klass)
|
|
G_TYPE_VARIANT);
|
|
}
|
|
|
|
+static GDBusConnection *
|
|
+create_bus_from_address (const char *address,
|
|
+ GError **error)
|
|
+{
|
|
+ g_autoptr(GDBusConnection) bus = NULL;
|
|
+
|
|
+ if (!address)
|
|
+ {
|
|
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Missing D-Bus session bus address");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ bus = g_dbus_connection_new_for_address_sync (address,
|
|
+ G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
|
|
+ G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
|
|
+ NULL, NULL,
|
|
+ error);
|
|
+ return g_steal_pointer (&bus);
|
|
+}
|
|
+
|
|
static void
|
|
xdp_portal_init (XdpPortal *portal)
|
|
{
|
|
+ g_autoptr(GError) error = NULL;
|
|
int i;
|
|
|
|
- portal->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
+ /* g_bus_get_sync() returns a singleton. In the test suite we may restart
|
|
+ * the session bus, so we have to manually connect to the new bus */
|
|
+ if (getenv ("LIBPORTAL_TEST_SUITE"))
|
|
+ portal->bus = create_bus_from_address (getenv ("DBUS_SESSION_BUS_ADDRESS"), &error);
|
|
+ else
|
|
+ portal->bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
|
+
|
|
+ if (error)
|
|
+ {
|
|
+ g_critical ("Failed to create XdpPortal instance: %s\n", error->message);
|
|
+ abort ();
|
|
+ }
|
|
+
|
|
+ g_assert (portal->bus != NULL);
|
|
+
|
|
portal->sender = g_strdup (g_dbus_connection_get_unique_name (portal->bus) + 1);
|
|
for (i = 0; portal->sender[i]; i++)
|
|
if (portal->sender[i] == '.')
|
|
diff --git a/tests/gir-testenv.sh b/tests/gir-testenv.sh
|
|
new file mode 100755
|
|
index 0000000..6cb8e47
|
|
--- /dev/null
|
|
+++ b/tests/gir-testenv.sh
|
|
@@ -0,0 +1,31 @@
|
|
+#!/bin/sh
|
|
+#
|
|
+# Wrapper to set up the right environment variables and start a nested
|
|
+# shell. Usage:
|
|
+#
|
|
+# $ ./tests/gir-testenv.sh
|
|
+# (nested shell) $ pytest
|
|
+# (nested shell) $ exit
|
|
+#
|
|
+# If you have meson 0.58 or later, you can instead do:
|
|
+# $ meson devenv -C builddir
|
|
+# (nested shell) $ cd ../tests
|
|
+# (nested shell) $ pytest
|
|
+# (nested shell) $ exit
|
|
+#
|
|
+
|
|
+builddir=$(find $PWD -name meson-logs -printf "%h" -quit)
|
|
+
|
|
+if [ -z "$builddir" ]; then
|
|
+ echo "Unable to find meson builddir"
|
|
+ exit 1
|
|
+fi
|
|
+
|
|
+echo "Using meson builddir: $builddir"
|
|
+
|
|
+export LD_LIBRARY_PATH="$builddir/libportal:$LD_LIBRARY_PATH"
|
|
+export GI_TYPELIB_PATH="$builddir/libportal:$GI_TYPELIB_PATH"
|
|
+
|
|
+echo "pytest must be run from within the tests/ directory"
|
|
+# Don't think this is portable, but oh well
|
|
+${SHELL}
|
|
diff --git a/tests/meson.build b/tests/meson.build
|
|
index ffc415f..0c67335 100644
|
|
--- a/tests/meson.build
|
|
+++ b/tests/meson.build
|
|
@@ -1,3 +1,22 @@
|
|
if 'qt5' in backends
|
|
subdir('qt5')
|
|
endif
|
|
+
|
|
+if meson.version().version_compare('>= 0.56.0')
|
|
+ pytest = find_program('pytest-3', 'pytest', required: false)
|
|
+ pymod = import('python')
|
|
+ python = pymod.find_installation('python3', modules: ['dbus', 'dbusmock'], required: false)
|
|
+
|
|
+ if pytest.found() and python.found()
|
|
+ test_env = environment()
|
|
+ test_env.set('LD_LIBRARY_PATH', meson.project_build_root() / 'libportal')
|
|
+ test_env.set('GI_TYPELIB_PATH', meson.project_build_root() / 'libportal')
|
|
+
|
|
+ test('pytest',
|
|
+ pytest,
|
|
+ args: ['--verbose', '--log-level=DEBUG'],
|
|
+ env: test_env,
|
|
+ workdir: meson.current_source_dir()
|
|
+ )
|
|
+ endif
|
|
+endif
|
|
diff --git a/tests/pyportaltest/__init__.py b/tests/pyportaltest/__init__.py
|
|
new file mode 100644
|
|
index 0000000..e298612
|
|
--- /dev/null
|
|
+++ b/tests/pyportaltest/__init__.py
|
|
@@ -0,0 +1,149 @@
|
|
+# SPDX-License-Identifier: LGPL-3.0-only
|
|
+#
|
|
+# This file is formatted with Python Black
|
|
+
|
|
+from typing import Any, Dict, List, Tuple
|
|
+
|
|
+import gi
|
|
+from gi.repository import GLib
|
|
+from dbus.mainloop.glib import DBusGMainLoop
|
|
+
|
|
+import dbus
|
|
+import dbusmock
|
|
+import fcntl
|
|
+import logging
|
|
+import os
|
|
+import pytest
|
|
+import subprocess
|
|
+
|
|
+logging.basicConfig(format="%(levelname)s | %(name)s: %(message)s", level=logging.DEBUG)
|
|
+logger = logging.getLogger("pyportaltest")
|
|
+
|
|
+DBusGMainLoop(set_as_default=True)
|
|
+
|
|
+# Uncomment this to have dbus-monitor listen on the normal session address
|
|
+# rather than the test DBus. This can be useful for cases where *something*
|
|
+# messes up and tests run against the wrong bus.
|
|
+#
|
|
+# session_dbus_address = os.environ["DBUS_SESSION_BUS_ADDRESS"]
|
|
+
|
|
+
|
|
+def start_dbus_monitor() -> "subprocess.Process":
|
|
+ import subprocess
|
|
+
|
|
+ env = os.environ.copy()
|
|
+ try:
|
|
+ env["DBUS_SESSION_BUS_ADDRESS"] = session_dbus_address
|
|
+ except NameError:
|
|
+ # See comment above
|
|
+ pass
|
|
+
|
|
+ argv = ["dbus-monitor", "--session"]
|
|
+ mon = subprocess.Popen(argv, env=env)
|
|
+
|
|
+ def stop_dbus_monitor():
|
|
+ mon.terminate()
|
|
+ mon.wait()
|
|
+
|
|
+ GLib.timeout_add(2000, stop_dbus_monitor)
|
|
+ return mon
|
|
+
|
|
+
|
|
+class PortalTest(dbusmock.DBusTestCase):
|
|
+ """
|
|
+ Parent class for portal tests. Subclass from this and name it after the
|
|
+ portal, e.g. ``TestWallpaper``.
|
|
+
|
|
+ .. attribute:: portal_interface
|
|
+
|
|
+ The :class:`dbus.Interface` referring to our portal
|
|
+
|
|
+ .. attribute:: properties_interface
|
|
+
|
|
+ A convenience :class:`dbus.Interface` referring to the DBus Properties
|
|
+ interface, call ``Get``, ``Set`` or ``GetAll`` on this interface to
|
|
+ retrieve the matching property/properties.
|
|
+
|
|
+ .. attribute:: mock_interface
|
|
+
|
|
+ The DBusMock :class:`dbus.Interface` that controls our DBus
|
|
+ appearance.
|
|
+
|
|
+ """
|
|
+ @classmethod
|
|
+ def setUpClass(cls):
|
|
+ if cls.__name__ != "PortalTest":
|
|
+ cls.PORTAL_NAME = cls.__name__.removeprefix("Test")
|
|
+ cls.INTERFACE_NAME = f"org.freedesktop.portal.{cls.PORTAL_NAME}"
|
|
+ os.environ["LIBPORTAL_TEST_SUITE"] = "1"
|
|
+
|
|
+ try:
|
|
+ dbusmock.mockobject.DBusMockObject.EmitSignalDetailed
|
|
+ except AttributeError:
|
|
+ pytest.skip("Updated version of dbusmock required")
|
|
+
|
|
+ def setUp(self):
|
|
+ self.p_mock = None
|
|
+ self._mainloop = None
|
|
+ self.dbus_monitor = None
|
|
+
|
|
+ def setup_daemon(self, params=None):
|
|
+ """
|
|
+ Start a DBusMock daemon in a separate process
|
|
+ """
|
|
+ self.start_session_bus()
|
|
+ self.p_mock, self.obj_portal = self.spawn_server_template(
|
|
+ template=f"pyportaltest/templates/{self.PORTAL_NAME.lower()}.py",
|
|
+ parameters=params,
|
|
+ stdout=subprocess.PIPE,
|
|
+ )
|
|
+ flags = fcntl.fcntl(self.p_mock.stdout, fcntl.F_GETFL)
|
|
+ fcntl.fcntl(self.p_mock.stdout, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
+ self.mock_interface = dbus.Interface(self.obj_portal, dbusmock.MOCK_IFACE)
|
|
+ self.properties_interface = dbus.Interface(
|
|
+ self.obj_portal, dbus.PROPERTIES_IFACE
|
|
+ )
|
|
+ self.portal_interface = dbus.Interface(self.obj_portal, self.INTERFACE_NAME)
|
|
+
|
|
+ self.dbus_monitor = start_dbus_monitor()
|
|
+
|
|
+ def tearDown(self):
|
|
+ if self.p_mock:
|
|
+ if self.p_mock.stdout:
|
|
+ out = (self.p_mock.stdout.read() or b"").decode("utf-8")
|
|
+ if out:
|
|
+ print(out)
|
|
+ self.p_mock.stdout.close()
|
|
+ self.p_mock.terminate()
|
|
+ self.p_mock.wait()
|
|
+
|
|
+ if self.dbus_monitor:
|
|
+ self.dbus_monitor.terminate()
|
|
+ self.dbus_monitor.wait()
|
|
+
|
|
+ @property
|
|
+ def mainloop(self):
|
|
+ """
|
|
+ The mainloop for this test. This mainloop automatically quits after a
|
|
+ fixed timeout, but only on the first run. That's usually enough for
|
|
+ tests, if you need to call mainloop.run() repeatedly ensure that a
|
|
+ timeout handler is set to ensure quick test case failure in case of
|
|
+ error.
|
|
+ """
|
|
+ if self._mainloop is None:
|
|
+
|
|
+ def quit():
|
|
+ self._mainloop.quit()
|
|
+ self._mainloop = None
|
|
+
|
|
+ self._mainloop = GLib.MainLoop()
|
|
+ GLib.timeout_add(2000, quit)
|
|
+
|
|
+ return self._mainloop
|
|
+
|
|
+ def assert_version_eq(self, version: int):
|
|
+ """Assert the given version number is the one our portal exports"""
|
|
+ interface_name = self.INTERFACE_NAME
|
|
+ params = {}
|
|
+ self.setup_daemon(params)
|
|
+ assert self.properties_interface.Get(interface_name, "version") == version
|
|
diff --git a/tests/pyportaltest/templates/__init__.py b/tests/pyportaltest/templates/__init__.py
|
|
new file mode 100644
|
|
index 0000000..c94a5cd
|
|
--- /dev/null
|
|
+++ b/tests/pyportaltest/templates/__init__.py
|
|
@@ -0,0 +1,94 @@
|
|
+# SPDX-License-Identifier: LGPL-3.0-only
|
|
+#
|
|
+# This file is formatted with Python Black
|
|
+
|
|
+from dbusmock import DBusMockObject
|
|
+from typing import Dict, Any, NamedTuple, Optional
|
|
+from itertools import count
|
|
+from gi.repository import GLib
|
|
+
|
|
+import dbus
|
|
+import logging
|
|
+
|
|
+
|
|
+ASVType = Dict[str, Any]
|
|
+
|
|
+logging.basicConfig(format="%(levelname).1s|%(name)s: %(message)s", level=logging.DEBUG)
|
|
+logger = logging.getLogger("templates")
|
|
+
|
|
+
|
|
+class Response(NamedTuple):
|
|
+ response: int
|
|
+ results: ASVType
|
|
+
|
|
+
|
|
+class Request:
|
|
+ _token_counter = count()
|
|
+
|
|
+ def __init__(
|
|
+ self, bus_name: dbus.service.BusName, sender: str, options: Optional[ASVType]
|
|
+ ):
|
|
+ options = options or {}
|
|
+ sender_token = sender.removeprefix(":").replace(".", "_")
|
|
+ handle_token = options.get("handle_token", next(self._token_counter))
|
|
+ self.sender = sender
|
|
+ self.handle = (
|
|
+ f"/org/freedesktop/portal/desktop/request/{sender_token}/{handle_token}"
|
|
+ )
|
|
+ self.mock = DBusMockObject(
|
|
+ bus_name=bus_name,
|
|
+ path=self.handle,
|
|
+ interface="org.freedesktop.portal.Request",
|
|
+ props={},
|
|
+ )
|
|
+ self.mock.AddMethod("", "Close", "", "", "self.RemoveObject(self.path)")
|
|
+
|
|
+ def respond(self, response: Response, delay: int = 0):
|
|
+ def respond():
|
|
+ logger.debug(f"Request.Response on {self.handle}: {response}")
|
|
+ self.mock.EmitSignalDetailed(
|
|
+ "",
|
|
+ "Response",
|
|
+ "ua{sv}",
|
|
+ [dbus.UInt32(response.response), response.results],
|
|
+ details={"destination": self.sender},
|
|
+ )
|
|
+
|
|
+ if delay > 0:
|
|
+ GLib.timeout_add(delay, respond)
|
|
+ else:
|
|
+ respond()
|
|
+
|
|
+
|
|
+class Session:
|
|
+ _token_counter = count()
|
|
+
|
|
+ def __init__(
|
|
+ self, bus_name: dbus.service.BusName, sender: str, options: Optional[ASVType]
|
|
+ ):
|
|
+ options = options or {}
|
|
+ sender_token = sender.removeprefix(":").replace(".", "_")
|
|
+ handle_token = options.get("session_handle_token", next(self._token_counter))
|
|
+ self.sender = sender
|
|
+ self.handle = (
|
|
+ f"/org/freedesktop/portal/desktop/session/{sender_token}/{handle_token}"
|
|
+ )
|
|
+ self.mock = DBusMockObject(
|
|
+ bus_name=bus_name,
|
|
+ path=self.handle,
|
|
+ interface="org.freedesktop.portal.Session",
|
|
+ props={},
|
|
+ )
|
|
+ self.mock.AddMethod("", "Close", "", "", "self.RemoveObject(self.path)")
|
|
+
|
|
+ def close(self, details: ASVType, delay: int = 0):
|
|
+ def respond():
|
|
+ logger.debug(f"Session.Closed on {self.handle}: {details}")
|
|
+ self.mock.EmitSignalDetailed(
|
|
+ "", "Closed", "a{sv}", [details], destination=self.sender
|
|
+ )
|
|
+
|
|
+ if delay > 0:
|
|
+ GLib.timeout_add(delay, respond)
|
|
+ else:
|
|
+ respond()
|
|
diff --git a/tests/pyportaltest/templates/wallpaper.py b/tests/pyportaltest/templates/wallpaper.py
|
|
new file mode 100644
|
|
index 0000000..f0371b0
|
|
--- /dev/null
|
|
+++ b/tests/pyportaltest/templates/wallpaper.py
|
|
@@ -0,0 +1,48 @@
|
|
+# SPDX-License-Identifier: LGPL-3.0-only
|
|
+#
|
|
+# This file is formatted with Python Black
|
|
+
|
|
+from pyportaltest.templates import Request, Response, ASVType
|
|
+from typing import Dict, List, Tuple, Iterator
|
|
+
|
|
+import dbus.service
|
|
+import logging
|
|
+
|
|
+logger = logging.getLogger(f"templates.{__name__}")
|
|
+
|
|
+BUS_NAME = "org.freedesktop.portal.Desktop"
|
|
+MAIN_OBJ = "/org/freedesktop/portal/desktop"
|
|
+SYSTEM_BUS = False
|
|
+MAIN_IFACE = "org.freedesktop.portal.Wallpaper"
|
|
+
|
|
+
|
|
+def load(mock, parameters=None):
|
|
+ logger.debug(f"loading {MAIN_IFACE} template")
|
|
+ mock.delay = 500
|
|
+
|
|
+ mock.response = parameters.get("response", 0)
|
|
+
|
|
+ mock.AddProperties(
|
|
+ MAIN_IFACE,
|
|
+ dbus.Dictionary({"version": dbus.UInt32(parameters.get("version", 1))}),
|
|
+ )
|
|
+
|
|
+
|
|
+@dbus.service.method(
|
|
+ MAIN_IFACE,
|
|
+ sender_keyword="sender",
|
|
+ in_signature="ssa{sv}",
|
|
+ out_signature="o",
|
|
+)
|
|
+def SetWallpaperURI(self, parent_window, uri, options, sender):
|
|
+ try:
|
|
+ logger.debug(f"SetWallpaperURI: {parent_window}, {uri}, {options}")
|
|
+ request = Request(bus_name=self.bus_name, sender=sender, options=options)
|
|
+
|
|
+ response = Response(self.response, {})
|
|
+
|
|
+ request.respond(response, delay=self.delay)
|
|
+
|
|
+ return request.handle
|
|
+ except Exception as e:
|
|
+ logger.critical(e)
|
|
diff --git a/tests/pyportaltest/test_wallpaper.py b/tests/pyportaltest/test_wallpaper.py
|
|
new file mode 100644
|
|
index 0000000..def66fc
|
|
--- /dev/null
|
|
+++ b/tests/pyportaltest/test_wallpaper.py
|
|
@@ -0,0 +1,117 @@
|
|
+# SPDX-License-Identifier: LGPL-3.0-only
|
|
+#
|
|
+# This file is formatted with Python Black
|
|
+
|
|
+from . import PortalTest
|
|
+
|
|
+import gi
|
|
+import logging
|
|
+
|
|
+gi.require_version("Xdp", "1.0")
|
|
+from gi.repository import GLib, Xdp
|
|
+
|
|
+logger = logging.getLogger(__name__)
|
|
+
|
|
+
|
|
+class TestWallpaper(PortalTest):
|
|
+ def test_version(self):
|
|
+ self.assert_version_eq(1)
|
|
+
|
|
+ def set_wallpaper(
|
|
+ self, uri_to_set: str, set_on: Xdp.WallpaperFlags, show_preview: bool
|
|
+ ):
|
|
+ params = {}
|
|
+ self.setup_daemon(params)
|
|
+
|
|
+ xdp = Xdp.Portal.new()
|
|
+ assert xdp is not None
|
|
+
|
|
+ flags = {
|
|
+ "background": Xdp.WallpaperFlags.BACKGROUND,
|
|
+ "lockscreen": Xdp.WallpaperFlags.LOCKSCREEN,
|
|
+ "both": Xdp.WallpaperFlags.BACKGROUND | Xdp.WallpaperFlags.LOCKSCREEN,
|
|
+ }[set_on]
|
|
+
|
|
+ if show_preview:
|
|
+ flags |= Xdp.WallpaperFlags.PREVIEW
|
|
+
|
|
+ wallpaper_was_set = False
|
|
+
|
|
+ def set_wallpaper_done(portal, task, data):
|
|
+ nonlocal wallpaper_was_set
|
|
+ wallpaper_was_set = portal.set_wallpaper_finish(task)
|
|
+ self.mainloop.quit()
|
|
+
|
|
+ xdp.set_wallpaper(
|
|
+ parent=None,
|
|
+ uri=uri_to_set,
|
|
+ flags=flags,
|
|
+ cancellable=None,
|
|
+ callback=set_wallpaper_done,
|
|
+ data=None,
|
|
+ )
|
|
+
|
|
+ self.mainloop.run()
|
|
+
|
|
+ method_calls = self.mock_interface.GetMethodCalls("SetWallpaperURI")
|
|
+ assert len(method_calls) == 1
|
|
+ timestamp, args = method_calls.pop(0)
|
|
+ parent, uri, options = args
|
|
+ assert uri == uri_to_set
|
|
+ assert options["set-on"] == set_on
|
|
+ assert options["show-preview"] == show_preview
|
|
+
|
|
+ assert wallpaper_was_set
|
|
+
|
|
+ def test_set_wallpaper_background(self):
|
|
+ self.set_wallpaper("https://background.nopreview", "background", False)
|
|
+
|
|
+ def test_set_wallpaper_background_preview(self):
|
|
+ self.set_wallpaper("https://background.preview", "background", True)
|
|
+
|
|
+ def test_set_wallpaper_lockscreen(self):
|
|
+ self.set_wallpaper("https://lockscreen.nopreview", "lockscreen", False)
|
|
+
|
|
+ def test_set_wallpaper_lockscreen_preview(self):
|
|
+ self.set_wallpaper("https://lockscreen.preview", "lockscreen", True)
|
|
+
|
|
+ def test_set_wallpaper_both(self):
|
|
+ self.set_wallpaper("https://both.nopreview", "both", False)
|
|
+
|
|
+ def test_set_wallpaper_both_preview(self):
|
|
+ self.set_wallpaper("https://both.preview", "both", True)
|
|
+
|
|
+ def test_set_wallpaper_cancel(self):
|
|
+ params = {"response": 1}
|
|
+ self.setup_daemon(params)
|
|
+
|
|
+ xdp = Xdp.Portal.new()
|
|
+ assert xdp is not None
|
|
+
|
|
+ flags = Xdp.WallpaperFlags.BACKGROUND
|
|
+
|
|
+ wallpaper_was_set = False
|
|
+
|
|
+ def set_wallpaper_done(portal, task, data):
|
|
+ nonlocal wallpaper_was_set
|
|
+ try:
|
|
+ wallpaper_was_set = portal.set_wallpaper_finish(task)
|
|
+ except GLib.GError:
|
|
+ pass
|
|
+ self.mainloop.quit()
|
|
+
|
|
+ xdp.set_wallpaper(
|
|
+ parent=None,
|
|
+ uri="https://ignored.anyway",
|
|
+ flags=flags,
|
|
+ cancellable=None,
|
|
+ callback=set_wallpaper_done,
|
|
+ data=None,
|
|
+ )
|
|
+
|
|
+ self.mainloop.run()
|
|
+
|
|
+ method_calls = self.mock_interface.GetMethodCalls("SetWallpaperURI")
|
|
+ assert len(method_calls) == 1
|
|
+
|
|
+ assert not wallpaper_was_set
|
|
--
|
|
2.39.0
|
|
|