diff --git a/.gitignore b/.gitignore index e04163f..0132aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /libportal-0.4.tar.xz /libportal-0.5.tar.xz /libportal-0.6.tar.xz +/libportal-0.7.1.tar.xz diff --git a/0.6-backports.patch b/0.6-backports.patch deleted file mode 100644 index fccfbc1..0000000 --- a/0.6-backports.patch +++ /dev/null @@ -1,1005 +0,0 @@ -From 6a52f680cf4ceda9feb8724793c090cd2258f6f7 Mon Sep 17 00:00:00 2001 -From: Billy -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 -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 -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 -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 -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 -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 -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 - diff --git a/libportal.spec b/libportal.spec index b673d51..60c2cd5 100644 --- a/libportal.spec +++ b/libportal.spec @@ -1,8 +1,8 @@ %bcond qt5 %[%{undefined rhel} || 0%{?rhel} < 10] Name: libportal -Version: 0.6 -Release: 8%{?dist} +Version: 0.7.1 +Release: 1%{?dist} Summary: Flatpak portal library License: LGPLv3 Url: https://github.com/flatpak/libportal @@ -13,7 +13,6 @@ BuildRequires: gcc-c++ BuildRequires: gi-docgen BuildRequires: meson BuildRequires: vala -BuildRequires: git BuildRequires: pkgconfig(gio-2.0) BuildRequires: pkgconfig(gio-unix-2.0) BuildRequires: pkgconfig(gobject-introspection-1.0) @@ -26,9 +25,6 @@ BuildRequires: pkgconfig(Qt5Widgets) BuildRequires: pkgconfig(Qt5X11Extras) %endif -# Backport from post-0.6 fixes -Patch0: 0.6-backports.patch - %description libportal provides GIO-style asynchronous APIs for most Flatpak portals. @@ -116,10 +112,18 @@ libportal provides GIO-style asynchronous APIs for most Flatpak portals. This package provides development documentations for libportal. %prep -%autosetup -p1 -S git_am +%autosetup -p1 %build -%meson -Dbackends=gtk3,gtk4%{?with_qt5:,qt5} +%meson \ + -Dbackend-gtk3=enabled \ + -Dbackend-gtk4=enabled \ +%if %{with qt5} + -Dbackend-qt5=enabled \ +%else + -Dbackend-qt5=disabled \ +%endif + %{nil} %meson_build %install @@ -179,6 +183,9 @@ This package provides development documentations for libportal. %{_datadir}/doc/libportal-1 %changelog +* Thu Sep 21 2023 Kalev Lember - 0.7.1-1 +- Update to 0.7.1 + * Thu Jul 20 2023 Fedora Release Engineering - 0.6-8 - Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild diff --git a/sources b/sources index 11f0d4c..d71ffc8 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (libportal-0.6.tar.xz) = f5f76a0dd0c66521b745c276e41f298addbbd41dd5fb3df9381ea85c122387878cb0e51ffac1e17f9e82548ab222d2c2fe92ab0d018de8a9ae9b2dfd65e454c4 +SHA512 (libportal-0.7.1.tar.xz) = cbc50bfd86787fffc975fc53835acc6c3c0fd54b7ee02fce1983f1bd0fc40b15a0537780cd5e943ecedcf951840080a0f55a23a96e706223e52a6144ee70332c