From a0e2a73925f30316c4536b974cf8f728d384a5f0 Mon Sep 17 00:00:00 2001 From: Kyle Gospodnetich Date: Thu, 22 Aug 2024 10:01:43 -0700 Subject: [PATCH] chore: Update upower chore: Remove some unneeded packages --- README.md | 2 - spec_files/duperemove/duperemove.spec | 118 - ...-center-initial-setup-fedora-welcome.patch | 28 - ...tor-Special-case-shaped-Java-windows.patch | 70 - spec_files/mutter/1441.patch | 1868 ----------- spec_files/mutter/3720+3567.patch | 2012 ------------ .../mutter-42.alpha-disable-tegra.patch | 25 - spec_files/mutter/mutter.spec | 211 -- spec_files/upower/upower.spec | 2 +- spec_files/vkroots/vkroots.spec | 57 - spec_files/wireplumber/valve.patch | 2813 ----------------- spec_files/wireplumber/wireplumber.spec | 272 -- 12 files changed, 1 insertion(+), 7477 deletions(-) delete mode 100644 spec_files/duperemove/duperemove.spec delete mode 100644 spec_files/mutter/0001-place-Always-center-initial-setup-fedora-welcome.patch delete mode 100644 spec_files/mutter/0001-window-actor-Special-case-shaped-Java-windows.patch delete mode 100644 spec_files/mutter/1441.patch delete mode 100644 spec_files/mutter/3720+3567.patch delete mode 100644 spec_files/mutter/mutter-42.alpha-disable-tegra.patch delete mode 100644 spec_files/mutter/mutter.spec delete mode 100644 spec_files/vkroots/vkroots.spec delete mode 100644 spec_files/wireplumber/valve.patch delete mode 100644 spec_files/wireplumber/wireplumber.spec diff --git a/README.md b/README.md index d5734674..396fac5b 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,6 @@ Ported SteamOS and ChimeraOS packages, among others used by Bazzite, are built o | Package | Status | | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | ds-inhibit | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/ds-inhibit/status_image/last_build.png?) | -| duperemove | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/duperemove/status_image/last_build.png?) | | [extest](https://github.com/Supreeeme/extest) | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite-multilib/package/extest/status_image/last_build.png?) | | gamescope | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite-multilib/package/gamescope/status_image/last_build.png?) | | [gamescope-session-plus](https://github.com/ChimeraOS/gamescope-session) | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/gamescope-session-plus/status_image/last_build.png?) | @@ -287,7 +286,6 @@ Ported SteamOS and ChimeraOS packages, among others used by Bazzite, are built o | [umu-launcher](https://github.com/Open-Wine-Components/umu-launcher) | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/umu-launcher/status_image/last_build.png?) | | upower | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/upower/status_image/last_build.png?) | | vpower | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/vpower/status_image/last_build.png?) | -| wireplumber | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/wireplumber/status_image/last_build.png?) | | [xwiimote-ng](https://github.com/dev-0x7C6/xwiimote-ng) | ![Build Status](https://copr.fedorainfracloud.org/coprs/kylegospo/bazzite/package/xwiimote-ng/status_image/last_build.png?) | Additionally, the following packages are used from other Copr repos: diff --git a/spec_files/duperemove/duperemove.spec b/spec_files/duperemove/duperemove.spec deleted file mode 100644 index 47e126ff..00000000 --- a/spec_files/duperemove/duperemove.spec +++ /dev/null @@ -1,118 +0,0 @@ -%define _legacy_common_support 1 - -Name: duperemove -Version: 0.14.1 -Release: 5%{?dist} -Summary: Tools for deduping file systems -License: GPL-2.0-only -URL: https://github.com/markfasheh/%{name} -BuildRequires: pkgconfig(glib-2.0) -BuildRequires: pkgconfig(sqlite3) -BuildRequires: pkgconfig(uuid) -BuildRequires: libgcrypt-devel -BuildRequires: xxhash-devel -BuildRequires: libatomic -BuildRequires: gcc -BuildRequires: git -BuildRequires: pandoc -# Enable devtoolset so we get libatomic in EPEL-7 -%if 0%{?el7} -BuildRequires: devtoolset-7-toolchain, devtoolset-7-libatomic-devel -%endif -BuildRequires: make - -%description -Duperemove is a simple tool for finding duplicated extents and -submitting them for deduplication. When given a list of files it will -hash their contents on a block by block basis and compare those hashes -to each other, finding and categorizing extents that match each other. - -When given the -d option, duperemove will submit those extents for -deduplication using the btrfs-extent-same ioctl. - -%prep -git init . -git remote add -t \* -f origin %{url}.git -git checkout v%{version} - -%build -make - -%install -make install DESTDIR=%{buildroot} PREFIX=%{_prefix} BINDIR=%{_sbindir} - -%files -%doc README.md -%license LICENSE -%{_mandir}/man8/btrfs-extent-same*.8* -%{_mandir}/man8/%{name}*.8* -%{_mandir}/man8/hashstats*.8* -%{_mandir}/man8/show-shared-extents*.8* -%{_sbindir}/btrfs-extent-same -%{_sbindir}/%{name} -%{_sbindir}/hashstats - -%changelog -* Wed Jul 19 2023 Fedora Release Engineering - 0.11.3-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild - -* Thu Jan 19 2023 Fedora Release Engineering - 0.11.3-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild - -* Thu Jul 21 2022 Fedora Release Engineering - 0.11.3-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild - -* Thu Jan 20 2022 Fedora Release Engineering - 0.11.3-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild - -* Wed Sep 8 2021 Jonathan Dieter - 0.11.3-1 -- Update to 0.11.3 with bug fixes -- Remove patch to use system xxhash since upstream does that by default now - -* Wed Jul 21 2021 Fedora Release Engineering - 0.11.1-6 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild - -* Tue Jan 26 2021 Fedora Release Engineering - 0.11.1-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild - -* Mon Jul 27 2020 Fedora Release Engineering - 0.11.1-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild - -* Mon Feb 03 2020 Jonathan Dieter - 0.11.1-3 -- Work around GCC 10 build failure - -* Tue Jan 28 2020 Fedora Release Engineering - 0.11.1-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild - -* Tue Dec 10 2019 Jonathan Dieter - 0.11.1-1.1 -- Fix build for EPEL-8 - -* Sun Dec 08 2019 Jonathan Dieter - 0.11.1-1 -- New release - -* Wed Jul 24 2019 Fedora Release Engineering - 0.11-5 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild - -* Thu Jan 31 2019 Fedora Release Engineering - 0.11-4 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild - -* Sat Oct 20 2018 Jonathan Dieter - 0.11-3 -- Add devsettool BR and build for EPEL - -* Fri Oct 19 2018 Jonathan Dieter - 0.11-2 -- Add missing BR -- Fix build to use build flags - -* Sat Oct 13 2018 Jonathan Dieter - 0.11-1 -- Bump to 0.11 -- Unbundle xxhash - -* Fri Nov 13 2015 Francesco Frassinelli - 0.10-1 -- Version bump; license and minor packaging issues fixed - -* Thu Jul 30 2015 Francesco Frassinelli - 0.09.5-2 -- %%license macro added - -* Sun Jul 19 2015 Francesco Frassinelli - 0.09.5-1 -- Initial build - diff --git a/spec_files/mutter/0001-place-Always-center-initial-setup-fedora-welcome.patch b/spec_files/mutter/0001-place-Always-center-initial-setup-fedora-welcome.patch deleted file mode 100644 index 078b7912..00000000 --- a/spec_files/mutter/0001-place-Always-center-initial-setup-fedora-welcome.patch +++ /dev/null @@ -1,28 +0,0 @@ -From 692546a9701a7b363e6190af441a95385c244907 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 2 Dec 2022 22:49:41 +0100 -Subject: [PATCH] place: Always center initial-setup/fedora-welcome - ---- - src/core/place.c | 5 +++++ - 1 file changed, 5 insertions(+) - -diff --git a/src/core/place.c b/src/core/place.c -index f9877dfc7..a69a3ebd1 100644 ---- a/src/core/place.c -+++ b/src/core/place.c -@@ -321,6 +321,11 @@ window_place_centered (MetaWindow *window) - - type = window->type; - -+ if (g_strcmp0 (meta_window_get_wm_class (window), "org.gnome.InitialSetup") == 0 || -+ g_strcmp0 (meta_window_get_wm_class (window), "org.fedoraproject.welcome-screen") == 0 || -+ g_strcmp0 (meta_window_get_wm_class (window), "fedora-welcome") == 0) -+ return TRUE; -+ - return (type == META_WINDOW_DIALOG || - type == META_WINDOW_MODAL_DIALOG || - type == META_WINDOW_SPLASHSCREEN || --- -2.39.2 - diff --git a/spec_files/mutter/0001-window-actor-Special-case-shaped-Java-windows.patch b/spec_files/mutter/0001-window-actor-Special-case-shaped-Java-windows.patch deleted file mode 100644 index 51f9ecf1..00000000 --- a/spec_files/mutter/0001-window-actor-Special-case-shaped-Java-windows.patch +++ /dev/null @@ -1,70 +0,0 @@ -From b3b5aa01c63aee1df079e0394b0e6372df1838d0 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Florian=20M=C3=BCllner?= -Date: Fri, 12 May 2017 13:40:31 +0200 -Subject: [PATCH] window-actor: Special-case shaped Java windows - -OpenJDK wrongly assumes that shaping a window implies no shadows. -They got lucky until commit b975676c changed the fallback case, -but now their compliance tests are broken. Make them happy again -by special-casing shaped Java windows. ---- - src/compositor/meta-window-actor-x11.c | 8 ++++++++ - src/x11/window-x11-private.h | 2 ++ - src/x11/window-x11.c | 9 +++++++++ - 3 files changed, 19 insertions(+) - -diff --git a/src/compositor/meta-window-actor-x11.c b/src/compositor/meta-window-actor-x11.c -index 19827af331..7d5e46ac75 100644 ---- a/src/compositor/meta-window-actor-x11.c -+++ b/src/compositor/meta-window-actor-x11.c -@@ -424,6 +424,14 @@ has_shadow (MetaWindowActorX11 *actor_x11) - */ - if (window->has_custom_frame_extents) - return FALSE; -+ -+ /* -+ * OpenJDK wrongly assumes that shaping a window implies no compositor -+ * shadows; make its compliance tests happy to give it what it wants ... -+ */ -+ if (g_strcmp0 (window->res_name, "sun-awt-X11-XWindowPeer") == 0 && -+ meta_window_x11_is_shaped (window)) -+ return FALSE; - - /* - * Generate shadows for all other windows. -diff --git a/src/x11/window-x11-private.h b/src/x11/window-x11-private.h -index c947744ee5..cb862f0d72 100644 ---- a/src/x11/window-x11-private.h -+++ b/src/x11/window-x11-private.h -@@ -125,6 +125,8 @@ gboolean meta_window_x11_has_pointer (MetaWindow *window); - gboolean meta_window_x11_same_application (MetaWindow *window, - MetaWindow *other_window); - -+gboolean meta_window_x11_is_shaped (MetaWindow *window); -+ - void meta_window_x11_shutdown_group (MetaWindow *window); - - META_EXPORT -diff --git a/src/x11/window-x11.c b/src/x11/window-x11.c -index 745c45db18..83cdd2e420 100644 ---- a/src/x11/window-x11.c -+++ b/src/x11/window-x11.c -@@ -2585,6 +2585,15 @@ meta_window_x11_update_shape_region (MetaWindow *window) - meta_window_set_shape_region (window, region); - } - -+gboolean -+meta_window_x11_is_shaped (MetaWindow *window) -+{ -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); -+ MetaWindowX11Private *priv = meta_window_x11_get_instance_private (window_x11); -+ -+ return priv->shape_region != NULL; -+} -+ - /* Generally meta_window_x11_same_application() is a better idea - * of "sameness", since it handles the case where multiple apps - * want to look like the same app or the same app wants to look --- -2.43.2 - diff --git a/spec_files/mutter/1441.patch b/spec_files/mutter/1441.patch deleted file mode 100644 index 9a17ac97..00000000 --- a/spec_files/mutter/1441.patch +++ /dev/null @@ -1,1868 +0,0 @@ -diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c -index 93e4c9329..6bd83101e 100644 ---- a/clutter/clutter/clutter-frame-clock.c -+++ b/clutter/clutter/clutter-frame-clock.c -@@ -42,6 +42,15 @@ enum - - static guint signals[N_SIGNALS]; - -+typedef enum -+{ -+ TRIPLE_BUFFERING_MODE_NEVER, -+ TRIPLE_BUFFERING_MODE_AUTO, -+ TRIPLE_BUFFERING_MODE_ALWAYS, -+} TripleBufferingMode; -+ -+static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; -+ - #define SYNC_DELAY_FALLBACK_FRACTION 0.875 - - #define MINIMUM_REFRESH_RATE 30.f -@@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState - CLUTTER_FRAME_CLOCK_STATE_IDLE, - CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, - CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, -- CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, -- CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, - } ClutterFrameClockState; - - struct _ClutterFrameClock -@@ -92,6 +103,7 @@ struct _ClutterFrameClock - ClutterFrameClockMode mode; - - int64_t last_dispatch_time_us; -+ int64_t prev_last_dispatch_time_us; - int64_t last_dispatch_lateness_us; - int64_t last_presentation_time_us; - int64_t next_update_time_us; -@@ -111,6 +123,9 @@ struct _ClutterFrameClock - int64_t vblank_duration_us; - /* Last KMS buffer submission time. */ - int64_t last_flip_time_us; -+ int64_t prev_last_flip_time_us; -+ -+ ClutterFrameHint last_flip_hints; - - /* Last time we promoted short-term maximum to long-term one */ - int64_t longterm_promotion_us; -@@ -245,10 +260,6 @@ static void - maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, - ClutterFrameInfo *frame_info) - { -- /* Do not update long-term max if there has been no measurement */ -- if (!frame_clock->shortterm_max_update_duration_us) -- return; -- - if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) < - G_USEC_PER_SEC) - return; -@@ -275,6 +286,12 @@ void - clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - ClutterFrameInfo *frame_info) - { -+#ifdef CLUTTER_ENABLE_DEBUG -+ const char *debug_state = -+ frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ? -+ "Triple buffering" : "Double buffering"; -+#endif -+ - COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, - "Clutter::FrameClock::presented()"); - COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, -@@ -361,22 +378,52 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - - frame_clock->got_measurements_last_frame = FALSE; - -- if (frame_info->cpu_time_before_buffer_swap_us != 0 && -- frame_info->has_valid_gpu_rendering_duration) -+ if ((frame_info->cpu_time_before_buffer_swap_us != 0 && -+ frame_info->has_valid_gpu_rendering_duration) || -+ frame_clock->ever_got_measurements) - { - int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; -+ int64_t dispatch_time_us = 0, flip_time_us = 0; - -- dispatch_to_swap_us = -- frame_info->cpu_time_before_buffer_swap_us - -- frame_clock->last_dispatch_time_us; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ g_warn_if_reached (); -+ G_GNUC_FALLTHROUGH; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ dispatch_time_us = frame_clock->last_dispatch_time_us; -+ flip_time_us = frame_clock->last_flip_time_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ dispatch_time_us = frame_clock->prev_last_dispatch_time_us; -+ flip_time_us = frame_clock->prev_last_flip_time_us; -+ break; -+ } -+ -+ if (frame_info->cpu_time_before_buffer_swap_us == 0) -+ { -+ /* Cursor-only updates with no "swap" or "flip" */ -+ dispatch_to_swap_us = 0; -+ swap_to_flip_us = 0; -+ } -+ else -+ { -+ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us - -+ dispatch_time_us; -+ swap_to_flip_us = flip_time_us - -+ frame_info->cpu_time_before_buffer_swap_us; -+ } - swap_to_rendering_done_us = - frame_info->gpu_rendering_duration_ns / 1000; -- swap_to_flip_us = -- frame_clock->last_flip_time_us - -- frame_info->cpu_time_before_buffer_swap_us; - - CLUTTER_NOTE (FRAME_TIMINGS, -- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", -+ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", -+ debug_state, - frame_clock->last_dispatch_lateness_us, - dispatch_to_swap_us, - swap_to_rendering_done_us, -@@ -386,7 +433,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + - MAX (swap_to_rendering_done_us, swap_to_flip_us), - frame_clock->shortterm_max_update_duration_us, -- frame_clock->refresh_interval_us); -+ 2 * frame_clock->refresh_interval_us); - - maybe_update_longterm_max_duration_us (frame_clock, frame_info); - -@@ -395,7 +442,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - } - else - { -- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", -+ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs", -+ debug_state, - frame_clock->last_dispatch_lateness_us); - } - -@@ -413,11 +461,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: - g_warn_if_reached (); - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - } - -@@ -435,26 +494,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: - g_warn_if_reached (); - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - } - --static int64_t --clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) -+static gboolean -+clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock, -+ int64_t *max_render_time_us) - { - int64_t refresh_interval_us; -- int64_t max_render_time_us; - - refresh_interval_us = frame_clock->refresh_interval_us; - - if (!frame_clock->ever_got_measurements || - G_UNLIKELY (clutter_paint_debug_flags & - CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) -- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; -+ return FALSE; - - /* Max render time shows how early the frame clock needs to be dispatched - * to make it to the predicted next presentation time. It is an estimate of -@@ -468,15 +538,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) - * - The duration of vertical blank. - * - A constant to account for variations in the above estimates. - */ -- max_render_time_us = -+ *max_render_time_us = - MAX (frame_clock->longterm_max_update_duration_us, - frame_clock->shortterm_max_update_duration_us) + - frame_clock->vblank_duration_us + - clutter_max_render_time_constant_us; - -- max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); -+ *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us); - -- return max_render_time_us; -+ return TRUE; - } - - static void -@@ -491,7 +561,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - int64_t min_render_time_allowed_us; - int64_t max_render_time_allowed_us; - int64_t next_presentation_time_us; -+ int64_t next_smooth_presentation_time_us = 0; - int64_t next_update_time_us; -+ gboolean max_render_time_is_known; - - now_us = g_get_monotonic_time (); - -@@ -511,10 +583,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - - min_render_time_allowed_us = refresh_interval_us / 2; -- max_render_time_allowed_us = -- clutter_frame_clock_compute_max_render_time_us (frame_clock); - -- if (min_render_time_allowed_us > max_render_time_allowed_us) -+ max_render_time_is_known = -+ clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_allowed_us); -+ -+ if (max_render_time_is_known && -+ min_render_time_allowed_us > max_render_time_allowed_us) - min_render_time_allowed_us = max_render_time_allowed_us; - - /* -@@ -535,7 +610,28 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - * - */ - last_presentation_time_us = frame_clock->last_presentation_time_us; -- next_presentation_time_us = last_presentation_time_us + refresh_interval_us; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ refresh_interval_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ 2 * refresh_interval_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ 3 * refresh_interval_us; -+ break; -+ } -+ -+ next_presentation_time_us = next_smooth_presentation_time_us; - - /* - * However, the last presentation could have happened more than a frame ago. -@@ -601,7 +697,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - } - -- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us) -+ if (next_presentation_time_us != next_smooth_presentation_time_us) - { - /* There was an idle period since the last presentation, so there seems - * be no constantly updating actor. In this case it's best to start -@@ -613,6 +709,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - else - { -+ /* If the max render time isn't known then using the current value of -+ * next_presentation_time_us is suboptimal. Targeting always one frame -+ * prior to that we'd lose the ability to scale up to triple buffering -+ * on late presentation. But targeting two frames prior we would be -+ * always triple buffering even when not required. -+ * So the algorithm for deciding when to scale up to triple buffering -+ * in the absence of render time measurements is to simply target full -+ * frame rate. If we're keeping up then we'll stay double buffering. If -+ * we're not keeping up then this will switch us to triple buffering. -+ */ -+ if (!max_render_time_is_known) -+ { -+ max_render_time_allowed_us = -+ refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; -+ next_presentation_time_us = -+ last_presentation_time_us + refresh_interval_us; -+ } -+ - while (next_presentation_time_us - min_render_time_allowed_us < now_us) - next_presentation_time_us += refresh_interval_us; - -@@ -644,7 +758,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, - - refresh_interval_us = frame_clock->refresh_interval_us; - -- if (frame_clock->last_presentation_time_us == 0) -+ if (frame_clock->last_presentation_time_us == 0 || -+ !clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_allowed_us)) - { - *out_next_update_time_us = - frame_clock->last_dispatch_time_us ? -@@ -657,9 +773,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, - return; - } - -- max_render_time_allowed_us = -- clutter_frame_clock_compute_max_render_time_us (frame_clock); -- - last_presentation_time_us = frame_clock->last_presentation_time_us; - next_presentation_time_us = last_presentation_time_us + refresh_interval_us; - -@@ -733,8 +846,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) - frame_clock->pending_reschedule_now = TRUE; - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->pending_reschedule_now = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - break; - } - -@@ -753,6 +875,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock) - maybe_reschedule_update (frame_clock); - } - -+static gboolean -+want_triple_buffering (ClutterFrameClock *frame_clock) -+{ -+ switch (triple_buffering_mode) -+ { -+ case TRIPLE_BUFFERING_MODE_NEVER: -+ return FALSE; -+ case TRIPLE_BUFFERING_MODE_AUTO: -+ return frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_FIXED && -+ !(frame_clock->last_flip_hints & -+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); -+ case TRIPLE_BUFFERING_MODE_ALWAYS: -+ return TRUE; -+ } -+ -+ g_assert_not_reached (); -+ return FALSE; -+} -+ - void - clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - { -@@ -770,11 +911,24 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - case CLUTTER_FRAME_CLOCK_STATE_INIT: - case CLUTTER_FRAME_CLOCK_STATE_IDLE: - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: - return; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ if (want_triple_buffering (frame_clock)) -+ { -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; -+ break; -+ } -+ G_GNUC_FALLTHROUGH; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - frame_clock->pending_reschedule = TRUE; - frame_clock->pending_reschedule_now = TRUE; - return; -@@ -803,13 +957,17 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - - frame_clock->next_update_time_us = next_update_time_us; - g_source_set_ready_time (frame_clock->source, next_update_time_us); -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; - } - - void - clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - { - int64_t next_update_time_us = -1; -+ TripleBufferingMode current_mode = triple_buffering_mode; -+ -+ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && -+ !want_triple_buffering (frame_clock)) -+ current_mode = TRIPLE_BUFFERING_MODE_NEVER; - - if (frame_clock->inhibit_count > 0) - { -@@ -825,12 +983,33 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - return; - case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: - return; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ switch (current_mode) -+ { -+ case TRIPLE_BUFFERING_MODE_NEVER: -+ frame_clock->pending_reschedule = TRUE; -+ return; -+ case TRIPLE_BUFFERING_MODE_AUTO: -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; -+ break; -+ case TRIPLE_BUFFERING_MODE_ALWAYS: -+ next_update_time_us = g_get_monotonic_time (); -+ frame_clock->next_presentation_time_us = 0; -+ frame_clock->is_next_presentation_time_valid = FALSE; -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; -+ goto got_update_time; -+ } -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - frame_clock->pending_reschedule = TRUE; - return; - } -@@ -855,11 +1034,11 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - break; - } - -+got_update_time: - g_warn_if_fail (next_update_time_us != -1); - - frame_clock->next_update_time_us = next_update_time_us; - g_source_set_ready_time (frame_clock->source, next_update_time_us); -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - } - - void -@@ -875,6 +1054,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, - { - case CLUTTER_FRAME_CLOCK_STATE_INIT: - case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: - frame_clock->pending_reschedule = TRUE; -@@ -885,8 +1066,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, - frame_clock->pending_reschedule_now = TRUE; - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->pending_reschedule_now = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; - break; - } - -@@ -922,7 +1109,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - frame_clock->refresh_interval_us; - - lateness_us = time_us - ideal_dispatch_time_us; -- if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) -+ if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4) - frame_clock->last_dispatch_lateness_us = 0; - else - frame_clock->last_dispatch_lateness_us = lateness_us; -@@ -943,10 +1130,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - } - #endif - -+ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us; - frame_clock->last_dispatch_time_us = time_us; - g_source_set_ready_time (frame_clock->source, -1); - -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ g_warn_if_reached (); -+ return; -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; -+ break; -+ } - - frame_count = frame_clock->frame_count++; - -@@ -977,26 +1181,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); - COGL_TRACE_END (ClutterFrameClockFrame); - -- switch (frame_clock->state) -+ switch (result) - { -- case CLUTTER_FRAME_CLOCK_STATE_INIT: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -- g_warn_if_reached (); -+ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: - break; -- case CLUTTER_FRAME_CLOCK_STATE_IDLE: -- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -- break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- switch (result) -+ case CLUTTER_FRAME_RESULT_IDLE: -+ /* The frame was aborted; nothing to paint/present */ -+ switch (frame_clock->state) - { -- case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED; -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ g_warn_if_reached (); - break; -- case CLUTTER_FRAME_RESULT_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - break; - } -@@ -1029,21 +1243,31 @@ frame_clock_source_dispatch (GSource *source, - } - - void --clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, -- int64_t flip_time_us) -+clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, -+ int64_t flip_time_us, -+ ClutterFrameHint hints) - { -+ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; - frame_clock->last_flip_time_us = flip_time_us; -+ frame_clock->last_flip_hints = hints; - } - - GString * - clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) - { -+ int64_t max_render_time_us; - int64_t max_update_duration_us; - GString *string; - -- string = g_string_new (NULL); -- g_string_append_printf (string, "Max render time: %ld µs", -- clutter_frame_clock_compute_max_render_time_us (frame_clock)); -+ string = g_string_new ("Max render time: "); -+ if (!clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_us)) -+ { -+ g_string_append (string, "unknown"); -+ return string; -+ } -+ -+ g_string_append_printf (string, "%ld µs", max_render_time_us); - - if (frame_clock->got_measurements_last_frame) - g_string_append_printf (string, " ="); -@@ -1210,8 +1434,6 @@ clutter_frame_clock_dispose (GObject *object) - { - ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); - -- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING); -- - if (frame_clock->source) - { - g_signal_emit (frame_clock, signals[DESTROY], 0); -@@ -1235,6 +1457,15 @@ static void - clutter_frame_clock_class_init (ClutterFrameClockClass *klass) - { - GObjectClass *object_class = G_OBJECT_CLASS (klass); -+ const char *mode_str; -+ -+ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING"); -+ if (!g_strcmp0 (mode_str, "never")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER; -+ else if (!g_strcmp0 (mode_str, "auto")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; -+ else if (!g_strcmp0 (mode_str, "always")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS; - - object_class->dispose = clutter_frame_clock_dispose; - -diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h -index a7be5ef31..bfc89bde0 100644 ---- a/clutter/clutter/clutter-frame-clock.h -+++ b/clutter/clutter/clutter-frame-clock.h -@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult - CLUTTER_FRAME_RESULT_IDLE, - } ClutterFrameResult; - -+typedef enum _ClutterFrameHint -+{ -+ CLUTTER_FRAME_HINT_NONE = 0, -+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0, -+} ClutterFrameHint; -+ - #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ()) - CLUTTER_EXPORT - G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, -@@ -102,7 +108,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, - CLUTTER_EXPORT - float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); - --void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, -- int64_t flip_time_us); -+void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, -+ int64_t flip_time_us, -+ ClutterFrameHint hints); - - GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); -diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h -index ef66b874e..ce140560a 100644 ---- a/clutter/clutter/clutter-frame-private.h -+++ b/clutter/clutter/clutter-frame-private.h -@@ -36,6 +36,7 @@ struct _ClutterFrame - - gboolean has_result; - ClutterFrameResult result; -+ ClutterFrameHint hints; - }; - - CLUTTER_EXPORT -diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c -index 7436f9f18..53c289b2c 100644 ---- a/clutter/clutter/clutter-frame.c -+++ b/clutter/clutter/clutter-frame.c -@@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame, - frame->result = result; - frame->has_result = TRUE; - } -+ -+void -+clutter_frame_set_hint (ClutterFrame *frame, -+ ClutterFrameHint hint) -+{ -+ frame->hints |= hint; -+} -+ -+ClutterFrameHint -+clutter_frame_get_hints (ClutterFrame *frame) -+{ -+ return frame->hints; -+} -diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h -index 34f0770bd..c7b3d02ac 100644 ---- a/clutter/clutter/clutter-frame.h -+++ b/clutter/clutter/clutter-frame.h -@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame, - CLUTTER_EXPORT - gboolean clutter_frame_has_result (ClutterFrame *frame); - -+CLUTTER_EXPORT -+void clutter_frame_set_hint (ClutterFrame *frame, -+ ClutterFrameHint hint); -+ -+CLUTTER_EXPORT -+ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame); -+ - G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref) -diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c -index f5188e2ac..d53e37785 100644 ---- a/clutter/clutter/clutter-stage-view.c -+++ b/clutter/clutter/clutter-stage-view.c -@@ -898,14 +898,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, - - _clutter_stage_window_redraw_view (stage_window, view, frame); - -- clutter_frame_clock_record_flip_time (frame_clock, -- g_get_monotonic_time ()); -+ clutter_frame_clock_record_flip (frame_clock, -+ g_get_monotonic_time (), -+ clutter_frame_get_hints (frame)); - - clutter_stage_emit_after_paint (stage, view, frame); - - if (_clutter_context_get_show_fps ()) - end_frame_timing_measurement (view); - } -+ else -+ { -+ clutter_frame_clock_record_flip (frame_clock, -+ g_get_monotonic_time (), -+ clutter_frame_get_hints (frame)); -+ } - - _clutter_stage_window_finish_frame (stage_window, view, frame); - -diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h -index 959a60533..86d8ea2d5 100644 ---- a/cogl/cogl/cogl-onscreen-private.h -+++ b/cogl/cogl/cogl-onscreen-private.h -@@ -78,4 +78,7 @@ COGL_EXPORT CoglFrameInfo * - cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); - - COGL_EXPORT CoglFrameInfo * --cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); -+cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); -+ -+COGL_EXPORT unsigned int -+cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen); -diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c -index afb648bcd..086be7ed7 100644 ---- a/cogl/cogl/cogl-onscreen.c -+++ b/cogl/cogl/cogl-onscreen.c -@@ -515,6 +515,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen) - return g_queue_pop_head (&priv->pending_frame_infos); - } - -+unsigned int -+cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen) -+{ -+ CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen); -+ -+ return g_queue_get_length (&priv->pending_frame_infos); -+} -+ - CoglFrameClosure * - cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen, - CoglFrameCallback callback, -diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c -index 7aa24439d..727e1a5f3 100644 ---- a/src/backends/meta-stage-impl.c -+++ b/src/backends/meta-stage-impl.c -@@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window, - { - g_autoptr (GError) error = NULL; - -+ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); -+ - if (meta_stage_impl_scanout_view (stage_impl, - stage_view, - scanout, -diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c -index b15eee14d..05bc89e83 100644 ---- a/src/backends/native/meta-kms-impl-device.c -+++ b/src/backends/native/meta-kms-impl-device.c -@@ -1559,9 +1559,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, - meta_kms_update_merge_from (crtc_frame->pending_update, update); - meta_kms_update_free (update); - update = g_steal_pointer (&crtc_frame->pending_update); -- disarm_crtc_frame_deadline_timer (crtc_frame); - } - -+ if (crtc_frame->deadline.armed) -+ disarm_crtc_frame_deadline_timer (crtc_frame); -+ - meta_kms_device_handle_flush (priv->device, latch_crtc); - - feedback = do_process (impl_device, latch_crtc, update, flags); -diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c -index 795008b21..70d1e792c 100644 ---- a/src/backends/native/meta-kms.c -+++ b/src/backends/native/meta-kms.c -@@ -63,6 +63,8 @@ struct _MetaKms - int kernel_thread_inhibit_count; - - MetaKmsCursorManager *cursor_manager; -+ -+ gboolean shutting_down; - }; - - G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD) -@@ -354,6 +356,7 @@ static void - on_prepare_shutdown (MetaBackend *backend, - MetaKms *kms) - { -+ kms->shutting_down = TRUE; - meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL); - meta_thread_flush_callbacks (META_THREAD (kms)); - -@@ -408,6 +411,12 @@ meta_kms_new (MetaBackend *backend, - return kms; - } - -+gboolean -+meta_kms_is_shutting_down (MetaKms *kms) -+{ -+ return kms->shutting_down; -+} -+ - static void - meta_kms_finalize (GObject *object) - { -diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h -index 743401406..f6b19520b 100644 ---- a/src/backends/native/meta-kms.h -+++ b/src/backends/native/meta-kms.h -@@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms, - MetaKmsDeviceFlag flags, - GError **error); - -+gboolean meta_kms_is_shutting_down (MetaKms *kms); -+ - MetaKms * meta_kms_new (MetaBackend *backend, - MetaKmsFlags flags, - GError **error); -diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c -index 1a31f04a1..9836663d0 100644 ---- a/src/backends/native/meta-onscreen-native.c -+++ b/src/backends/native/meta-onscreen-native.c -@@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState - - struct { - MetaDrmBufferDumb *current_dumb_fb; -- MetaDrmBufferDumb *dumb_fbs[2]; -+ MetaDrmBufferDumb *dumb_fbs[3]; - } cpu; - - gboolean noted_primary_gpu_copy_ok; -@@ -98,9 +98,13 @@ struct _MetaOnscreenNative - struct { - struct gbm_surface *surface; - MetaDrmBuffer *current_fb; -+ MetaDrmBuffer *posted_fb; - MetaDrmBuffer *next_fb; -+ MetaDrmBuffer *stalled_fb; - CoglScanout *current_scanout; -+ CoglScanout *posted_scanout; - CoglScanout *next_scanout; -+ CoglScanout *stalled_scanout; - } gbm; - - #ifdef HAVE_EGL_DEVICE -@@ -125,6 +129,16 @@ struct _MetaOnscreenNative - gulong privacy_screen_changed_handler_id; - gulong color_space_changed_handler_id; - gulong hdr_metadata_changed_handler_id; -+ -+ gboolean needs_flush; -+ -+ unsigned int swaps_pending; -+ -+ struct { -+ int *rectangles; /* 4 x n_rectangles */ -+ int n_rectangles; -+ ClutterFrame *frame; -+ } next_post; - }; - - G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, -@@ -132,44 +146,42 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, - - static GQuark blit_source_quark = 0; - -+static void -+try_post_latest_swap (CoglOnscreen *onscreen); -+ -+static void -+post_finish_frame (MetaOnscreenNative *onscreen_native, -+ MetaKmsUpdate *kms_update); -+ - static gboolean - init_secondary_gpu_state (MetaRendererNative *renderer_native, - CoglOnscreen *onscreen, - GError **error); - --static void --free_current_bo (CoglOnscreen *onscreen) --{ -- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -- -- g_clear_object (&onscreen_native->gbm.current_fb); -- g_clear_object (&onscreen_native->gbm.current_scanout); --} -- - static void - meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) - { - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - -- if (!onscreen_native->gbm.next_fb) -+ if (!onscreen_native->gbm.posted_fb) - return; - -- free_current_bo (onscreen); -+ g_set_object (&onscreen_native->gbm.current_fb, -+ onscreen_native->gbm.posted_fb); -+ g_clear_object (&onscreen_native->gbm.posted_fb); - -- g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb); -- g_clear_object (&onscreen_native->gbm.next_fb); - g_set_object (&onscreen_native->gbm.current_scanout, -- onscreen_native->gbm.next_scanout); -- g_clear_object (&onscreen_native->gbm.next_scanout); -+ onscreen_native->gbm.posted_scanout); -+ g_clear_object (&onscreen_native->gbm.posted_scanout); - } - - static void --meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen) -+meta_onscreen_native_clear_posted_fb (CoglOnscreen *onscreen) - { - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - -- g_clear_object (&onscreen_native->gbm.next_fb); -- g_clear_object (&onscreen_native->gbm.next_scanout); -+ g_clear_object (&onscreen_native->gbm.posted_fb); -+ g_clear_object (&onscreen_native->gbm.posted_scanout); - } - - static void -@@ -207,7 +219,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) - - info = cogl_onscreen_pop_head_frame_info (onscreen); - -- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); -+ g_return_if_fail (info); - - _cogl_onscreen_notify_frame_sync (onscreen, info); - _cogl_onscreen_notify_complete (onscreen, info); -@@ -243,6 +255,7 @@ notify_view_crtc_presented (MetaRendererView *view, - - meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_swap_drm_fb (onscreen); -+ try_post_latest_swap (onscreen); - } - - static void -@@ -292,15 +305,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, - CoglFramebuffer *framebuffer = - clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); - CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); -- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - CoglFrameInfo *frame_info; - - frame_info = cogl_onscreen_peek_head_frame_info (onscreen); - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - -- g_warn_if_fail (!onscreen_native->gbm.next_fb); -- - meta_onscreen_native_notify_frame_complete (onscreen); -+ try_post_latest_swap (onscreen); - } - - static void -@@ -350,7 +361,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - - meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); -+ meta_onscreen_native_clear_posted_fb (onscreen); -+ try_post_latest_swap (onscreen); - } - - static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { -@@ -411,18 +423,41 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data, - } - #endif /* HAVE_EGL_DEVICE */ - --void --meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) -+static void -+drop_stalled_swap (CoglOnscreen *onscreen) - { - CoglFrameInfo *frame_info; -+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - -- meta_onscreen_native_swap_drm_fb (onscreen); -+ /* Remember we can't compare stalled_fb because it's not used by -+ * META_RENDERER_NATIVE_MODE_EGL_DEVICE. So we judge stalled to be whenever -+ * swaps_pending > 1. -+ */ -+ if (onscreen_native->swaps_pending <= 1) -+ return; -+ -+ onscreen_native->swaps_pending--; -+ -+ g_clear_object (&onscreen_native->gbm.stalled_fb); -+ g_clear_object (&onscreen_native->gbm.stalled_scanout); - - frame_info = cogl_onscreen_peek_tail_frame_info (onscreen); - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - meta_onscreen_native_notify_frame_complete (onscreen); - } - -+void -+meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) -+{ -+ drop_stalled_swap (onscreen); -+ -+ /* If the monitor just woke up and the shell is fully idle (has nothing -+ * more to swap) then we just woke to an indefinitely black screen. Let's -+ * fix that using the last swap (which is never classified as "stalled"). -+ */ -+ try_post_latest_swap (onscreen); -+} -+ - static void - apply_transform (MetaCrtcKms *crtc_kms, - MetaKmsPlaneAssignment *kms_plane_assignment, -@@ -521,13 +556,21 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, - switch (renderer_gpu_data->mode) - { - case META_RENDERER_NATIVE_MODE_GBM: -- buffer = onscreen_native->gbm.next_fb; -+ g_set_object (&onscreen_native->gbm.posted_fb, -+ onscreen_native->gbm.next_fb); -+ g_clear_object (&onscreen_native->gbm.next_fb); -+ -+ buffer = onscreen_native->gbm.posted_fb; - -- if (onscreen_native->gbm.next_scanout) -+ g_set_object (&onscreen_native->gbm.posted_scanout, -+ onscreen_native->gbm.next_scanout); -+ g_clear_object (&onscreen_native->gbm.next_scanout); -+ -+ if (onscreen_native->gbm.posted_scanout) - { -- cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, -+ cogl_scanout_get_src_rect (onscreen_native->gbm.posted_scanout, - &src_rect); -- cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout, -+ cogl_scanout_get_dst_rect (onscreen_native->gbm.posted_scanout, - &dst_rect); - } - else -@@ -918,12 +961,17 @@ static MetaDrmBufferDumb * - secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) - { - MetaDrmBufferDumb *current_dumb_fb; -+ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); -+ int i; - - current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; -- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) -- return secondary_gpu_state->cpu.dumb_fbs[1]; -- else -- return secondary_gpu_state->cpu.dumb_fbs[0]; -+ for (i = 0; i < n_dumb_fbs; i++) -+ { -+ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i]) -+ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs]; -+ } -+ -+ return secondary_gpu_state->cpu.dumb_fbs[0]; - } - - static MetaDrmBuffer * -@@ -1255,10 +1303,17 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, - g_warning ("Page flip failed: %s", error->message); - - frame_info = cogl_onscreen_peek_head_frame_info (onscreen); -- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - -- meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); -+ /* After resuming from suspend, drop_stalled_swap might have done this -+ * already and emptied the frame_info queue. -+ */ -+ if (frame_info) -+ { -+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; -+ meta_onscreen_native_notify_frame_complete (onscreen); -+ } -+ -+ meta_onscreen_native_clear_posted_fb (onscreen); - } - - static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { -@@ -1279,32 +1334,37 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; - MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; - MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; -- MetaRenderer *renderer = META_RENDERER (renderer_native); -- MetaBackend *backend = meta_renderer_get_backend (renderer); -- MetaMonitorManager *monitor_manager = -- meta_backend_get_monitor_manager (backend); - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; - MetaGpuKms *render_gpu = onscreen_native->render_gpu; - MetaDeviceFile *render_device_file; - ClutterFrame *frame = user_data; -- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); -- MetaKmsUpdate *kms_update; - CoglOnscreenClass *parent_class; - gboolean create_timestamp_query = TRUE; - gboolean egl_context_changed = FALSE; -- MetaPowerSave power_save_mode; - g_autoptr (GError) error = NULL; - MetaDrmBufferFlags buffer_flags; - MetaDrmBufferGbm *buffer_gbm; - g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; - g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; -- MetaKmsCrtc *kms_crtc; -- MetaKmsDevice *kms_device; -+ size_t rectangles_size; - - COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, - "Meta::OnscreenNative::swap_buffers_with_damage()"); - -+ if (meta_is_topic_enabled (META_DEBUG_KMS)) -+ { -+ unsigned int frames_pending = -+ cogl_onscreen_count_pending_frames (onscreen); -+ -+ meta_topic (META_DEBUG_KMS, -+ "Swap buffers: %u frames pending (%s-buffering)", -+ frames_pending, -+ frames_pending == 1 ? "double" : -+ frames_pending == 2 ? "triple" : -+ "?"); -+ } -+ - secondary_gpu_fb = - update_secondary_gpu_state_pre_swap_buffers (onscreen, - rectangles, -@@ -1379,7 +1439,17 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - switch (renderer_gpu_data->mode) - { - case META_RENDERER_NATIVE_MODE_GBM: -- g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); -+ if (onscreen_native->gbm.next_fb != NULL) -+ { -+ g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL); -+ drop_stalled_swap (onscreen); -+ g_assert (onscreen_native->gbm.stalled_fb == NULL); -+ onscreen_native->gbm.stalled_fb = -+ g_steal_pointer (&onscreen_native->gbm.next_fb); -+ onscreen_native->gbm.stalled_scanout = -+ g_steal_pointer (&onscreen_native->gbm.next_scanout); -+ } -+ - if (onscreen_native->secondary_gpu_state) - g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); - else -@@ -1404,6 +1474,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - #endif - } - -+ clutter_frame_set_result (frame, -+ CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+ - /* - * If we changed EGL context, cogl will have the wrong idea about what is - * current, making it fail to set it when it needs to. Avoid that by making -@@ -1413,12 +1486,78 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - if (egl_context_changed) - _cogl_winsys_egl_ensure_current (cogl_display); - -- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); -- kms_device = meta_kms_crtc_get_device (kms_crtc); -+ rectangles_size = n_rectangles * 4 * sizeof (int); -+ onscreen_native->next_post.rectangles = -+ g_realloc (onscreen_native->next_post.rectangles, rectangles_size); -+ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size); -+ onscreen_native->next_post.n_rectangles = n_rectangles; -+ -+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); -+ onscreen_native->next_post.frame = clutter_frame_ref (frame); -+ -+ onscreen_native->swaps_pending++; -+ try_post_latest_swap (onscreen); -+} -+ -+static void -+try_post_latest_swap (CoglOnscreen *onscreen) -+{ -+ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); -+ CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); -+ CoglRenderer *cogl_renderer = cogl_context->display->renderer; -+ CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; -+ MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; -+ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; -+ MetaRenderer *renderer = META_RENDERER (renderer_native); -+ MetaBackend *backend = meta_renderer_get_backend (renderer); -+ MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); -+ MetaKms *kms = meta_backend_native_get_kms (backend_native); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); -+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -+ MetaPowerSave power_save_mode; -+ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); -+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); -+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); -+ MetaKmsUpdate *kms_update; -+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; -+ g_autoptr (ClutterFrame) frame = NULL; -+ MetaFrameNative *frame_native; -+ -+ if (onscreen_native->next_post.frame == NULL || -+ onscreen_native->view == NULL || -+ meta_kms_is_shutting_down (kms)) -+ return; - - power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); - if (power_save_mode == META_POWER_SAVE_ON) - { -+ unsigned int frames_pending = -+ cogl_onscreen_count_pending_frames (onscreen); -+ unsigned int posts_pending; -+ -+ g_assert (frames_pending >= onscreen_native->swaps_pending); -+ posts_pending = frames_pending - onscreen_native->swaps_pending; -+ if (posts_pending > 0) -+ return; /* wait for the next frame notification and then try again */ -+ -+ frame = g_steal_pointer (&onscreen_native->next_post.frame); -+ frame_native = meta_frame_native_from_frame (frame); -+ -+ if (onscreen_native->swaps_pending == 0) -+ { -+ if (frame_native) -+ { -+ kms_update = meta_frame_native_steal_kms_update (frame_native); -+ if (kms_update) -+ post_finish_frame (onscreen_native, kms_update); -+ } -+ return; -+ } -+ -+ drop_stalled_swap (onscreen); -+ onscreen_native->swaps_pending--; -+ - kms_update = meta_frame_native_ensure_kms_update (frame_native, - kms_device); - meta_kms_update_add_result_listener (kms_update, -@@ -1433,15 +1572,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - onscreen_native->crtc, - kms_update, - META_KMS_ASSIGN_PLANE_FLAG_NONE, -- rectangles, -- n_rectangles); -+ onscreen_native->next_post.rectangles, -+ onscreen_native->next_post.n_rectangles); - } - else - { - meta_renderer_native_queue_power_save_page_flip (renderer_native, - onscreen); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - -@@ -1461,8 +1598,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update = meta_frame_native_steal_kms_update (frame_native); - meta_renderer_native_queue_mode_set_update (renderer_native, - kms_update); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - else if (meta_renderer_native_has_pending_mode_set (renderer_native)) -@@ -1476,8 +1611,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - - meta_frame_native_steal_kms_update (frame_native); - meta_renderer_native_post_mode_set_updates (renderer_native); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - break; -@@ -1493,8 +1626,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update); - - meta_renderer_native_post_mode_set_updates (renderer_native); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - break; -@@ -1509,7 +1640,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update = meta_frame_native_steal_kms_update (frame_native); - meta_kms_device_post_update (kms_device, kms_update, - META_KMS_UPDATE_FLAG_NONE); -- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - } - - gboolean -@@ -1580,7 +1710,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, - - g_warning ("Direct scanout page flip failed: %s", error->message); - -- cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, -+ cogl_scanout_notify_failed (onscreen_native->gbm.posted_scanout, - onscreen); - clutter_stage_view_add_redraw_clip (view, NULL); - clutter_stage_view_schedule_update_now (view); -@@ -1590,7 +1720,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - - meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); -+ meta_onscreen_native_clear_posted_fb (onscreen); - } - - static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { -@@ -1642,6 +1772,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, - return FALSE; - } - -+ /* Our direct scanout frame counts as 1, so more than that means we would -+ * be jumping the queue (and post would fail). -+ */ -+ if (cogl_onscreen_count_pending_frames (onscreen) > 1) -+ { -+ g_set_error_literal (error, -+ COGL_SCANOUT_ERROR, -+ COGL_SCANOUT_ERROR_INHIBITED, -+ "Direct scanout is inhibited during triple buffering"); -+ return FALSE; -+ } -+ - renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, - render_gpu); - -@@ -1757,11 +1899,7 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen, - ClutterFrame *frame) - { - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); -- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); - -- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), -- kms_crtc); - maybe_update_frame_sync (onscreen_native, frame); - } - -@@ -1877,22 +2015,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, - MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); - MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); - MetaKmsUpdate *kms_update; -+ unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen); -+ unsigned int swaps_pending = onscreen_native->swaps_pending; -+ unsigned int posts_pending = frames_pending - swaps_pending; - -- kms_update = meta_frame_native_steal_kms_update (frame_native); -- if (!kms_update) -+ onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device, -+ kms_crtc); -+ -+ if (!meta_frame_native_has_kms_update (frame_native)) - { -- if (meta_kms_device_handle_flush (kms_device, kms_crtc)) -- { -- kms_update = meta_kms_update_new (kms_device); -- meta_kms_update_set_flushing (kms_update, kms_crtc); -- } -- else -+ if (!onscreen_native->needs_flush || posts_pending) - { - clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); - return; - } - } - -+ if (posts_pending && !swaps_pending) -+ { -+ g_return_if_fail (meta_frame_native_has_kms_update (frame_native)); -+ g_warn_if_fail (onscreen_native->next_post.frame == NULL); -+ -+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); -+ onscreen_native->next_post.frame = clutter_frame_ref (frame); -+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+ return; -+ } -+ -+ kms_update = meta_frame_native_steal_kms_update (frame_native); -+ -+ if (posts_pending && swaps_pending) -+ { -+ MetaFrameNative *older_frame_native; -+ MetaKmsUpdate *older_kms_update; -+ -+ g_return_if_fail (kms_update); -+ g_return_if_fail (onscreen_native->next_post.frame != NULL); -+ -+ older_frame_native = -+ meta_frame_native_from_frame (onscreen_native->next_post.frame); -+ older_kms_update = -+ meta_frame_native_ensure_kms_update (older_frame_native, kms_device); -+ meta_kms_update_merge_from (older_kms_update, kms_update); -+ meta_kms_update_free (kms_update); -+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); -+ return; -+ } -+ -+ if (!kms_update) -+ { -+ kms_update = meta_kms_update_new (kms_device); -+ g_warn_if_fail (onscreen_native->needs_flush); -+ } -+ -+ if (onscreen_native->needs_flush) -+ { -+ meta_kms_update_set_flushing (kms_update, kms_crtc); -+ onscreen_native->needs_flush = FALSE; -+ } -+ -+ post_finish_frame (onscreen_native, kms_update); -+ -+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+} -+ -+static void -+post_finish_frame (MetaOnscreenNative *onscreen_native, -+ MetaKmsUpdate *kms_update) -+{ -+ MetaCrtc *crtc = onscreen_native->crtc; -+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); -+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); -+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; -+ - meta_kms_update_add_result_listener (kms_update, - &finish_frame_result_listener_vtable, - NULL, -@@ -1915,7 +2110,19 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, - meta_kms_update_set_flushing (kms_update, kms_crtc); - meta_kms_device_post_update (kms_device, kms_update, - META_KMS_UPDATE_FLAG_NONE); -- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+} -+ -+void -+meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen) -+{ -+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -+ -+ onscreen_native->swaps_pending = 0; -+ -+ g_clear_object (&onscreen_native->gbm.stalled_fb); -+ g_clear_object (&onscreen_native->gbm.stalled_scanout); -+ g_clear_object (&onscreen_native->gbm.next_fb); -+ g_clear_object (&onscreen_native->gbm.next_scanout); - } - - static gboolean -@@ -2830,8 +3037,11 @@ meta_onscreen_native_dispose (GObject *object) - { - case META_RENDERER_NATIVE_MODE_GBM: - g_clear_object (&onscreen_native->gbm.next_fb); -+ g_clear_object (&onscreen_native->gbm.posted_fb); -+ g_clear_object (&onscreen_native->gbm.current_fb); - g_clear_object (&onscreen_native->gbm.next_scanout); -- free_current_bo (onscreen); -+ g_clear_object (&onscreen_native->gbm.posted_scanout); -+ g_clear_object (&onscreen_native->gbm.current_scanout); - break; - case META_RENDERER_NATIVE_MODE_SURFACELESS: - g_assert_not_reached (); -@@ -2865,6 +3075,10 @@ meta_onscreen_native_dispose (GObject *object) - - g_clear_object (&onscreen_native->output); - g_clear_object (&onscreen_native->crtc); -+ -+ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free); -+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); -+ onscreen_native->next_post.n_rectangles = 0; - } - - static void -diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h -index 0e1193325..e30357d19 100644 ---- a/src/backends/native/meta-onscreen-native.h -+++ b/src/backends/native/meta-onscreen-native.h -@@ -48,6 +48,8 @@ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen); - gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, - CoglScanout *scanout); - -+void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen); -+ - void meta_onscreen_native_set_view (CoglOnscreen *onscreen, - MetaRendererView *view); - -diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c -index aa76d018c..3c22b4e86 100644 ---- a/src/backends/native/meta-renderer-native.c -+++ b/src/backends/native/meta-renderer-native.c -@@ -731,12 +731,18 @@ static gboolean - dummy_power_save_page_flip_cb (gpointer user_data) - { - MetaRendererNative *renderer_native = user_data; -+ GList *old_list = -+ g_steal_pointer (&renderer_native->power_save_page_flip_onscreens); - -- g_list_foreach (renderer_native->power_save_page_flip_onscreens, -+ g_list_foreach (old_list, - (GFunc) meta_onscreen_native_dummy_power_save_page_flip, - NULL); -- g_clear_list (&renderer_native->power_save_page_flip_onscreens, -+ g_clear_list (&old_list, - g_object_unref); -+ -+ if (renderer_native->power_save_page_flip_onscreens != NULL) -+ return G_SOURCE_CONTINUE; -+ - renderer_native->power_save_page_flip_source_id = 0; - - return G_SOURCE_REMOVE; -@@ -748,6 +754,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na - { - const unsigned int timeout_ms = 100; - -+ if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen)) -+ return; -+ - if (!renderer_native->power_save_page_flip_source_id) - { - renderer_native->power_save_page_flip_source_id = -@@ -1529,6 +1538,26 @@ detach_onscreens (MetaRenderer *renderer) - } - } - -+static void -+discard_pending_swaps (MetaRenderer *renderer) -+{ -+ GList *views = meta_renderer_get_views (renderer);; -+ GList *l; -+ -+ for (l = views; l; l = l->next) -+ { -+ ClutterStageView *stage_view = l->data; -+ CoglFramebuffer *fb = clutter_stage_view_get_onscreen (stage_view); -+ CoglOnscreen *onscreen; -+ -+ if (!COGL_IS_ONSCREEN (fb)) -+ continue; -+ -+ onscreen = COGL_ONSCREEN (fb); -+ meta_onscreen_native_discard_pending_swaps (onscreen); -+ } -+} -+ - static void - meta_renderer_native_rebuild_views (MetaRenderer *renderer) - { -@@ -1539,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer) - MetaRendererClass *parent_renderer_class = - META_RENDERER_CLASS (meta_renderer_native_parent_class); - -+ discard_pending_swaps (renderer); - meta_kms_discard_pending_page_flips (kms); - g_hash_table_remove_all (renderer_native->mode_set_updates); - -diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c -index f5ebc23fe..2f870fdc3 100644 ---- a/src/tests/native-kms-render.c -+++ b/src/tests/native-kms-render.c -@@ -39,6 +39,8 @@ - #include "tests/meta-wayland-test-driver.h" - #include "tests/meta-wayland-test-utils.h" - -+#define N_FRAMES_PER_TEST 30 -+ - typedef struct - { - int number_of_frames_left; -@@ -46,12 +48,15 @@ typedef struct - - struct { - int n_paints; -- uint32_t fb_id; -+ int n_presentations; -+ int n_direct_scanouts; -+ GList *fb_ids; - } scanout; - - gboolean wait_for_scanout; - - struct { -+ int scanouts_attempted; - gboolean scanout_sabotaged; - gboolean fallback_painted; - guint repaint_guard_id; -@@ -101,7 +106,7 @@ meta_test_kms_render_basic (void) - gulong handler_id; - - test = (KmsRenderingTest) { -- .number_of_frames_left = 10, -+ .number_of_frames_left = N_FRAMES_PER_TEST, - .loop = g_main_loop_new (NULL, FALSE), - }; - handler_id = g_signal_connect (stage, "after-update", -@@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage, - KmsRenderingTest *test) - { - test->scanout.n_paints = 0; -- test->scanout.fb_id = 0; - } - - static void -@@ -135,6 +139,7 @@ on_scanout_before_paint (ClutterStage *stage, - CoglScanout *scanout; - CoglScanoutBuffer *scanout_buffer; - MetaDrmBuffer *buffer; -+ uint32_t fb_id; - - scanout = clutter_stage_view_peek_scanout (stage_view); - if (!scanout) -@@ -143,8 +148,13 @@ on_scanout_before_paint (ClutterStage *stage, - scanout_buffer = cogl_scanout_get_buffer (scanout); - g_assert_true (META_IS_DRM_BUFFER (scanout_buffer)); - buffer = META_DRM_BUFFER (scanout_buffer); -- test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); -- g_assert_cmpuint (test->scanout.fb_id, >, 0); -+ fb_id = meta_drm_buffer_get_fb_id (buffer); -+ g_assert_cmpuint (fb_id, >, 0); -+ test->scanout.fb_ids = g_list_append (test->scanout.fb_ids, -+ GUINT_TO_POINTER (fb_id)); -+ -+ /* Triple buffering, but no higher */ -+ g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2); - } - - static void -@@ -173,12 +183,12 @@ on_scanout_presented (ClutterStage *stage, - MetaDeviceFile *device_file; - GError *error = NULL; - drmModeCrtc *drm_crtc; -+ uint32_t first_fb_id_expected; - -- if (test->wait_for_scanout && test->scanout.n_paints > 0) -+ if (test->wait_for_scanout && test->scanout.fb_ids == NULL) - return; - -- if (test->wait_for_scanout && test->scanout.fb_id == 0) -- return; -+ test->scanout.n_presentations++; - - device_pool = meta_backend_native_get_device_pool (backend_native); - -@@ -197,15 +207,41 @@ on_scanout_presented (ClutterStage *stage, - drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file), - meta_kms_crtc_get_id (kms_crtc)); - g_assert_nonnull (drm_crtc); -- if (test->scanout.fb_id == 0) -- g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id); -+ -+ if (test->scanout.fb_ids) -+ { -+ test->scanout.n_direct_scanouts++; -+ first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data); -+ test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids, -+ test->scanout.fb_ids); -+ } - else -- g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id); -+ { -+ first_fb_id_expected = 0; -+ } -+ -+ /* The buffer ID won't match on the first frame because switching from -+ * triple buffered compositing to double buffered direct scanout takes -+ * an extra frame to drain the queue. Thereafter we are in direct scanout -+ * mode and expect the buffer IDs to match. -+ */ -+ if (test->scanout.n_presentations > 1) -+ { -+ if (first_fb_id_expected == 0) -+ g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected); -+ else -+ g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected); -+ } -+ - drmModeFreeCrtc (drm_crtc); - - meta_device_file_release (device_file); - -- g_main_loop_quit (test->loop); -+ test->number_of_frames_left--; -+ if (test->number_of_frames_left <= 0) -+ g_main_loop_quit (test->loop); -+ else -+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); - } - - typedef enum -@@ -244,7 +280,9 @@ meta_test_kms_render_client_scanout (void) - g_assert_nonnull (wayland_test_client); - - test = (KmsRenderingTest) { -+ .number_of_frames_left = N_FRAMES_PER_TEST, - .loop = g_main_loop_new (NULL, FALSE), -+ .scanout = {0}, - .wait_for_scanout = TRUE, - }; - -@@ -270,7 +308,8 @@ meta_test_kms_render_client_scanout (void) - clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); - g_main_loop_run (test.loop); - -- g_assert_cmpuint (test.scanout.fb_id, >, 0); -+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); -+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); - - g_debug ("Unmake fullscreen"); - window = meta_find_window_from_title (test_context, "dma-buf-scanout-test"); -@@ -292,10 +331,15 @@ meta_test_kms_render_client_scanout (void) - g_assert_cmpint (buffer_rect.y, ==, 10); - - test.wait_for_scanout = FALSE; -+ test.number_of_frames_left = N_FRAMES_PER_TEST; -+ test.scanout.n_presentations = 0; -+ test.scanout.n_direct_scanouts = 0; -+ - clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); - g_main_loop_run (test.loop); - -- g_assert_cmpuint (test.scanout.fb_id, ==, 0); -+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); -+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 0); - - g_debug ("Moving back to 0, 0"); - meta_window_move_frame (window, TRUE, 0, 0); -@@ -307,10 +351,15 @@ meta_test_kms_render_client_scanout (void) - g_assert_cmpint (buffer_rect.y, ==, 0); - - test.wait_for_scanout = TRUE; -+ test.number_of_frames_left = N_FRAMES_PER_TEST; -+ test.scanout.n_presentations = 0; -+ test.scanout.n_direct_scanouts = 0; -+ - clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); - g_main_loop_run (test.loop); - -- g_assert_cmpuint (test.scanout.fb_id, >, 0); -+ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); -+ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); - - g_signal_handler_disconnect (stage, before_update_handler_id); - g_signal_handler_disconnect (stage, before_paint_handler_id); -@@ -364,6 +413,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage, - if (!scanout) - return; - -+ test->scanout_fallback.scanouts_attempted++; -+ -+ /* The first scanout candidate frame will get composited due to triple -+ * buffering draining the queue to drop to double buffering. So don't -+ * sabotage that first frame. -+ */ -+ if (test->scanout_fallback.scanouts_attempted < 2) -+ return; -+ - g_assert_false (test->scanout_fallback.scanout_sabotaged); - - if (is_atomic_mode_setting (kms_device)) -@@ -401,6 +459,15 @@ on_scanout_fallback_paint_view (ClutterStage *stage, - g_clear_handle_id (&test->scanout_fallback.repaint_guard_id, - g_source_remove); - test->scanout_fallback.fallback_painted = TRUE; -+ test->scanout_fallback.scanout_sabotaged = FALSE; -+ } -+ else if (test->scanout_fallback.scanouts_attempted == 1) -+ { -+ /* Now that we've seen the first scanout attempt that was inhibited by -+ * triple buffering, try a second frame. The second one should scanout -+ * and will be sabotaged. -+ */ -+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); - } - } - -@@ -410,11 +477,11 @@ on_scanout_fallback_presented (ClutterStage *stage, - ClutterFrameInfo *frame_info, - KmsRenderingTest *test) - { -- if (!test->scanout_fallback.scanout_sabotaged) -- return; -+ if (test->scanout_fallback.fallback_painted) -+ g_main_loop_quit (test->loop); - -- g_assert_true (test->scanout_fallback.fallback_painted); -- g_main_loop_quit (test->loop); -+ test->number_of_frames_left--; -+ g_assert_cmpint (test->number_of_frames_left, >, 0); - } - - static void -@@ -443,6 +510,7 @@ meta_test_kms_render_client_scanout_fallback (void) - g_assert_nonnull (wayland_test_client); - - test = (KmsRenderingTest) { -+ .number_of_frames_left = N_FRAMES_PER_TEST, - .loop = g_main_loop_new (NULL, FALSE), - }; - diff --git a/spec_files/mutter/3720+3567.patch b/spec_files/mutter/3720+3567.patch deleted file mode 100644 index 1e02c828..00000000 --- a/spec_files/mutter/3720+3567.patch +++ /dev/null @@ -1,2012 +0,0 @@ -diff --git a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml -index b05337d74..7294c57a8 100644 ---- a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml -+++ b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml -@@ -426,10 +426,6 @@ - always use the same scale. Absence of - this means logical monitor scales can - differ. -- * "legacy-ui-scaling-factor" (i): The legacy scaling factor traditionally -- used to scale X11 clients (commonly -- communicated via the -- Gdk/WindowScalingFactor XSetting entry). - --> - - -diff --git a/data/dbus-interfaces/org.gnome.Mutter.X11.xml b/data/dbus-interfaces/org.gnome.Mutter.X11.xml -new file mode 100644 -index 000000000..3d3c8a42f ---- /dev/null -+++ b/data/dbus-interfaces/org.gnome.Mutter.X11.xml -@@ -0,0 +1,8 @@ -+ -+ -+ -+ -+ -+ -diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in -index 92c97b12e..18abd8d51 100644 ---- a/data/org.gnome.mutter.gschema.xml.in -+++ b/data/org.gnome.mutter.gschema.xml.in -@@ -5,6 +5,7 @@ - - - -+ - - - - - -diff --git a/src/backends/meta-monitor-manager-private.h b/src/backends/meta-monitor-manager-private.h -index 0760a341a..6ed3fc2c3 100644 ---- a/src/backends/meta-monitor-manager-private.h -+++ b/src/backends/meta-monitor-manager-private.h -@@ -436,3 +436,5 @@ gboolean meta_monitor_manager_apply_monitors_config (MetaMonitorManager * - MetaMonitorsConfig *config, - MetaMonitorsConfigMethod method, - GError **error); -+ -+MetaLogicalMonitorLayoutMode meta_monitor_manager_get_layout_mode (MetaMonitorManager *manager); -diff --git a/src/backends/meta-monitor-manager.c b/src/backends/meta-monitor-manager.c -index 77743bc72..45033d966 100644 ---- a/src/backends/meta-monitor-manager.c -+++ b/src/backends/meta-monitor-manager.c -@@ -2051,14 +2051,12 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, - GDBusMethodInvocation *invocation, - MetaMonitorManager *manager) - { -- MetaSettings *settings = meta_backend_get_settings (manager->backend); - GVariantBuilder monitors_builder; - GVariantBuilder logical_monitors_builder; - GVariantBuilder properties_builder; - GList *l; - int i; - MetaMonitorManagerCapability capabilities; -- int ui_scaling_factor; - int max_screen_width, max_screen_height; - - g_variant_builder_init (&monitors_builder, -@@ -2261,11 +2259,6 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, - g_variant_new_boolean (TRUE)); - } - -- ui_scaling_factor = meta_settings_get_ui_scaling_factor (settings); -- g_variant_builder_add (&properties_builder, "{sv}", -- "legacy-ui-scaling-factor", -- g_variant_new_int32 (ui_scaling_factor)); -- - if (meta_monitor_manager_get_max_screen_size (manager, - &max_screen_width, - &max_screen_height)) -@@ -4123,3 +4116,9 @@ meta_monitor_manager_get_virtual_monitors (MetaMonitorManager *manager) - - return priv->virtual_monitors; - } -+ -+MetaLogicalMonitorLayoutMode -+meta_monitor_manager_get_layout_mode (MetaMonitorManager *manager) -+{ -+ return manager->layout_mode; -+} -diff --git a/src/backends/meta-settings-private.h b/src/backends/meta-settings-private.h -index afbba054a..2081a81b1 100644 ---- a/src/backends/meta-settings-private.h -+++ b/src/backends/meta-settings-private.h -@@ -32,6 +32,7 @@ typedef enum _MetaExperimentalFeature - META_EXPERIMENTAL_FEATURE_KMS_MODIFIERS = (1 << 1), - META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND = (1 << 2), - META_EXPERIMENTAL_FEATURE_VARIABLE_REFRESH_RATE = (1 << 3), -+ META_EXPERIMENTAL_FEATURE_XWAYLAND_NATIVE_SCALING = (1 << 4), - } MetaExperimentalFeature; - - typedef enum _MetaXwaylandExtension -diff --git a/src/backends/meta-settings.c b/src/backends/meta-settings.c -index 3703b23b0..1ae59d636 100644 ---- a/src/backends/meta-settings.c -+++ b/src/backends/meta-settings.c -@@ -296,6 +296,8 @@ experimental_features_handler (GVariant *features_variant, - feature = META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND; - else if (g_str_equal (feature_str, "variable-refresh-rate")) - feature = META_EXPERIMENTAL_FEATURE_VARIABLE_REFRESH_RATE; -+ else if (g_str_equal (feature_str, "xwayland-native-scaling")) -+ feature = META_EXPERIMENTAL_FEATURE_XWAYLAND_NATIVE_SCALING; - - if (feature) - g_message ("Enabling experimental feature '%s'", feature_str); -diff --git a/src/compositor/meta-window-actor-x11.c b/src/compositor/meta-window-actor-x11.c -index 7d5e46ac7..577ed2760 100644 ---- a/src/compositor/meta-window-actor-x11.c -+++ b/src/compositor/meta-window-actor-x11.c -@@ -696,11 +696,23 @@ meta_window_actor_x11_process_damage (MetaWindowActorX11 *actor_x11, - - surface = meta_window_actor_get_surface (META_WINDOW_ACTOR (actor_x11)); - if (surface) -- meta_surface_actor_process_damage (surface, -- event->area.x, -- event->area.y, -- event->area.width, -- event->area.height); -+ { -+ MetaWindow *window = -+ meta_window_actor_get_meta_window (META_WINDOW_ACTOR (actor_x11)); -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); -+ MtkRectangle damage; -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->area.x, event->area.y, -+ event->area.width, event->area.height, -+ &damage.x, &damage.y, -+ &damage.width, &damage.height); -+ meta_surface_actor_process_damage (surface, -+ damage.x, -+ damage.y, -+ damage.width, -+ damage.height); -+ } - - meta_window_actor_notify_damaged (META_WINDOW_ACTOR (actor_x11)); - } -diff --git a/src/core/frame.c b/src/core/frame.c -index c74a2e04e..d45a8759b 100644 ---- a/src/core/frame.c -+++ b/src/core/frame.c -@@ -33,6 +33,7 @@ - #include "x11/meta-x11-display-private.h" - #include "x11/window-x11-private.h" - #include "x11/window-props.h" -+#include "x11/window-x11.h" - - #include - #include -@@ -66,6 +67,7 @@ meta_window_x11_set_frame_xwindow (MetaWindow *window, - XSetWindowAttributes attrs; - gulong create_serial = 0; - MetaFrame *frame; -+ int child_x, child_y; - - if (window->frame) - return; -@@ -127,11 +129,19 @@ meta_window_x11_set_frame_xwindow (MetaWindow *window, - meta_stack_tracker_record_remove (window->display->stack_tracker, - meta_window_x11_get_xwindow (window), - XNextRequest (x11_display->xdisplay)); -+ meta_window_x11_stage_to_protocol (META_WINDOW_X11 (window), -+ frame->child_x, -+ frame->child_y, -+ 0, 0, -+ &child_x, -+ &child_y, -+ NULL, NULL); -+ - XReparentWindow (x11_display->xdisplay, - meta_window_x11_get_xwindow (window), - frame->xwindow, -- frame->child_x, -- frame->child_y); -+ child_x, -+ child_y); - window->reparents_pending += 1; - /* FIXME handle this error */ - mtk_x11_error_trap_pop (x11_display->xdisplay); -@@ -201,6 +211,8 @@ meta_window_destroy_frame (MetaWindow *window) - - if (!x11_display->closing) - { -+ int child_x, child_y; -+ - if (!window->unmanaging) - { - meta_stack_tracker_record_add (window->display->stack_tracker, -@@ -208,6 +220,14 @@ meta_window_destroy_frame (MetaWindow *window) - XNextRequest (x11_display->xdisplay)); - } - -+ meta_window_x11_stage_to_protocol (META_WINDOW_X11 (window), -+ window->frame->rect.x + borders.invisible.left, -+ window->frame->rect.y + borders.invisible.top, -+ 0, 0, -+ &child_x, -+ &child_y, -+ NULL, NULL); -+ - XReparentWindow (x11_display->xdisplay, - meta_window_x11_get_xwindow (window), - x11_display->xroot, -@@ -215,8 +235,7 @@ meta_window_destroy_frame (MetaWindow *window) - * coordinates here means we'll need to ensure a configure - * notify event is sent; see bug 399552. - */ -- window->frame->rect.x + borders.invisible.left, -- window->frame->rect.y + borders.invisible.top); -+ child_x, child_y); - window->reparents_pending += 1; - } - -@@ -270,6 +289,7 @@ meta_frame_query_borders (MetaFrame *frame, - MetaFrameBorders *borders) - { - MetaWindow *window = frame->window; -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); - MetaX11Display *x11_display = window->display->x11_display; - int format, res; - Atom type; -@@ -293,12 +313,22 @@ meta_frame_query_borders (MetaFrame *frame, - if (mtk_x11_error_trap_pop_with_return (x11_display->xdisplay) == Success && - res == Success && nitems == 4) - { -- borders->invisible = (MetaFrameBorder) { -- ((long *) data)[0], -- ((long *) data)[1], -- ((long *) data)[2], -- ((long *) data)[3], -- }; -+ int left, right, top, bottom; -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ ((long *) data)[0], -+ ((long *) data)[1], -+ ((long *) data)[2], -+ ((long *) data)[3], -+ &left, -+ &right, -+ &top, -+ &bottom); -+ -+ borders->invisible.left = left; -+ borders->invisible.right = right; -+ borders->invisible.top = top; -+ borders->invisible.bottom = bottom; - } - else - { -@@ -321,12 +351,21 @@ meta_frame_query_borders (MetaFrame *frame, - if (mtk_x11_error_trap_pop_with_return (x11_display->xdisplay) == Success && - res == Success && nitems == 4) - { -- borders->visible = (MetaFrameBorder) { -- ((long *) data)[0], -- ((long *) data)[1], -- ((long *) data)[2], -- ((long *) data)[3], -- }; -+ int left, right, top, bottom; -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ ((long *) data)[0], -+ ((long *) data)[1], -+ ((long *) data)[2], -+ ((long *) data)[3], -+ &left, -+ &right, -+ &top, -+ &bottom); -+ borders->visible.left = left; -+ borders->visible.right = right; -+ borders->visible.top = top; -+ borders->visible.bottom = bottom; - } - else - { -@@ -374,7 +413,9 @@ meta_frame_sync_to_window (MetaFrame *frame, - gboolean need_resize) - { - MetaWindow *window = frame->window; -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); - MetaX11Display *x11_display = window->display->x11_display; -+ MtkRectangle rect; - - meta_topic (META_DEBUG_GEOMETRY, - "Syncing frame geometry %d,%d %dx%d (SE: %d,%d)", -@@ -385,12 +426,22 @@ meta_frame_sync_to_window (MetaFrame *frame, - - mtk_x11_error_trap_push (x11_display->xdisplay); - -+ meta_window_x11_stage_to_protocol (window_x11, -+ frame->rect.x, -+ frame->rect.y, -+ frame->rect.width, -+ frame->rect.height, -+ &rect.x, -+ &rect.y, -+ &rect.width, -+ &rect.height); -+ - XMoveResizeWindow (x11_display->xdisplay, - frame->xwindow, -- frame->rect.x, -- frame->rect.y, -- frame->rect.width, -- frame->rect.height); -+ rect.x, -+ rect.y, -+ rect.width, -+ rect.height); - - mtk_x11_error_trap_pop (x11_display->xdisplay); - -@@ -427,6 +478,7 @@ static void - send_configure_notify (MetaFrame *frame) - { - MetaX11Display *x11_display = frame->window->display->x11_display; -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (frame->window); - XEvent event = { 0 }; - - /* We never get told by the frames client, just reassert the -@@ -436,10 +488,16 @@ send_configure_notify (MetaFrame *frame) - event.xconfigure.display = x11_display->xdisplay; - event.xconfigure.event = frame->xwindow; - event.xconfigure.window = frame->xwindow; -- event.xconfigure.x = frame->rect.x; -- event.xconfigure.y = frame->rect.y; -- event.xconfigure.width = frame->rect.width; -- event.xconfigure.height = frame->rect.height; -+ -+ meta_window_x11_stage_to_protocol (window_x11, -+ frame->rect.x, -+ frame->rect.y, -+ frame->rect.width, -+ frame->rect.height, -+ &event.xconfigure.x, -+ &event.xconfigure.y, -+ &event.xconfigure.width, -+ &event.xconfigure.height); - event.xconfigure.border_width = 0; - event.xconfigure.above = None; - event.xconfigure.override_redirect = False; -diff --git a/src/meson.build b/src/meson.build -index 05df3bfd2..e658f98ca 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -949,6 +949,11 @@ dbus_interfaces = [ - 'interface': 'org.gnome.Mutter.DebugControl.xml', - 'prefix': 'org.gnome.Mutter', - }, -+ { -+ 'name': 'meta-dbus-x11', -+ 'interface': 'org.gnome.Mutter.X11.xml', -+ 'prefix': 'org.gnome.Mutter', -+ }, - ] - - if have_profiler -diff --git a/src/meta/meta-context.h b/src/meta/meta-context.h -index ef36bd2c3..2adb9b07e 100644 ---- a/src/meta/meta-context.h -+++ b/src/meta/meta-context.h -@@ -101,3 +101,8 @@ gboolean meta_context_raise_rlimit_nofile (MetaContext *context, - META_EXPORT - gboolean meta_context_restore_rlimit_nofile (MetaContext *context, - GError **error); -+ -+#ifdef HAVE_WAYLAND -+META_EXPORT -+MetaWaylandCompositor * meta_context_get_wayland_compositor (MetaContext *context); -+#endif -diff --git a/src/meta/meta-wayland-compositor.h b/src/meta/meta-wayland-compositor.h -index 7f4a50705..3df92fda5 100644 ---- a/src/meta/meta-wayland-compositor.h -+++ b/src/meta/meta-wayland-compositor.h -@@ -31,9 +31,6 @@ G_DECLARE_FINAL_TYPE (MetaWaylandCompositor, - META, WAYLAND_COMPOSITOR, - GObject) - --META_EXPORT --MetaWaylandCompositor *meta_context_get_wayland_compositor (MetaContext *context); -- - META_EXPORT - struct wl_display *meta_wayland_compositor_get_wayland_display (MetaWaylandCompositor *compositor); - -diff --git a/src/meta/types.h b/src/meta/types.h -index cbe2a9a3d..8fba4a839 100644 ---- a/src/meta/types.h -+++ b/src/meta/types.h -@@ -38,3 +38,7 @@ typedef struct _MetaSettings MetaSettings; - - typedef struct _MetaWorkspaceManager MetaWorkspaceManager; - typedef struct _MetaSelection MetaSelection; -+ -+#ifdef HAVE_WAYLAND -+typedef struct _MetaWaylandCompositor MetaWaylandCompositor; -+#endif -diff --git a/src/wayland/meta-wayland-cursor-surface.c b/src/wayland/meta-wayland-cursor-surface.c -index 87a8895c8..5a16ce7d8 100644 ---- a/src/wayland/meta-wayland-cursor-surface.c -+++ b/src/wayland/meta-wayland-cursor-surface.c -@@ -92,37 +92,29 @@ cursor_sprite_prepare_at (MetaCursorSprite *cursor_sprite, - { - MetaWaylandSurfaceRole *role = META_WAYLAND_SURFACE_ROLE (cursor_surface); - MetaWaylandSurface *surface = meta_wayland_surface_role_get_surface (role); -- -- if (!meta_wayland_surface_is_xwayland (surface)) -+ MetaContext *context = -+ meta_wayland_compositor_get_context (surface->compositor); -+ MetaBackend *backend = meta_context_get_backend (context); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); -+ MetaLogicalMonitor *logical_monitor; -+ -+ logical_monitor = -+ meta_monitor_manager_get_logical_monitor_at (monitor_manager, x, y); -+ if (logical_monitor) - { -- MetaWaylandSurfaceRole *surface_role = -- META_WAYLAND_SURFACE_ROLE (cursor_surface); -- MetaWaylandSurface *surface = -- meta_wayland_surface_role_get_surface (surface_role); -- MetaContext *context = -- meta_wayland_compositor_get_context (surface->compositor); -- MetaBackend *backend = meta_context_get_backend (context); -- MetaMonitorManager *monitor_manager = -- meta_backend_get_monitor_manager (backend); -- MetaLogicalMonitor *logical_monitor; -- -- logical_monitor = -- meta_monitor_manager_get_logical_monitor_at (monitor_manager, x, y); -- if (logical_monitor) -- { -- int surface_scale = surface->applied_state.scale; -- float texture_scale; -- -- if (meta_backend_is_stage_views_scaled (backend)) -- texture_scale = 1.0 / surface_scale; -- else -- texture_scale = (meta_logical_monitor_get_scale (logical_monitor) / -- surface_scale); -- -- meta_cursor_sprite_set_texture_scale (cursor_sprite, texture_scale); -- meta_cursor_sprite_set_texture_transform (cursor_sprite, -- surface->buffer_transform); -- } -+ int surface_scale = surface->applied_state.scale; -+ float texture_scale; -+ -+ if (meta_backend_is_stage_views_scaled (backend)) -+ texture_scale = 1.0 / surface_scale; -+ else -+ texture_scale = (meta_logical_monitor_get_scale (logical_monitor) / -+ surface_scale); -+ -+ meta_cursor_sprite_set_texture_scale (cursor_sprite, texture_scale); -+ meta_cursor_sprite_set_texture_transform (cursor_sprite, -+ surface->buffer_transform); - } - - meta_wayland_surface_update_outputs (surface); -diff --git a/src/wayland/meta-wayland-outputs.c b/src/wayland/meta-wayland-outputs.c -index 89ae86445..f957bc339 100644 ---- a/src/wayland/meta-wayland-outputs.c -+++ b/src/wayland/meta-wayland-outputs.c -@@ -31,6 +31,10 @@ - #include "backends/meta-monitor-manager-private.h" - #include "wayland/meta-wayland-private.h" - -+#ifdef HAVE_XWAYLAND -+#include "wayland/meta-xwayland.h" -+#endif -+ - #include "xdg-output-unstable-v1-server-protocol.h" - - /* Wayland protocol headers list new additions, not deprecations */ -@@ -50,6 +54,8 @@ struct _MetaWaylandOutput - { - GObject parent; - -+ MetaWaylandCompositor *compositor; -+ - struct wl_global *global; - GList *resources; - GList *xdg_output_resources; -@@ -422,6 +428,7 @@ meta_wayland_output_new (MetaWaylandCompositor *compositor, - MetaWaylandOutput *wayland_output; - - wayland_output = g_object_new (META_TYPE_WAYLAND_OUTPUT, NULL); -+ wayland_output->compositor = compositor; - wayland_output->global = wl_global_create (compositor->wayland_display, - &wl_output_interface, - META_WL_OUTPUT_VERSION, -@@ -596,6 +603,37 @@ static const struct zxdg_output_v1_interface - meta_xdg_output_destroy, - }; - -+#ifdef HAVE_XWAYLAND -+static gboolean -+is_xwayland_resource (MetaWaylandOutput *wayland_output, -+ struct wl_resource *resource) -+{ -+ MetaXWaylandManager *manager = &wayland_output->compositor->xwayland_manager; -+ -+ return resource && wl_resource_get_client (resource) == manager->client; -+} -+#endif -+ -+static void -+maybe_scale_for_xwayland (MetaWaylandOutput *wayland_output, -+ struct wl_resource *resource, -+ int *x, -+ int *y) -+{ -+#ifdef HAVE_XWAYLAND -+ if (is_xwayland_resource (wayland_output, resource)) -+ { -+ MetaXWaylandManager *xwayland_manager = -+ &wayland_output->compositor->xwayland_manager; -+ int xwayland_scale; -+ -+ xwayland_scale = meta_xwayland_get_effective_scale (xwayland_manager); -+ *x *= xwayland_scale; -+ *y *= xwayland_scale; -+ } -+#endif -+} -+ - static void - send_xdg_output_events (struct wl_resource *resource, - MetaWaylandOutput *wayland_output, -@@ -616,6 +654,7 @@ send_xdg_output_events (struct wl_resource *resource, - if (need_all_events || - old_layout.x != layout.x || old_layout.y != layout.y) - { -+ maybe_scale_for_xwayland (wayland_output, resource, &layout.x, &layout.y); - zxdg_output_v1_send_logical_position (resource, layout.x, layout.y); - need_done = TRUE; - } -@@ -623,6 +662,7 @@ send_xdg_output_events (struct wl_resource *resource, - if (need_all_events || - old_layout.width != layout.width || old_layout.height != layout.height) - { -+ maybe_scale_for_xwayland (wayland_output, resource, &layout.width, &layout.height); - zxdg_output_v1_send_logical_size (resource, layout.width, layout.height); - need_done = TRUE; - } -@@ -745,7 +785,7 @@ meta_wayland_outputs_init (MetaWaylandCompositor *compositor) - MetaMonitorManager *monitor_manager = - meta_backend_get_monitor_manager (backend); - -- g_signal_connect (monitor_manager, "monitors-changed-internal", -+ g_signal_connect (monitor_manager, "monitors-changed", - G_CALLBACK (on_monitors_changed), compositor); - - compositor->outputs = -diff --git a/src/wayland/meta-wayland-pointer.c b/src/wayland/meta-wayland-pointer.c -index 88b27f84d..324092970 100644 ---- a/src/wayland/meta-wayland-pointer.c -+++ b/src/wayland/meta-wayland-pointer.c -@@ -1244,6 +1244,20 @@ pointer_set_cursor (struct wl_client *client, - cursor_surface = META_WAYLAND_CURSOR_SURFACE (surface->role); - meta_wayland_cursor_surface_set_renderer (cursor_surface, - cursor_renderer); -+ -+#ifdef HAVE_XWAYLAND -+ if (meta_wayland_surface_is_xwayland (surface)) -+ { -+ MetaXWaylandManager *xwayland_manager = -+ &surface->compositor->xwayland_manager; -+ int scale; -+ -+ scale = meta_xwayland_get_effective_scale (xwayland_manager); -+ hot_x = round (hot_x / (double) scale); -+ hot_y = round (hot_y / (double) scale); -+ } -+#endif -+ - meta_wayland_cursor_surface_set_hotspot (cursor_surface, - hot_x, hot_y); - -diff --git a/src/wayland/meta-wayland-private.h b/src/wayland/meta-wayland-private.h -index e8d442c03..834753ffd 100644 ---- a/src/wayland/meta-wayland-private.h -+++ b/src/wayland/meta-wayland-private.h -@@ -77,6 +77,8 @@ struct _MetaXWaylandManager - int rr_error_base; - - gboolean should_enable_ei_portal; -+ -+ double highest_monitor_scale; - }; - - struct _MetaWaylandCompositor -diff --git a/src/wayland/meta-wayland-surface.c b/src/wayland/meta-wayland-surface.c -index 6dc5006b7..81ee47bbd 100644 ---- a/src/wayland/meta-wayland-surface.c -+++ b/src/wayland/meta-wayland-surface.c -@@ -781,8 +781,19 @@ meta_wayland_surface_apply_state (MetaWaylandSurface *surface, - state->buffer->type != META_WAYLAND_BUFFER_TYPE_SINGLE_PIXEL)); - } - -- if (state->scale > 0) -- surface->applied_state.scale = state->scale; -+ if (meta_wayland_surface_is_xwayland (surface)) -+ { -+#ifdef HAVE_XWAYLAND -+ MetaXWaylandManager *xwayland_manager = -+ &surface->compositor->xwayland_manager; -+ -+ surface->applied_state.scale = meta_xwayland_get_effective_scale (xwayland_manager); -+#endif -+ } -+ else if (state->scale > 0) -+ { -+ surface->applied_state.scale = state->scale; -+ } - - if (state->has_new_buffer_transform) - surface->buffer_transform = state->buffer_transform; -@@ -977,8 +988,9 @@ meta_wayland_surface_commit (MetaWaylandSurface *surface) - MetaMultiTexture *committed_texture = surface->committed_state.texture; - int committed_scale = surface->committed_state.scale; - -- if ((meta_multi_texture_get_width (committed_texture) % committed_scale != 0) || -- (meta_multi_texture_get_height (committed_texture) % committed_scale != 0)) -+ if (((meta_multi_texture_get_width (committed_texture) % committed_scale != 0) || -+ (meta_multi_texture_get_height (committed_texture) % committed_scale != 0)) && -+ !meta_wayland_surface_is_xwayland (surface)) - { - if (!surface->role || !META_IS_WAYLAND_CURSOR_SURFACE (surface->role)) - { -@@ -1506,6 +1518,16 @@ meta_wayland_surface_update_outputs (MetaWaylandSurface *surface) - g_hash_table_foreach (surface->compositor->outputs, - update_surface_output_state, - surface); -+ -+ if (meta_wayland_surface_is_xwayland (surface)) -+ { -+#ifdef HAVE_XWAYLAND -+ MetaXWaylandManager *xwayland_manager = -+ &surface->compositor->xwayland_manager; -+ -+ surface->applied_state.scale = meta_xwayland_get_effective_scale (xwayland_manager); -+#endif -+ } - } - - void -diff --git a/src/wayland/meta-window-xwayland.c b/src/wayland/meta-window-xwayland.c -index 1299a351c..5fb006962 100644 ---- a/src/wayland/meta-window-xwayland.c -+++ b/src/wayland/meta-window-xwayland.c -@@ -27,8 +27,9 @@ - #include "x11/window-x11-private.h" - #include "x11/xprops.h" - #include "wayland/meta-window-xwayland.h" --#include "wayland/meta-wayland.h" -+#include "wayland/meta-wayland-private.h" - #include "wayland/meta-wayland-surface-private.h" -+#include "wayland/meta-xwayland.h" - - enum - { -@@ -315,6 +316,72 @@ meta_window_xwayland_process_property_notify (MetaWindow *window, - meta_window_queue (window, META_QUEUE_MOVE_RESIZE); - } - -+static void -+meta_window_xwayland_stage_to_protocol (MetaWindowX11 *window_x11, -+ int stage_x, -+ int stage_y, -+ int stage_width, -+ int stage_height, -+ int *protocol_x, -+ int *protocol_y, -+ int *protocol_width, -+ int *protocol_height) -+{ -+ MetaDisplay *display = meta_window_get_display (META_WINDOW (window_x11)); -+ MetaContext *context = meta_display_get_context (display); -+ MetaWaylandCompositor *wayland_compositor = -+ meta_context_get_wayland_compositor (context); -+ MetaXWaylandManager *xwayland_manager = &wayland_compositor->xwayland_manager; -+ int scale; -+ -+ scale = meta_xwayland_get_effective_scale (xwayland_manager); -+ if (protocol_x) -+ *protocol_x = stage_x * scale; -+ if (protocol_y) -+ *protocol_y = stage_y * scale; -+ if (protocol_width) -+ *protocol_width = stage_width * scale; -+ if (protocol_height) -+ *protocol_height = stage_height * scale; -+} -+ -+static void -+meta_window_xwayland_protocol_to_stage (MetaWindowX11 *window_x11, -+ int protocol_x, -+ int protocol_y, -+ int protocol_width, -+ int protocol_height, -+ int *stage_x, -+ int *stage_y, -+ int *stage_width, -+ int *stage_height) -+{ -+ MetaDisplay *display = meta_window_get_display (META_WINDOW (window_x11)); -+ MetaContext *context = meta_display_get_context (display); -+ MetaWaylandCompositor *wayland_compositor = -+ meta_context_get_wayland_compositor (context); -+ MetaXWaylandManager *xwayland_manager = &wayland_compositor->xwayland_manager; -+ MtkRectangle rect; -+ int scale; -+ -+ rect.x = protocol_x; -+ rect.y = protocol_y; -+ rect.width = protocol_width; -+ rect.height = protocol_height; -+ -+ scale = meta_xwayland_get_effective_scale (xwayland_manager); -+ mtk_rectangle_scale_double (&rect, 1.0 / scale, MTK_ROUNDING_STRATEGY_GROW, &rect); -+ -+ if (stage_x) -+ *stage_x = rect.x; -+ if (stage_y) -+ *stage_y = rect.y; -+ if (stage_width) -+ *stage_width = rect.width; -+ if (stage_height) -+ *stage_height = rect.height; -+} -+ - static void - meta_window_xwayland_class_init (MetaWindowXwaylandClass *klass) - { -@@ -331,6 +398,8 @@ meta_window_xwayland_class_init (MetaWindowXwaylandClass *klass) - window_x11_class->thaw_commits = meta_window_xwayland_thaw_commits; - window_x11_class->always_update_shape = meta_window_xwayland_always_update_shape; - window_x11_class->process_property_notify = meta_window_xwayland_process_property_notify; -+ window_x11_class->stage_to_protocol = meta_window_xwayland_stage_to_protocol; -+ window_x11_class->protocol_to_stage = meta_window_xwayland_protocol_to_stage; - - gobject_class->get_property = meta_window_xwayland_get_property; - gobject_class->set_property = meta_window_xwayland_set_property; -diff --git a/src/wayland/meta-xwayland-private.h b/src/wayland/meta-xwayland-private.h -index 7a9cb73fd..9e06f0315 100644 ---- a/src/wayland/meta-xwayland-private.h -+++ b/src/wayland/meta-xwayland-private.h -@@ -20,6 +20,7 @@ - #include - - #include "wayland/meta-wayland-private.h" -+#include "wayland/meta-xwayland.h" - - gboolean - meta_xwayland_init (MetaXWaylandManager *manager, -diff --git a/src/wayland/meta-xwayland-surface.c b/src/wayland/meta-xwayland-surface.c -index 8fa1c72a9..c6daf9b26 100644 ---- a/src/wayland/meta-xwayland-surface.c -+++ b/src/wayland/meta-xwayland-surface.c -@@ -163,13 +163,19 @@ meta_xwayland_surface_get_relative_coordinates (MetaWaylandSurfaceRole *surface_ - float *out_sy) - { - MetaXwaylandSurface *xwayland_surface = META_XWAYLAND_SURFACE (surface_role); -+ MetaWaylandSurface *surface = -+ meta_wayland_surface_role_get_surface (surface_role); -+ MetaWaylandCompositor *compositor = -+ meta_wayland_surface_get_compositor (surface); - MtkRectangle window_rect = { 0 }; -+ int xwayland_scale; - - if (xwayland_surface->window) - meta_window_get_buffer_rect (xwayland_surface->window, &window_rect); - -- *out_sx = abs_x - window_rect.x; -- *out_sy = abs_y - window_rect.y; -+ xwayland_scale = meta_xwayland_get_effective_scale (&compositor->xwayland_manager); -+ *out_sx = (abs_x - window_rect.x) * xwayland_scale; -+ *out_sy = (abs_y - window_rect.y) * xwayland_scale; - } - - static MetaWaylandSurface * -diff --git a/src/wayland/meta-xwayland.c b/src/wayland/meta-xwayland.c -index ea9c27d74..828e6f64e 100644 ---- a/src/wayland/meta-xwayland.c -+++ b/src/wayland/meta-xwayland.c -@@ -1051,6 +1051,29 @@ meta_xwayland_shutdown (MetaWaylandCompositor *compositor) - } - } - -+static void -+update_highest_monitor_scale (MetaXWaylandManager *manager) -+{ -+ MetaWaylandCompositor *compositor = manager->compositor; -+ MetaContext *context = meta_wayland_compositor_get_context (compositor); -+ MetaBackend *backend = meta_context_get_backend (context); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); -+ GList *logical_monitors; -+ GList *l; -+ double scale = 1.0; -+ -+ logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager); -+ for (l = logical_monitors; l; l = l->next) -+ { -+ MetaLogicalMonitor *logical_monitor = l->data; -+ -+ scale = MAX (scale, meta_logical_monitor_get_scale (logical_monitor)); -+ } -+ -+ manager->highest_monitor_scale = scale; -+} -+ - gboolean - meta_xwayland_init (MetaXWaylandManager *manager, - MetaWaylandCompositor *compositor, -@@ -1058,6 +1081,9 @@ meta_xwayland_init (MetaXWaylandManager *manager, - GError **error) - { - MetaContext *context = compositor->context; -+ MetaBackend *backend = meta_context_get_backend (context); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); - MetaX11DisplayPolicy policy; - int display = 0; - -@@ -1121,6 +1147,10 @@ meta_xwayland_init (MetaXWaylandManager *manager, - /* Xwayland specific protocol, needs to be filtered out for all other clients */ - meta_xwayland_grab_keyboard_init (compositor); - -+ g_signal_connect_swapped (monitor_manager, "monitors-changed-internal", -+ G_CALLBACK (update_highest_monitor_scale), manager); -+ update_highest_monitor_scale (manager); -+ - return TRUE; - } - -@@ -1312,3 +1342,29 @@ meta_xwayland_set_should_enable_ei_portal (MetaXWaylandManager *manager, - { - manager->should_enable_ei_portal = should_enable_ei_portal; - } -+ -+int -+meta_xwayland_get_effective_scale (MetaXWaylandManager *manager) -+{ -+ MetaWaylandCompositor *compositor = manager->compositor; -+ MetaContext *context = meta_wayland_compositor_get_context (compositor); -+ MetaBackend *backend = meta_context_get_backend (context); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); -+ MetaSettings *settings = meta_backend_get_settings (backend); -+ -+ switch (meta_monitor_manager_get_layout_mode (monitor_manager)) -+ { -+ case META_LOGICAL_MONITOR_LAYOUT_MODE_PHYSICAL: -+ break; -+ -+ case META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL: -+ if (meta_settings_is_experimental_feature_enabled (settings, -+ META_EXPERIMENTAL_FEATURE_XWAYLAND_NATIVE_SCALING) && -+ meta_settings_is_experimental_feature_enabled (settings, -+ META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER)) -+ return ceil (manager->highest_monitor_scale); -+ } -+ -+ return 1; -+} -diff --git a/src/wayland/meta-xwayland.h b/src/wayland/meta-xwayland.h -index daf9d1abb..ae7a06977 100644 ---- a/src/wayland/meta-xwayland.h -+++ b/src/wayland/meta-xwayland.h -@@ -48,3 +48,5 @@ META_EXPORT_TEST - gboolean meta_xwayland_signal (MetaXWaylandManager *manager, - int signum, - GError **error); -+ -+int meta_xwayland_get_effective_scale (MetaXWaylandManager *manager); -diff --git a/src/x11/meta-x11-display.c b/src/x11/meta-x11-display.c -index 438f3bd1e..f2a613392 100644 ---- a/src/x11/meta-x11-display.c -+++ b/src/x11/meta-x11-display.c -@@ -70,7 +70,7 @@ - #include "wayland/meta-xwayland-private.h" - #endif - --G_DEFINE_TYPE (MetaX11Display, meta_x11_display, G_TYPE_OBJECT) -+#include "meta-dbus-x11.h" - - static GQuark quark_x11_display_logical_monitor_data = 0; - -@@ -89,6 +89,14 @@ typedef struct _MetaX11DisplayLogicalMonitorData - int xinerama_index; - } MetaX11DisplayLogicalMonitorData; - -+typedef struct _MetaX11DisplayPrivate -+{ -+ MetaDBusX11 *dbus_api; -+ guint dbus_name_id; -+} MetaX11DisplayPrivate; -+ -+G_DEFINE_TYPE_WITH_PRIVATE (MetaX11Display, meta_x11_display, G_TYPE_OBJECT) -+ - static char *get_screen_name (Display *xdisplay, - int number); - -@@ -122,6 +130,42 @@ backend_from_x11_display (MetaX11Display *x11_display) - return meta_context_get_backend (context); - } - -+static void -+stage_to_protocol (MetaX11Display *x11_display, -+ int stage_x, -+ int stage_y, -+ int *protocol_x, -+ int *protocol_y) -+{ -+ MetaDisplay *display = meta_x11_display_get_display (x11_display); -+ MetaContext *context = meta_display_get_context (display); -+ int scale = 1; -+ -+ switch (meta_context_get_compositor_type (context)) -+ { -+ case META_COMPOSITOR_TYPE_WAYLAND: -+ { -+#ifdef HAVE_XWAYLAND -+ MetaWaylandCompositor *wayland_compositor = -+ meta_context_get_wayland_compositor (context); -+ MetaXWaylandManager *xwayland_manager = -+ &wayland_compositor->xwayland_manager; -+ -+ scale = meta_xwayland_get_effective_scale (xwayland_manager); -+#endif -+ break; -+ } -+ -+ case META_COMPOSITOR_TYPE_X11: -+ break; -+ } -+ -+ if (protocol_x) -+ *protocol_x = stage_x * scale; -+ if (protocol_y) -+ *protocol_y = stage_y * scale; -+} -+ - static void - meta_x11_display_unmanage_windows (MetaX11Display *x11_display) - { -@@ -151,13 +195,68 @@ meta_x11_event_filter_free (MetaX11EventFilter *filter) - g_free (filter); - } - -+static void -+on_bus_acquired (GDBusConnection *connection, -+ const char *name, -+ gpointer user_data) -+{ -+ MetaX11Display *x11_display = user_data; -+ MetaX11DisplayPrivate *priv = -+ meta_x11_display_get_instance_private (x11_display); -+ -+ g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (priv->dbus_api), -+ connection, -+ "/org/gnome/Mutter/X11", -+ NULL); -+} -+ -+static void -+update_ui_scaling_factor (MetaX11Display *x11_display) -+{ -+ MetaX11DisplayPrivate *priv = -+ meta_x11_display_get_instance_private (x11_display); -+ MetaBackend *backend = backend_from_x11_display (x11_display); -+ MetaContext *context = meta_backend_get_context (backend); -+ int ui_scaling_factor = 1; -+ -+ switch (meta_context_get_compositor_type (context)) -+ { -+ case META_COMPOSITOR_TYPE_WAYLAND: -+ { -+#ifdef HAVE_XWAYLAND -+ MetaWaylandCompositor *wayland_compositor = -+ meta_context_get_wayland_compositor (context); -+ MetaXWaylandManager *xwayland_manager = -+ &wayland_compositor->xwayland_manager; -+ -+ ui_scaling_factor = meta_xwayland_get_effective_scale (xwayland_manager); -+#endif -+ break; -+ } -+ case META_COMPOSITOR_TYPE_X11: -+ { -+ MetaSettings *settings = meta_backend_get_settings (backend); -+ -+ ui_scaling_factor = meta_settings_get_ui_scaling_factor (settings); -+ break; -+ } -+ } -+ -+ meta_dbus_x11_set_ui_scaling_factor (priv->dbus_api, ui_scaling_factor); -+} -+ - static void - meta_x11_display_dispose (GObject *object) - { - MetaX11Display *x11_display = META_X11_DISPLAY (object); -+ MetaX11DisplayPrivate *priv = -+ meta_x11_display_get_instance_private (x11_display); - - x11_display->closing = TRUE; - -+ g_clear_handle_id (&priv->dbus_name_id, g_bus_unown_name); -+ g_clear_object (&priv->dbus_api); -+ - g_clear_pointer (&x11_display->alarm_filters, g_ptr_array_unref); - - g_clear_list (&x11_display->event_funcs, -@@ -572,6 +671,9 @@ set_desktop_geometry_hint (MetaX11Display *x11_display) - return; - - meta_display_get_size (x11_display->display, &monitor_width, &monitor_height); -+ stage_to_protocol (x11_display, -+ monitor_width, monitor_height, -+ &monitor_width, &monitor_height); - - data[0] = monitor_width; - data[1] = monitor_height; -@@ -981,14 +1083,22 @@ set_workspace_work_area_hint (MetaWorkspace *workspace, - - for (l = logical_monitors; l; l = l->next) - { -- MtkRectangle area; -+ MtkRectangle stage_area; -+ MtkRectangle protocol_area; - -- meta_workspace_get_work_area_for_logical_monitor (workspace, l->data, &area); -+ meta_workspace_get_work_area_for_logical_monitor (workspace, l->data, -+ &stage_area); - -- tmp[0] = area.x; -- tmp[1] = area.y; -- tmp[2] = area.width; -- tmp[3] = area.height; -+ stage_to_protocol (x11_display, -+ stage_area.x, stage_area.y, -+ &protocol_area.x, &protocol_area.y); -+ stage_to_protocol (x11_display, -+ stage_area.width, stage_area.height, -+ &protocol_area.width, &protocol_area.height); -+ tmp[0] = protocol_area.x; -+ tmp[1] = protocol_area.y; -+ tmp[2] = protocol_area.width; -+ tmp[3] = protocol_area.height; - - tmp += 4; - } -@@ -1017,7 +1127,6 @@ set_work_area_hint (MetaDisplay *display, - int num_workspaces; - GList *l; - unsigned long *data, *tmp; -- MtkRectangle area; - - num_workspaces = meta_workspace_manager_get_n_workspaces (workspace_manager); - data = g_new (unsigned long, num_workspaces * 4); -@@ -1026,14 +1135,22 @@ set_work_area_hint (MetaDisplay *display, - for (l = workspace_manager->workspaces; l; l = l->next) - { - MetaWorkspace *workspace = l->data; -+ MtkRectangle stage_area; -+ MtkRectangle protocol_area; - -- meta_workspace_get_work_area_all_monitors (workspace, &area); -+ meta_workspace_get_work_area_all_monitors (workspace, &stage_area); - set_workspace_work_area_hint (workspace, x11_display); - -- tmp[0] = area.x; -- tmp[1] = area.y; -- tmp[2] = area.width; -- tmp[3] = area.height; -+ stage_to_protocol (x11_display, -+ stage_area.x, stage_area.y, -+ &protocol_area.x, &protocol_area.y); -+ stage_to_protocol (x11_display, -+ stage_area.width, stage_area.height, -+ &protocol_area.width, &protocol_area.height); -+ tmp[0] = protocol_area.x; -+ tmp[1] = protocol_area.y; -+ tmp[2] = protocol_area.width; -+ tmp[3] = protocol_area.height; - - tmp += 4; - } -@@ -1196,6 +1313,58 @@ meta_x11_display_init_frames_client (MetaX11Display *x11_display) - on_frames_client_died, x11_display); - } - -+static void -+initialize_dbus_interface (MetaX11Display *x11_display) -+{ -+ MetaX11DisplayPrivate *priv = -+ meta_x11_display_get_instance_private (x11_display); -+ -+ priv->dbus_api = meta_dbus_x11_skeleton_new (); -+ priv->dbus_name_id = -+ g_bus_own_name (G_BUS_TYPE_SESSION, -+ "org.gnome.Mutter.X11", -+ G_BUS_NAME_OWNER_FLAGS_NONE, -+ on_bus_acquired, -+ NULL, NULL, -+ x11_display, NULL); -+ update_ui_scaling_factor (x11_display); -+} -+ -+static void -+experimental_features_changed (MetaSettings *settings, -+ MetaExperimentalFeature old_experimental_features, -+ MetaX11Display *x11_display) -+{ -+ gboolean was_xwayland_native_scaling; -+ gboolean was_stage_views_scaled; -+ gboolean is_xwayland_native_scaling; -+ gboolean is_stage_views_scaled; -+ -+ was_xwayland_native_scaling = -+ !!(old_experimental_features & -+ META_EXPERIMENTAL_FEATURE_XWAYLAND_NATIVE_SCALING); -+ was_stage_views_scaled = -+ !!(old_experimental_features & -+ META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER); -+ -+ is_xwayland_native_scaling = -+ meta_settings_is_experimental_feature_enabled ( -+ settings, -+ META_EXPERIMENTAL_FEATURE_XWAYLAND_NATIVE_SCALING); -+ is_stage_views_scaled = -+ meta_settings_is_experimental_feature_enabled ( -+ settings, -+ META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER); -+ -+ if (is_xwayland_native_scaling != was_xwayland_native_scaling || -+ is_stage_views_scaled != was_stage_views_scaled) -+ { -+ update_ui_scaling_factor (x11_display); -+ set_desktop_geometry_hint (x11_display); -+ set_work_area_hint (x11_display->display, x11_display); -+ } -+} -+ - /** - * meta_x11_display_new: - * -@@ -1214,6 +1383,7 @@ meta_x11_display_new (MetaDisplay *display, - MetaBackend *backend = meta_context_get_backend (context); - MetaMonitorManager *monitor_manager = - meta_backend_get_monitor_manager (backend); -+ MetaSettings *settings = meta_backend_get_settings (backend); - g_autoptr (MetaX11Display) x11_display = NULL; - Display *xdisplay; - Screen *xscreen; -@@ -1290,6 +1460,8 @@ meta_x11_display_new (MetaDisplay *display, - x11_display = g_object_new (META_TYPE_X11_DISPLAY, NULL); - x11_display->display = display; - -+ initialize_dbus_interface (x11_display); -+ - /* here we use XDisplayName which is what the user - * probably put in, vs. DisplayString(display) which is - * canonicalized by XOpenDisplay() -@@ -1382,7 +1554,7 @@ meta_x11_display_new (MetaDisplay *display, - "monitors-changed-internal", - G_CALLBACK (on_monitors_changed_internal), - x11_display, -- 0); -+ G_CONNECT_AFTER); - - init_leader_window (x11_display, ×tamp); - x11_display->timestamp = timestamp; -@@ -1475,6 +1647,11 @@ meta_x11_display_new (MetaDisplay *display, - - meta_prefs_add_listener (prefs_changed_callback, x11_display); - -+ g_signal_connect_object (settings, -+ "experimental-features-changed", -+ G_CALLBACK (experimental_features_changed), -+ x11_display, 0); -+ - set_work_area_hint (display, x11_display); - - g_signal_connect_object (display, "workareas-changed", -@@ -1683,16 +1860,12 @@ meta_x11_display_reload_cursor (MetaX11Display *x11_display) - } - - static void --set_cursor_theme (Display *xdisplay, -- MetaBackend *backend) -+set_cursor_theme (Display *xdisplay, -+ const char *theme, -+ int size) - { -- MetaSettings *settings = meta_backend_get_settings (backend); -- int scale; -- -- scale = meta_settings_get_ui_scaling_factor (settings); -- XcursorSetTheme (xdisplay, meta_prefs_get_cursor_theme ()); -- XcursorSetDefaultSize (xdisplay, -- meta_prefs_get_cursor_size () * scale); -+ XcursorSetTheme (xdisplay, theme); -+ XcursorSetDefaultSize (xdisplay, size); - } - - static void -@@ -1744,8 +1917,37 @@ static void - update_cursor_theme (MetaX11Display *x11_display) - { - MetaBackend *backend = backend_from_x11_display (x11_display); -+ MetaContext *context = meta_backend_get_context (backend); -+ MetaSettings *settings = meta_backend_get_settings (backend); -+ int scale = 1; -+ int size; -+ const char *theme; -+ -+ switch (meta_context_get_compositor_type (context)) -+ { -+ case META_COMPOSITOR_TYPE_WAYLAND: -+ { -+#ifdef HAVE_XWAYLAND -+ MetaWaylandCompositor *wayland_compositor = -+ meta_context_get_wayland_compositor (context); -+ MetaXWaylandManager *xwayland_manager = -+ &wayland_compositor->xwayland_manager; - -- set_cursor_theme (x11_display->xdisplay, backend); -+ scale = meta_xwayland_get_effective_scale (xwayland_manager); -+#endif -+ break; -+ } -+ -+ case META_COMPOSITOR_TYPE_X11: -+ scale = meta_settings_get_ui_scaling_factor (settings); -+ break; -+ } -+ -+ size = meta_prefs_get_cursor_size () * scale; -+ -+ theme = meta_prefs_get_cursor_theme (); -+ -+ set_cursor_theme (x11_display->xdisplay, theme, size); - schedule_reload_x11_cursor (x11_display); - - if (META_IS_BACKEND_X11 (backend)) -@@ -1753,7 +1955,7 @@ update_cursor_theme (MetaX11Display *x11_display) - MetaBackendX11 *backend_x11 = META_BACKEND_X11 (backend); - Display *xdisplay = meta_backend_x11_get_xdisplay (backend_x11); - -- set_cursor_theme (xdisplay, backend); -+ set_cursor_theme (xdisplay, theme, size); - meta_backend_x11_reload_cursor (backend_x11); - } - } -@@ -1946,6 +2148,8 @@ on_monitors_changed_internal (MetaMonitorManager *monitor_manager, - } - - x11_display->has_xinerama_indices = FALSE; -+ -+ update_ui_scaling_factor (x11_display); - } - - static Bool -diff --git a/src/x11/window-props.c b/src/x11/window-props.c -index c18b3eab5..494fbe843 100644 ---- a/src/x11/window-props.c -+++ b/src/x11/window-props.c -@@ -305,10 +305,15 @@ reload_icon_geometry (MetaWindow *window, - { - MtkRectangle geometry; - -- geometry.x = (int)value->v.cardinal_list.cardinals[0]; -- geometry.y = (int)value->v.cardinal_list.cardinals[1]; -- geometry.width = (int)value->v.cardinal_list.cardinals[2]; -- geometry.height = (int)value->v.cardinal_list.cardinals[3]; -+ meta_window_x11_protocol_to_stage (META_WINDOW_X11 (window), -+ value->v.cardinal_list.cardinals[0], -+ value->v.cardinal_list.cardinals[1], -+ value->v.cardinal_list.cardinals[2], -+ value->v.cardinal_list.cardinals[3], -+ &geometry.x, -+ &geometry.y, -+ &geometry.width, -+ &geometry.height); - - meta_window_set_icon_geometry (window, &geometry); - } -@@ -370,11 +375,24 @@ reload_gtk_frame_extents (MetaWindow *window, - } - else - { -+ int left, right, top, bottom; - MetaFrameBorder extents; -- extents.left = (int)value->v.cardinal_list.cardinals[0]; -- extents.right = (int)value->v.cardinal_list.cardinals[1]; -- extents.top = (int)value->v.cardinal_list.cardinals[2]; -- extents.bottom = (int)value->v.cardinal_list.cardinals[3]; -+ -+ meta_window_x11_protocol_to_stage (META_WINDOW_X11 (window), -+ value->v.cardinal_list.cardinals[0], -+ value->v.cardinal_list.cardinals[1], -+ value->v.cardinal_list.cardinals[2], -+ value->v.cardinal_list.cardinals[3], -+ &left, -+ &right, -+ &top, -+ &bottom); -+ -+ extents.left = left; -+ extents.right = right; -+ extents.top = top; -+ extents.bottom = bottom; -+ - meta_window_set_custom_frame_extents (window, &extents, initial); - } - } -@@ -678,10 +696,16 @@ reload_opaque_region (MetaWindow *window, - { - MtkRectangle *rect = &rects[rect_index]; - -- rect->x = region[i++]; -- rect->y = region[i++]; -- rect->width = region[i++]; -- rect->height = region[i++]; -+ meta_window_x11_protocol_to_stage (META_WINDOW_X11 (window), -+ region[i + 0], -+ region[i + 1], -+ region[i + 2], -+ region[i + 3], -+ &rect->x, -+ &rect->y, -+ &rect->width, -+ &rect->height); -+ i += 4; - - rect_index++; - } -@@ -1245,9 +1269,65 @@ meta_set_normal_hints (MetaWindow *window, - * as if flags were zero - */ - if (hints) -- window->size_hints = *(MetaSizeHints*)(hints); -+ { -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); -+ -+ window->size_hints = *(MetaSizeHints *) hints; -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->x, hints->y, -+ hints->width, hints->height, -+ &window->size_hints.x, -+ &window->size_hints.y, -+ &window->size_hints.width, -+ &window->size_hints.height); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->min_width, hints->min_height, -+ 0, 0, -+ &window->size_hints.min_width, -+ &window->size_hints.min_height, -+ NULL, NULL); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->max_width, hints->max_height, -+ 0, 0, -+ &window->size_hints.max_width, -+ &window->size_hints.max_height, -+ NULL, NULL); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->width_inc, hints->height_inc, -+ 0, 0, -+ &window->size_hints.width_inc, -+ &window->size_hints.height_inc, -+ NULL, NULL); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->min_aspect.x, hints->min_aspect.y, -+ 0, 0, -+ &window->size_hints.min_aspect.x, -+ &window->size_hints.min_aspect.y, -+ NULL, NULL); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->max_aspect.x, hints->max_aspect.y, -+ 0, 0, -+ &window->size_hints.max_aspect.x, -+ &window->size_hints.max_aspect.y, -+ NULL, NULL); -+ -+ meta_window_x11_protocol_to_stage (window_x11, -+ hints->base_width, hints->base_height, -+ 0, 0, -+ &window->size_hints.base_width, -+ &window->size_hints.base_height, -+ NULL, NULL); -+ } - else -- window->size_hints.flags = 0; -+ { -+ window->size_hints.flags = 0; -+ } - - /* Put back saved ConfigureRequest. */ - window->size_hints.x = x; -diff --git a/src/x11/window-x11.c b/src/x11/window-x11.c -index 6d2016e3e..897bf946d 100644 ---- a/src/x11/window-x11.c -+++ b/src/x11/window-x11.c -@@ -110,6 +110,113 @@ meta_window_x11_get_private (MetaWindowX11 *window_x11) - return meta_window_x11_get_instance_private (window_x11); - } - -+static void -+meta_window_x11_real_stage_to_protocol (MetaWindowX11 *window_x11, -+ int stage_x, -+ int stage_y, -+ int stage_width, -+ int stage_height, -+ int *protocol_x, -+ int *protocol_y, -+ int *protocol_width, -+ int *protocol_height) -+{ -+ if (protocol_x) -+ *protocol_x = stage_x; -+ if (protocol_y) -+ *protocol_y = stage_y; -+ if (protocol_width) -+ *protocol_width = stage_width; -+ if (protocol_height) -+ *protocol_height = stage_height; -+} -+ -+static void -+meta_window_x11_real_protocol_to_stage (MetaWindowX11 *window_x11, -+ int protocol_x, -+ int protocol_y, -+ int protocol_width, -+ int protocol_height, -+ int *stage_x, -+ int *stage_y, -+ int *stage_width, -+ int *stage_height) -+{ -+ if (stage_x) -+ *stage_x = protocol_x; -+ if (stage_y) -+ *stage_y = protocol_y; -+ if (stage_width) -+ *stage_width = protocol_width; -+ if (stage_height) -+ *stage_height = protocol_height; -+} -+ -+void -+meta_window_x11_stage_to_protocol (MetaWindowX11 *window_x11, -+ int stage_x, -+ int stage_y, -+ int stage_width, -+ int stage_height, -+ int *protocol_x, -+ int *protocol_y, -+ int *protocol_width, -+ int *protocol_height) -+{ -+ MetaWindowX11Class *klass = META_WINDOW_X11_GET_CLASS (window_x11); -+ -+ klass->stage_to_protocol (window_x11, -+ stage_x, stage_y, -+ stage_width, stage_height, -+ protocol_x, protocol_y, -+ protocol_width, protocol_height); -+} -+ -+void -+meta_window_x11_protocol_to_stage (MetaWindowX11 *window_x11, -+ int protocol_x, -+ int protocol_y, -+ int protocol_width, -+ int protocol_height, -+ int *stage_x, -+ int *stage_y, -+ int *stage_width, -+ int *stage_height) -+{ -+ MetaWindowX11Class *klass = META_WINDOW_X11_GET_CLASS (window_x11); -+ -+ klass->protocol_to_stage (window_x11, -+ protocol_x, protocol_y, -+ protocol_width, protocol_height, -+ stage_x, stage_y, -+ stage_width, stage_height); -+} -+ -+static MtkRegion * -+region_protocol_to_stage (MtkRegion *region, -+ MetaWindowX11 *window_x11) -+{ -+ int n_rects, i; -+ MtkRectangle *rects; -+ MtkRegion *scaled_region; -+ -+ n_rects = mtk_region_num_rectangles (region); -+ MTK_RECTANGLE_CREATE_ARRAY_SCOPED (n_rects, rects); -+ for (i = 0; i < n_rects; i++) -+ { -+ rects[i] = mtk_region_get_rectangle (region, i); -+ meta_window_x11_protocol_to_stage (window_x11, -+ rects[i].x, rects[i].y, -+ rects[i].width, rects[i].height, -+ &rects[i].x, &rects[i].y, -+ &rects[i].width, &rects[i].height); -+ } -+ -+ scaled_region = mtk_region_create_rectangles (rects, n_rects); -+ -+ return scaled_region; -+} -+ - static void - send_icccm_message (MetaWindow *window, - Atom atom, -@@ -254,8 +361,13 @@ send_configure_notify (MetaWindow *window) - event.xconfigure.display = x11_display->xdisplay; - event.xconfigure.event = priv->xwindow; - event.xconfigure.window = priv->xwindow; -- event.xconfigure.x = priv->client_rect.x - priv->border_width; -- event.xconfigure.y = priv->client_rect.y - priv->border_width; -+ meta_window_x11_stage_to_protocol (window_x11, -+ priv->client_rect.x - priv->border_width, -+ priv->client_rect.y - priv->border_width, -+ 0, 0, -+ &event.xconfigure.x, -+ &event.xconfigure.y, -+ NULL, NULL); - if (window->frame) - { - if (window->withdrawn) -@@ -267,19 +379,42 @@ send_configure_notify (MetaWindow *window) - - meta_frame_calc_borders (window->frame, &borders); - -- event.xconfigure.x = window->frame->rect.x + borders.invisible.left; -- event.xconfigure.y = window->frame->rect.y + borders.invisible.top; -+ meta_window_x11_stage_to_protocol (window_x11, -+ window->frame->rect.x + borders.invisible.left, -+ window->frame->rect.y + borders.invisible.top, -+ 0, 0, -+ &event.xconfigure.x, -+ &event.xconfigure.y, -+ NULL, NULL); - } - else - { -+ int dx, dy; -+ - /* Need to be in root window coordinates */ -- event.xconfigure.x += window->frame->rect.x; -- event.xconfigure.y += window->frame->rect.y; -+ meta_window_x11_stage_to_protocol (window_x11, -+ window->frame->rect.x, -+ window->frame->rect.y, -+ 0, 0, -+ &dx, -+ &dy, -+ NULL, NULL); -+ event.xconfigure.x += dx; -+ event.xconfigure.y += dy; - } - } -- event.xconfigure.width = priv->client_rect.width; -- event.xconfigure.height = priv->client_rect.height; -- event.xconfigure.border_width = priv->border_width; /* requested not actual */ -+ meta_window_x11_stage_to_protocol (window_x11, -+ priv->client_rect.width, -+ priv->client_rect.height, -+ 0, 0, -+ &event.xconfigure.width, -+ &event.xconfigure.height, -+ NULL, NULL); -+ meta_window_x11_stage_to_protocol (window_x11, -+ priv->border_width, -+ 0, 0, 0, -+ &event.xconfigure.border_width, -+ NULL, NULL, NULL); - event.xconfigure.above = None; /* FIXME */ - event.xconfigure.override_redirect = False; - -@@ -1137,20 +1272,26 @@ static void - update_net_frame_extents (MetaWindow *window) - { - MetaX11Display *x11_display = window->display->x11_display; -- -+ int left, right, top, bottom; - unsigned long data[4]; - MetaFrameBorders borders; - Window xwindow = meta_window_x11_get_xwindow (window); - - meta_frame_calc_borders (window->frame, &borders); -- /* Left */ -- data[0] = borders.visible.left; -- /* Right */ -- data[1] = borders.visible.right; -- /* Top */ -- data[2] = borders.visible.top; -- /* Bottom */ -- data[3] = borders.visible.bottom; -+ meta_window_x11_stage_to_protocol (META_WINDOW_X11 (window), -+ borders.visible.left, -+ borders.visible.right, -+ borders.visible.top, -+ borders.visible.bottom, -+ &left, -+ &right, -+ &top, -+ &bottom); -+ -+ data[0] = left; -+ data[1] = right; -+ data[2] = top; -+ data[3] = bottom; - - meta_topic (META_DEBUG_GEOMETRY, - "Setting _NET_FRAME_EXTENTS on managed window 0x%lx " -@@ -1482,10 +1623,11 @@ meta_window_x11_move_resize_internal (MetaWindow *window, - configure_frame_first = size_dx + size_dy >= 0; - - values.border_width = 0; -- values.x = client_rect.x; -- values.y = client_rect.y; -- values.width = client_rect.width; -- values.height = client_rect.height; -+ meta_window_x11_stage_to_protocol (window_x11, -+ client_rect.x, client_rect.y, -+ client_rect.width, client_rect.height, -+ &values.x, &values.y, -+ &values.width, &values.height); - - mask = 0; - if (is_configure_request && priv->border_width != 0) -@@ -1591,6 +1733,10 @@ meta_window_x11_update_struts (MetaWindow *window) - strut_begin = struts[4+(i*2)]; - strut_end = struts[4+(i*2)+1]; - -+ meta_window_x11_protocol_to_stage (META_WINDOW_X11 (window), -+ strut_begin, strut_end, thickness, 0, -+ &strut_begin, &strut_end, &thickness, NULL); -+ - temp = g_new0 (MetaStrut, 1); - temp->side = 1 << i; /* See MetaSide def. Matches nicely, eh? */ - meta_display_get_size (window->display, -@@ -1655,6 +1801,10 @@ meta_window_x11_update_struts (MetaWindow *window) - if (thickness == 0) - continue; - -+ meta_window_x11_protocol_to_stage (META_WINDOW_X11 (window), -+ thickness, 0, 0, 0, -+ &thickness, NULL, NULL, NULL); -+ - temp = g_new0 (MetaStrut, 1); - temp->side = 1 << i; - meta_display_get_size (window->display, -@@ -2040,9 +2190,10 @@ static void - meta_window_x11_constructed (GObject *object) - { - MetaWindow *window = META_WINDOW (object); -- MetaWindowX11 *x11_window = META_WINDOW_X11 (object); -- MetaWindowX11Private *priv = meta_window_x11_get_instance_private (x11_window); -+ MetaWindowX11 *window_x11 = META_WINDOW_X11 (object); -+ MetaWindowX11Private *priv = meta_window_x11_get_instance_private (window_x11); - XWindowAttributes attrs = priv->attributes; -+ MtkRectangle rect; - - meta_verbose ("attrs->map_state = %d (%s)", - attrs.map_state, -@@ -2057,16 +2208,17 @@ meta_window_x11_constructed (GObject *object) - window->client_type = META_WINDOW_CLIENT_TYPE_X11; - window->override_redirect = attrs.override_redirect; - -- window->rect.x = attrs.x; -- window->rect.y = attrs.y; -- window->rect.width = attrs.width; -- window->rect.height = attrs.height; -+ meta_window_x11_protocol_to_stage (window_x11, -+ attrs.x, attrs.y, attrs.width, attrs.height, -+ &rect.x, &rect.y, &rect.width, &rect.height); -+ -+ window->rect = rect; - - /* size_hints are the "request" */ -- window->size_hints.x = attrs.x; -- window->size_hints.y = attrs.y; -- window->size_hints.width = attrs.width; -- window->size_hints.height = attrs.height; -+ window->size_hints.x = rect.x; -+ window->size_hints.y = rect.y; -+ window->size_hints.width = rect.width; -+ window->size_hints.height = rect.height; - - window->depth = attrs.depth; - priv->xvisual = attrs.visual; -@@ -2076,11 +2228,11 @@ meta_window_x11_constructed (GObject *object) - - window->decorated = TRUE; - window->hidden = FALSE; -- priv->border_width = attrs.border_width; - priv->xclient_leader = None; - -- priv->keys_grabbed = FALSE; -- priv->grab_on_frame = FALSE; -+ meta_window_x11_protocol_to_stage (window_x11, -+ attrs.border_width, 0, 0, 0, -+ &priv->border_width, NULL, NULL, NULL); - - g_signal_connect (window, "notify::decorated", - G_CALLBACK (meta_window_x11_update_input_region), -@@ -2192,6 +2344,8 @@ meta_window_x11_class_init (MetaWindowX11Class *klass) - klass->thaw_commits = meta_window_x11_impl_thaw_commits; - klass->always_update_shape = meta_window_x11_impl_always_update_shape; - klass->process_property_notify = meta_window_x11_impl_process_property_notify; -+ klass->stage_to_protocol = meta_window_x11_real_stage_to_protocol; -+ klass->protocol_to_stage = meta_window_x11_real_protocol_to_stage; - - obj_props[PROP_ATTRIBUTES] = - g_param_spec_pointer ("attributes", NULL, NULL, -@@ -2400,6 +2554,7 @@ meta_window_x11_update_input_region (MetaWindow *window) - g_autoptr (MtkRegion) region = NULL; - MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); - MetaWindowX11Private *priv = meta_window_x11_get_instance_private (window_x11); -+ MtkRectangle bounding_rect = { 0 }; - Window xwindow; - - if (window->decorated) -@@ -2411,10 +2566,14 @@ meta_window_x11_update_input_region (MetaWindow *window) - return; - } - xwindow = window->frame->xwindow; -+ bounding_rect.width = window->buffer_rect.width; -+ bounding_rect.height = window->buffer_rect.height; - } - else - { - xwindow = priv->xwindow; -+ bounding_rect.width = priv->client_rect.width; -+ bounding_rect.height = priv->client_rect.height; - } - - if (META_X11_DISPLAY_HAS_SHAPE (x11_display)) -@@ -2458,8 +2617,8 @@ meta_window_x11_update_input_region (MetaWindow *window) - else if (n_rects == 1 && - (rects[0].x == 0 && - rects[0].y == 0 && -- rects[0].width == window->buffer_rect.width && -- rects[0].height == window->buffer_rect.height)) -+ rects[0].width == bounding_rect.width && -+ rects[0].height == bounding_rect.height)) - { - /* This is the bounding region case. Keep the - * region as NULL. */ -@@ -2468,7 +2627,10 @@ meta_window_x11_update_input_region (MetaWindow *window) - else - { - /* Window has a custom shape. */ -- region = region_create_from_x_rectangles (rects, n_rects); -+ g_autoptr (MtkRegion) protocol_region = NULL; -+ -+ protocol_region = region_create_from_x_rectangles (rects, n_rects); -+ region = region_protocol_to_stage (protocol_region, window_x11); - } - - meta_XFree (rects); -@@ -2476,13 +2638,6 @@ meta_window_x11_update_input_region (MetaWindow *window) - - if (region != NULL) - { -- MtkRectangle bounding_rect; -- -- bounding_rect.x = 0; -- bounding_rect.y = 0; -- bounding_rect.width = window->buffer_rect.width; -- bounding_rect.height = window->buffer_rect.height; -- - /* The shape we get back from the client may have coordinates - * outside of the frame. The X SHAPE Extension requires that - * the overall shape the client provides never exceeds the -@@ -2551,7 +2706,10 @@ meta_window_x11_update_shape_region (MetaWindow *window) - - if (rects) - { -- region = region_create_from_x_rectangles (rects, n_rects); -+ g_autoptr (MtkRegion) protocol_region = NULL; -+ -+ protocol_region = region_create_from_x_rectangles (rects, n_rects); -+ region = region_protocol_to_stage (protocol_region, window_x11); - XFree (rects); - } - } -@@ -2829,6 +2987,7 @@ meta_window_x11_configure_request (MetaWindow *window, - { - MetaWindowX11 *window_x11 = META_WINDOW_X11 (window); - MetaWindowX11Private *priv = meta_window_x11_get_instance_private (window_x11); -+ int new_x, new_y, new_width, new_height; - - /* Note that x, y is the corner of the window border, - * and width, height is the size of the window inside -@@ -2837,15 +2996,25 @@ meta_window_x11_configure_request (MetaWindow *window, - * requested border here. - */ - if (event->xconfigurerequest.value_mask & CWBorderWidth) -- priv->border_width = event->xconfigurerequest.border_width; -+ { -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->xconfigurerequest.border_width, 0, 0, 0, -+ &priv->border_width, NULL, NULL, NULL); -+ } - -- meta_window_move_resize_request(window, -- event->xconfigurerequest.value_mask, -- window->size_hints.win_gravity, -- event->xconfigurerequest.x, -- event->xconfigurerequest.y, -- event->xconfigurerequest.width, -- event->xconfigurerequest.height); -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->xconfigurerequest.x, event->xconfigurerequest.y, -+ event->xconfigurerequest.width, event->xconfigurerequest.height, -+ &new_x, &new_y, -+ &new_width, &new_height); -+ -+ meta_window_move_resize_request (window, -+ event->xconfigurerequest.value_mask, -+ window->size_hints.win_gravity, -+ new_x, -+ new_y, -+ new_width, -+ new_height); - - /* Handle stacking. We only handle raises/lowers, mostly because - * stack.c really can't deal with anything else. I guess we'll fix -@@ -3340,8 +3509,13 @@ meta_window_x11_client_message (MetaWindow *window, - guint32 timestamp; - MetaWindowDrag *window_drag; - -- x_root = event->xclient.data.l[0]; -- y_root = event->xclient.data.l[1]; -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->xclient.data.l[0], -+ event->xclient.data.l[1], -+ 0, 0, -+ &x_root, -+ &y_root, -+ NULL, NULL); - action = event->xclient.data.l[2]; - button = event->xclient.data.l[3]; - -@@ -3505,6 +3679,7 @@ meta_window_x11_client_message (MetaWindow *window, - { - MetaGravity gravity; - guint value_mask; -+ int x, y, width, height; - - gravity = (MetaGravity) (event->xclient.data.l[0] & 0xff); - value_mask = (event->xclient.data.l[0] & 0xf00) >> 8; -@@ -3513,13 +3688,20 @@ meta_window_x11_client_message (MetaWindow *window, - if (gravity == 0) - gravity = window->size_hints.win_gravity; - -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->xclient.data.l[1], -+ event->xclient.data.l[2], -+ event->xclient.data.l[3], -+ event->xclient.data.l[4], -+ &x, &y, &width, &height); -+ - meta_window_move_resize_request(window, - value_mask, - gravity, -- event->xclient.data.l[1], /* x */ -- event->xclient.data.l[2], /* y */ -- event->xclient.data.l[3], /* width */ -- event->xclient.data.l[4]); /* height */ -+ x, -+ y, -+ width, -+ height); - } - else if (event->xclient.message_type == - x11_display->atom__NET_ACTIVE_WINDOW && -@@ -3576,11 +3758,15 @@ meta_window_x11_client_message (MetaWindow *window, - else if (event->xclient.message_type == - x11_display->atom__GTK_SHOW_WINDOW_MENU) - { -- gulong x, y; -+ int x, y; - - /* l[0] is device_id, which we don't use */ -- x = event->xclient.data.l[1]; -- y = event->xclient.data.l[2]; -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->xclient.data.l[1], -+ event->xclient.data.l[2], -+ 0, 0, -+ &x, &y, -+ NULL, NULL); - - meta_window_show_menu (window, META_WINDOW_MENU_WM, x, y); - } -@@ -4102,10 +4288,11 @@ meta_window_x11_configure_notify (MetaWindow *window, - g_assert (window->override_redirect); - g_assert (window->frame == NULL); - -- window->rect.x = event->x; -- window->rect.y = event->y; -- window->rect.width = event->width; -- window->rect.height = event->height; -+ meta_window_x11_protocol_to_stage (window_x11, -+ event->x, event->y, -+ event->width, event->height, -+ &window->rect.x, &window->rect.y, -+ &window->rect.width, &window->rect.height); - - priv->client_rect = window->rect; - window->buffer_rect = window->rect; -diff --git a/src/x11/window-x11.h b/src/x11/window-x11.h -index 205eaaa63..fa3fbea6a 100644 ---- a/src/x11/window-x11.h -+++ b/src/x11/window-x11.h -@@ -45,6 +45,24 @@ struct _MetaWindowX11Class - gboolean (*always_update_shape) (MetaWindow *window); - void (*process_property_notify) (MetaWindow *window, - XPropertyEvent *event); -+ void (*stage_to_protocol) (MetaWindowX11 *window_x11, -+ int stage_x, -+ int stage_y, -+ int stage_width, -+ int stage_height, -+ int *protocol_x, -+ int *protocol_y, -+ int *protocol_width, -+ int *protocol_height); -+ void (*protocol_to_stage) (MetaWindowX11 *window_x11, -+ int protocol_x, -+ int protocol_y, -+ int protocol_width, -+ int protocol_height, -+ int *stage_x, -+ int *stage_y, -+ int *stage_width, -+ int *stage_height); - }; - - MetaWindow * meta_window_x11_new (MetaDisplay *display, -@@ -112,3 +130,23 @@ gboolean meta_window_x11_has_alpha_channel (MetaWindow *window); - - META_EXPORT - Window meta_window_x11_get_xwindow (MetaWindow *window); -+ -+void meta_window_x11_stage_to_protocol (MetaWindowX11 *window_x11, -+ int stage_x, -+ int stage_y, -+ int stage_width, -+ int stage_heigth, -+ int *protocol_x, -+ int *protocol_y, -+ int *protocol_width, -+ int *protocol_height); -+ -+void meta_window_x11_protocol_to_stage (MetaWindowX11 *window_x11, -+ int protocol_x, -+ int protocol_y, -+ int protocol_width, -+ int protocol_height, -+ int *stage_x, -+ int *stage_y, -+ int *stage_width, -+ int *stage_heigth); diff --git a/spec_files/mutter/mutter-42.alpha-disable-tegra.patch b/spec_files/mutter/mutter-42.alpha-disable-tegra.patch deleted file mode 100644 index 3225c8f5..00000000 --- a/spec_files/mutter/mutter-42.alpha-disable-tegra.patch +++ /dev/null @@ -1,25 +0,0 @@ -From a5c67e0debaa89f7a73452560664cdc5c581ab95 Mon Sep 17 00:00:00 2001 -From: Adam Williamson -Date: Tue, 9 Mar 2021 17:21:59 -0800 -Subject: [PATCH] Test: deny atomic KMS for "tegra" (RHBZ #1936991) - -Signed-off-by: Adam Williamson ---- - src/backends/native/meta-kms-impl-device-atomic.c | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/backends/native/meta-kms-impl-device-atomic.c b/src/backends/native/meta-kms-impl-device-atomic.c -index 35837f7429..ffff7b8e23 100644 ---- a/src/backends/native/meta-kms-impl-device-atomic.c -+++ b/src/backends/native/meta-kms-impl-device-atomic.c -@@ -1352,6 +1352,7 @@ is_atomic_allowed (const char *driver_name) - { - const char *atomic_driver_deny_list[] = { - "xlnx", -+ "tegra", - NULL, - }; - --- -2.43.0 - diff --git a/spec_files/mutter/mutter.spec b/spec_files/mutter/mutter.spec deleted file mode 100644 index 87d6e252..00000000 --- a/spec_files/mutter/mutter.spec +++ /dev/null @@ -1,211 +0,0 @@ -%global glib_version 2.75.1 -%global gtk3_version 3.19.8 -%global gtk4_version 4.0.0 -%global gsettings_desktop_schemas_version 40~alpha -%global json_glib_version 0.12.0 -%global libinput_version 1.19.0 -%global pipewire_version 0.3.33 -%global lcms2_version 2.6 -%global colord_version 1.4.5 -%global libei_version 1.0.0 -%global mutter_api_version 14 - -%global gnome_major_version 46 -%global gnome_version %{gnome_major_version}.1 -%global tarball_version %%(echo %{gnome_version} | tr '~' '.') -%global _default_patch_fuzz 2 - -Name: mutter -Version: %{gnome_version}.ublue.{{{ git_dir_version }}} -Release: 2%{?dist} -Summary: Window and compositing manager based on Clutter - -License: GPLv2+ -URL: http://www.gnome.org -Source0: https://download.gnome.org/sources/%{name}/%{gnome_major_version}/%{name}-%{tarball_version}.tar.xz - -# Work-around for OpenJDK's compliance test -Patch0: 0001-window-actor-Special-case-shaped-Java-windows.patch - -# https://bugzilla.redhat.com/show_bug.cgi?id=1936991 -Patch1: mutter-42.alpha-disable-tegra.patch - -# https://pagure.io/fedora-workstation/issue/79 -Patch2: 0001-place-Always-center-initial-setup-fedora-welcome.patch - -# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441 -Patch3: 1441.patch - -# https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3567 -Patch4: 3720+3567.patch - -BuildRequires: pkgconfig(gobject-introspection-1.0) >= 1.41.0 -BuildRequires: pkgconfig(sm) -BuildRequires: pkgconfig(libwacom) -BuildRequires: pkgconfig(x11) -BuildRequires: pkgconfig(xdamage) -BuildRequires: pkgconfig(xext) -BuildRequires: pkgconfig(xfixes) -BuildRequires: pkgconfig(xi) -BuildRequires: pkgconfig(xrandr) -BuildRequires: pkgconfig(xrender) -BuildRequires: pkgconfig(xcursor) -BuildRequires: pkgconfig(xcomposite) -BuildRequires: pkgconfig(x11-xcb) -BuildRequires: pkgconfig(xkbcommon) -BuildRequires: pkgconfig(xkbcommon-x11) -BuildRequires: pkgconfig(xkbfile) -BuildRequires: pkgconfig(xtst) -BuildRequires: mesa-libEGL-devel -BuildRequires: mesa-libGLES-devel -BuildRequires: mesa-libGL-devel -BuildRequires: mesa-libgbm-devel -BuildRequires: pkgconfig(glesv2) -BuildRequires: pkgconfig(graphene-gobject-1.0) -BuildRequires: pam-devel -BuildRequires: pkgconfig(libdisplay-info) -BuildRequires: pkgconfig(libpipewire-0.3) >= %{pipewire_version} -BuildRequires: pkgconfig(sysprof-capture-4) -BuildRequires: sysprof-devel -BuildRequires: pkgconfig(libsystemd) -BuildRequires: xorg-x11-server-Xorg -BuildRequires: xorg-x11-server-Xvfb -BuildRequires: pkgconfig(xkeyboard-config) -BuildRequires: desktop-file-utils -# Bootstrap requirements -BuildRequires: gettext-devel git-core -BuildRequires: pkgconfig(libcanberra) -BuildRequires: pkgconfig(gsettings-desktop-schemas) >= %{gsettings_desktop_schemas_version} -BuildRequires: pkgconfig(gnome-settings-daemon) -BuildRequires: meson -BuildRequires: pkgconfig(gbm) -BuildRequires: pkgconfig(gnome-desktop-4) -BuildRequires: pkgconfig(gudev-1.0) -BuildRequires: pkgconfig(libdrm) -BuildRequires: pkgconfig(libstartup-notification-1.0) -BuildRequires: pkgconfig(wayland-eglstream) -BuildRequires: pkgconfig(wayland-protocols) -BuildRequires: pkgconfig(wayland-server) -BuildRequires: pkgconfig(lcms2) >= %{lcms2_version} -BuildRequires: pkgconfig(colord) >= %{colord_version} -BuildRequires: pkgconfig(libei-1.0) >= %{libei_version} -BuildRequires: pkgconfig(libeis-1.0) >= %{libei_version} - -BuildRequires: pkgconfig(json-glib-1.0) >= %{json_glib_version} -BuildRequires: pkgconfig(libinput) >= %{libinput_version} -BuildRequires: pkgconfig(xwayland) - -BuildRequires: python3-dbusmock - -Requires: control-center-filesystem -Requires: gsettings-desktop-schemas%{?_isa} >= %{gsettings_desktop_schemas_version} -Requires: gnome-settings-daemon -Requires: gtk4%{?_isa} >= %{gtk4_version} -Requires: json-glib%{?_isa} >= %{json_glib_version} -Requires: libinput%{?_isa} >= %{libinput_version} -Requires: pipewire%{_isa} >= %{pipewire_version} -Requires: startup-notification -Requires: dbus - -# Need common -Requires: %{name}-common = %{version}-%{release} - -Recommends: mesa-dri-drivers%{?_isa} - -Provides: firstboot(windowmanager) = mutter - -# Cogl and Clutter were forked at these versions, but have diverged -# significantly since then. -Provides: bundled(cogl) = 1.22.0 -Provides: bundled(clutter) = 1.26.0 - -Provides: mutter = %{gnome_version}-%{release} - -Conflicts: mutter < 45~beta.1-2 - -# Make sure dnf updates gnome-shell together with this package; otherwise we -# might end up with broken gnome-shell installations due to mutter ABI changes. -Conflicts: gnome-shell < 45~rc - -%description -Mutter is a window and compositing manager that displays and manages -your desktop via OpenGL. Mutter combines a sophisticated display engine -using the Clutter toolkit with solid window-management logic inherited -from the Metacity window manager. - -While Mutter can be used stand-alone, it is primarily intended to be -used as the display core of a larger system such as GNOME Shell. For -this reason, Mutter is very extensible via plugins, which are used both -to add fancy visual effects and to rework the window management -behaviors to meet the needs of the environment. - -%package common -Summary: Common files used by %{name} and forks of %{name} -BuildArch: noarch -Conflicts: mutter < 45~beta.1-2 -Provides: mutter-common = %{gnome_version}-%{release} - -%description common -Common files used by Mutter and soft forks of Mutter - -%package devel -Summary: Development package for %{name} -Requires: %{name}%{?_isa} = %{version}-%{release} -# for EGL/eglmesaext.h that's included from public cogl-egl-defines.h header -Requires: mesa-libEGL-devel - -%description devel -Header files and libraries for developing Mutter plugins. Also includes -utilities for testing Metacity/Mutter themes. - -%package tests -Summary: Tests for the %{name} package -Requires: %{name}-devel%{?_isa} = %{version}-%{release} -Requires: %{name}%{?_isa} = %{version}-%{release} -Requires: gtk3%{?_isa} >= %{gtk3_version} - -%description tests -The %{name}-tests package contains tests that can be used to verify -the functionality of the installed %{name} package. - -%prep -%autosetup -S git -n %{name}-%{tarball_version} - -%build -%meson -Degl_device=true -Dwayland_eglstream=true -%meson_build - -%install -%meson_install - -%find_lang %{name} - -%files -f %{name}.lang -%license COPYING -%doc NEWS -%{_bindir}/mutter -%{_libdir}/lib*.so.* -%{_libdir}/mutter-%{mutter_api_version}/ -%{_libexecdir}/mutter-restart-helper -%{_libexecdir}/mutter-x11-frames -%{_mandir}/man1/mutter.1* - -%files common -%{_datadir}/GConf/gsettings/mutter-schemas.convert -%{_datadir}/glib-2.0/schemas/org.gnome.mutter.gschema.xml -%{_datadir}/glib-2.0/schemas/org.gnome.mutter.wayland.gschema.xml -%{_datadir}/gnome-control-center/keybindings/50-mutter-*.xml -%{_udevrulesdir}/61-mutter.rules - -%files devel -%{_includedir}/* -%{_libdir}/lib*.so -%{_libdir}/pkgconfig/* - -%files tests -%{_libexecdir}/installed-tests/mutter-%{mutter_api_version} -%{_datadir}/installed-tests/mutter-%{mutter_api_version} -%{_datadir}/mutter-%{mutter_api_version}/tests - -%changelog -%autochangelog diff --git a/spec_files/upower/upower.spec b/spec_files/upower/upower.spec index 312e6fdb..fe14b6c8 100644 --- a/spec_files/upower/upower.spec +++ b/spec_files/upower/upower.spec @@ -1,6 +1,6 @@ Summary: Power Management Service Name: upower -Version: 1.90.2 +Version: 1.90.4 Release: %autorelease.bazzite.{{{ git_dir_version }}} License: GPL-2.0-or-later URL: http://upower.freedesktop.org/ diff --git a/spec_files/vkroots/vkroots.spec b/spec_files/vkroots/vkroots.spec deleted file mode 100644 index dbce36d3..00000000 --- a/spec_files/vkroots/vkroots.spec +++ /dev/null @@ -1,57 +0,0 @@ -%global debug_package %{nil} -%global commit 2ceb105700e23842bec0e56a0a2cbd5885dfcf4b -%global shortcommit %(c=%{commit}; echo ${c:0:7}) -%global git_date 20231118 - -Name: vkroots -Version: 0^%{git_date}git%{shortcommit} -Release: 1%{?dist} -Summary: A stupid simple method of making Vulkan layers, at home -License: LGPL-2.1-or-later AND (Apache-2.0 or MIT) -URL: https://github.com/Joshua-Ashton/vkroots -Source: %{url}/archive/%{commit}/%{name}-%{shortcommit}.tar.gz - -BuildRequires: meson -BuildRequires: gcc -BuildRequires: gcc-c++ -BuildRequires: vulkan-headers - - -%description -vkroots is a framework for writing Vulkan layers that -takes all the complexity/hastle away from you. It's so simple. - - -%package devel -Summary: A stupid simple method of making Vulkan layers, at home - -%description devel -vkroots is a framework for writing Vulkan layers that -takes all the complexity/hastle away from you. It's so simple. - -%prep -%autosetup -p1 -n %{name}-%{commit} - - -%build -%meson -%meson_build - - -%install -%meson_install - - -%files devel -%license LICENSE -%doc README.md -%{_includedir}/%{name}.h -%{_libdir}/pkgconfig/%{name}.pc - - -%changelog -* Tue Jul 25 2023 Frantisek Zatloukal - 0^20230313gite554d4c-1 -- Rebase to a later snapshot - -* Fri Mar 03 2023 Onuralp SEZER - 0^20230103git2675710-1 -- Initial package vkroots diff --git a/spec_files/wireplumber/valve.patch b/spec_files/wireplumber/valve.patch deleted file mode 100644 index f0e97df8..00000000 --- a/spec_files/wireplumber/valve.patch +++ /dev/null @@ -1,2813 +0,0 @@ -From 9911c8532eb9072d93eb39b02aa94699f0a735a1 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Tue, 21 Mar 2023 09:18:30 -0400 -Subject: [PATCH 01/11] m-default-nodes: remove echo-cancel configuration - -This will be possible to do with the new module-filters-api. ---- - modules/module-default-nodes.c | 75 -------------------- - src/config/main.lua.d/40-device-defaults.lua | 9 --- - 2 files changed, 84 deletions(-) - -diff --git a/modules/module-default-nodes.c b/modules/module-default-nodes.c -index aaf29389..3bb93f00 100644 ---- a/modules/module-default-nodes.c -+++ b/modules/module-default-nodes.c -@@ -16,18 +16,12 @@ - #define NAME "default-nodes" - #define DEFAULT_SAVE_INTERVAL_MS 1000 - #define DEFAULT_USE_PERSISTENT_STORAGE TRUE --#define DEFAULT_AUTO_ECHO_CANCEL TRUE --#define DEFAULT_ECHO_CANCEL_SINK_NAME "echo-cancel-sink" --#define DEFAULT_ECHO_CANCEL_SOURCE_NAME "echo-cancel-source" - #define N_PREV_CONFIGS 16 - - enum { - PROP_0, - PROP_SAVE_INTERVAL_MS, - PROP_USE_PERSISTENT_STORAGE, -- PROP_AUTO_ECHO_CANCEL, -- PROP_ECHO_CANCEL_SINK_NAME, -- PROP_ECHO_CANCEL_SOURCE_NAME, - }; - - typedef struct _WpDefaultNode WpDefaultNode; -@@ -51,8 +45,6 @@ struct _WpDefaultNodes - /* properties */ - guint save_interval_ms; - gboolean use_persistent_storage; -- gboolean auto_echo_cancel; -- gchar *echo_cancel_names[2]; - }; - - G_DECLARE_FINAL_TYPE (WpDefaultNodes, wp_default_nodes, -@@ -243,21 +235,6 @@ node_has_available_routes (WpDefaultNodes * self, WpNode *node) - return FALSE; - } - --static gboolean --is_echo_cancel_node (WpDefaultNodes * self, WpNode *node, WpDirection direction) --{ -- const gchar *name = wp_pipewire_object_get_property ( -- WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_NAME); -- const gchar *virtual_str = wp_pipewire_object_get_property ( -- WP_PIPEWIRE_OBJECT (node), PW_KEY_NODE_VIRTUAL); -- gboolean virtual = virtual_str && pw_properties_parse_bool (virtual_str); -- -- if (!name || !virtual) -- return FALSE; -- -- return g_strcmp0 (name, self->echo_cancel_names[direction]) == 0; --} -- - static WpNode * - find_best_media_class_node (WpDefaultNodes * self, const gchar *media_class, - const WpDefaultNode *def, WpDirection direction, gint *priority) -@@ -291,9 +268,6 @@ find_best_media_class_node (WpDefaultNodes * self, const gchar *media_class, - if (!node_has_available_routes (self, node)) - continue; - -- if (self->auto_echo_cancel && is_echo_cancel_node (self, node, direction)) -- prio += 10000; -- - if (name && def->config_value && g_strcmp0 (name, def->config_value) == 0) { - prio += 20000 * (N_PREV_CONFIGS + 1); - } else if (name) { -@@ -597,41 +571,18 @@ wp_default_nodes_set_property (GObject * object, guint property_id, - case PROP_USE_PERSISTENT_STORAGE: - self->use_persistent_storage = g_value_get_boolean (value); - break; -- case PROP_AUTO_ECHO_CANCEL: -- self->auto_echo_cancel = g_value_get_boolean (value); -- break; -- case PROP_ECHO_CANCEL_SINK_NAME: -- g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free); -- self->echo_cancel_names[WP_DIRECTION_INPUT] = g_value_dup_string (value); -- break; -- case PROP_ECHO_CANCEL_SOURCE_NAME: -- g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free); -- self->echo_cancel_names[WP_DIRECTION_OUTPUT] = g_value_dup_string (value); -- break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - break; - } - } - --static void --wp_default_nodes_finalize (GObject * object) --{ -- WpDefaultNodes * self = WP_DEFAULT_NODES (object); -- -- g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_INPUT], g_free); -- g_clear_pointer (&self->echo_cancel_names[WP_DIRECTION_OUTPUT], g_free); -- -- G_OBJECT_CLASS (wp_default_nodes_parent_class)->finalize (object); --} -- - static void - wp_default_nodes_class_init (WpDefaultNodesClass * klass) - { - GObjectClass *object_class = (GObjectClass *) klass; - WpPluginClass *plugin_class = (WpPluginClass *) klass; - -- object_class->finalize = wp_default_nodes_finalize; - object_class->set_property = wp_default_nodes_set_property; - - plugin_class->enable = wp_default_nodes_enable; -@@ -646,21 +597,6 @@ wp_default_nodes_class_init (WpDefaultNodesClass * klass) - g_param_spec_boolean ("use-persistent-storage", "use-persistent-storage", - "use-persistent-storage", DEFAULT_USE_PERSISTENT_STORAGE, - G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); -- -- g_object_class_install_property (object_class, PROP_AUTO_ECHO_CANCEL, -- g_param_spec_boolean ("auto-echo-cancel", "auto-echo-cancel", -- "auto-echo-cancel", DEFAULT_AUTO_ECHO_CANCEL, -- G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); -- -- g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SINK_NAME, -- g_param_spec_string ("echo-cancel-sink-name", "echo-cancel-sink-name", -- "echo-cancel-sink-name", DEFAULT_ECHO_CANCEL_SINK_NAME, -- G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); -- -- g_object_class_install_property (object_class, PROP_ECHO_CANCEL_SOURCE_NAME, -- g_param_spec_string ("echo-cancel-source-name", "echo-cancel-source-name", -- "echo-cancel-source-name", DEFAULT_ECHO_CANCEL_SOURCE_NAME, -- G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); - } - - WP_PLUGIN_EXPORT gboolean -@@ -668,19 +604,11 @@ wireplumber__module_init (WpCore * core, GVariant * args, GError ** error) - { - guint save_interval_ms = DEFAULT_SAVE_INTERVAL_MS; - gboolean use_persistent_storage = DEFAULT_USE_PERSISTENT_STORAGE; -- gboolean auto_echo_cancel = DEFAULT_AUTO_ECHO_CANCEL; -- const gchar *echo_cancel_sink_name = DEFAULT_ECHO_CANCEL_SINK_NAME; -- const gchar *echo_cancel_source_name = DEFAULT_ECHO_CANCEL_SOURCE_NAME; - - if (args) { - g_variant_lookup (args, "save-interval-ms", "u", &save_interval_ms); - g_variant_lookup (args, "use-persistent-storage", "b", - &use_persistent_storage); -- g_variant_lookup (args, "auto-echo-cancel", "&s", &auto_echo_cancel); -- g_variant_lookup (args, "echo-cancel-sink-name", "&s", -- &echo_cancel_sink_name); -- g_variant_lookup (args, "echo-cancel-source-name", "&s", -- &echo_cancel_source_name); - } - - wp_plugin_register (g_object_new (wp_default_nodes_get_type (), -@@ -688,9 +616,6 @@ wireplumber__module_init (WpCore * core, GVariant * args, GError ** error) - "core", core, - "save-interval-ms", save_interval_ms, - "use-persistent-storage", use_persistent_storage, -- "auto-echo-cancel", auto_echo_cancel, -- "echo-cancel-sink-name", echo_cancel_sink_name, -- "echo-cancel-source-name", echo_cancel_source_name, - NULL)); - return TRUE; - } -diff --git a/src/config/main.lua.d/40-device-defaults.lua b/src/config/main.lua.d/40-device-defaults.lua -index 19202914..91c4e189 100644 ---- a/src/config/main.lua.d/40-device-defaults.lua -+++ b/src/config/main.lua.d/40-device-defaults.lua -@@ -10,15 +10,6 @@ device_defaults.properties = { - -- the default volumes to apply to ACP device nodes, in the linear scale - --["default-volume"] = 0.064, - --["default-input-volume"] = 1.0, -- -- -- Whether to auto-switch to echo cancel sink and source nodes or not -- ["auto-echo-cancel"] = true, -- -- -- Sets the default echo-cancel-sink node name to automatically switch to -- ["echo-cancel-sink-name"] = "echo-cancel-sink", -- -- -- Sets the default echo-cancel-source node name to automatically switch to -- ["echo-cancel-source-name"] = "echo-cancel-source", - } - - -- Sets persistent device profiles that should never change when wireplumber is --- -2.42.0 - - -From a9b6f3bcb4c525c1e257873875feb6223fa6b660 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Fri, 17 Mar 2023 11:06:11 -0400 -Subject: [PATCH 02/11] modules: add new module-filters-api to enable smart - filter policy - -This module provides an API to link filter nodes using the logic configured in -'policy.lua.d/30-filters-config.lua'. This configuration file allows grouping -filters together (sorted by priority) to use a common target node. A node is -considered a filter node if it has the node.link-group property. - -This is disabled by default. You can enable smart filters policy in the -'10-default-policy.lya' configuration file. ---- - modules/meson.build | 11 + - modules/module-filters-api.c | 905 ++++++++++++++++++ - modules/module-lua-scripting/api/json.c | 2 +- - src/config/policy.lua.d/10-default-policy.lua | 9 + - src/config/policy.lua.d/30-filters-config.lua | 73 ++ - src/scripts/filters-metadata.lua | 39 + - src/scripts/policy-node.lua | 120 ++- - 7 files changed, 1156 insertions(+), 3 deletions(-) - create mode 100644 modules/module-filters-api.c - create mode 100644 src/config/policy.lua.d/30-filters-config.lua - create mode 100644 src/scripts/filters-metadata.lua - -diff --git a/modules/meson.build b/modules/meson.build -index 4930bfae..4a337017 100644 ---- a/modules/meson.build -+++ b/modules/meson.build -@@ -159,6 +159,17 @@ shared_library( - dependencies : [wp_dep, pipewire_dep], - ) - -+shared_library( -+ 'wireplumber-module-filters-api', -+ [ -+ 'module-filters-api.c', -+ ], -+ c_args : [common_c_args, '-DG_LOG_DOMAIN="m-filters-api"'], -+ install : true, -+ install_dir : wireplumber_module_dir, -+ dependencies : [wp_dep, pipewire_dep], -+) -+ - if libsystemd_dep.found() or libelogind_dep.found() - shared_library( - 'wireplumber-module-logind', -diff --git a/modules/module-filters-api.c b/modules/module-filters-api.c -new file mode 100644 -index 00000000..32c67c85 ---- /dev/null -+++ b/modules/module-filters-api.c -@@ -0,0 +1,905 @@ -+/* WirePlumber -+ * -+ * Copyright © 2023 Collabora Ltd. -+ * @author Julian Bouzas -+ * -+ * SPDX-License-Identifier: MIT -+ */ -+ -+#include -+ -+#include -+ -+struct _WpFiltersApi -+{ -+ WpPlugin parent; -+ -+ WpObjectManager *metadata_om; -+ WpObjectManager *stream_nodes_om; -+ WpObjectManager *nodes_om; -+ WpObjectManager *filter_nodes_om; -+ guint n_playback_stream_nodes; -+ guint n_capture_stream_nodes; -+ GList *filters[2]; -+ GHashTable *targets; -+}; -+ -+enum { -+ ACTION_IS_FILTER_ENABLED, -+ ACTION_GET_FILTER_TARGET, -+ ACTION_GET_FILTER_FROM_TARGET, -+ ACTION_GET_DEFAULT_FILTER, -+ SIGNAL_CHANGED, -+ N_SIGNALS -+}; -+ -+static guint signals[N_SIGNALS] = {0}; -+ -+G_DECLARE_FINAL_TYPE (WpFiltersApi, wp_filters_api, WP, FILTERS_API, WpPlugin) -+G_DEFINE_TYPE (WpFiltersApi, wp_filters_api, WP_TYPE_PLUGIN) -+ -+struct _Filter { -+ gchar *link_group; -+ WpDirection direction; -+ WpNode *node; -+ WpNode *stream; -+ gchar *target; -+ gboolean enabled; -+ gint priority; -+}; -+typedef struct _Filter Filter; -+ -+static guint -+get_filter_priority (const gchar *link_group) -+{ -+ if (strstr (link_group, "loopback")) -+ return 300; -+ if (strstr (link_group, "filter-chain")) -+ return 200; -+ /* By default echo-cancel is the lowest priority to properly cancel audio */ -+ if (strstr (link_group, "echo-cancel")) -+ return 0; -+ return 100; -+} -+ -+static Filter * -+filter_new (const gchar *link_group, WpDirection dir, gboolean is_stream, -+ WpNode *node) -+{ -+ Filter *f = g_malloc0 (sizeof (Filter)); -+ f->link_group = g_strdup (link_group); -+ f->direction = dir; -+ f->node = is_stream ? NULL : g_object_ref (node); -+ f->stream = is_stream ? g_object_ref (node) : NULL; -+ f->target = NULL; -+ f->enabled = TRUE; -+ f->priority = get_filter_priority (link_group); -+ return f; -+} -+ -+static void -+filter_free (Filter *f) -+{ -+ g_clear_pointer (&f->link_group, g_free); -+ g_clear_pointer (&f->target, g_free); -+ g_clear_object (&f->node); -+ g_clear_object (&f->stream); -+ g_free (f); -+} -+ -+static gint -+filter_equal_func (const Filter *f, const gchar *link_group) -+{ -+ return g_str_equal (f->link_group, link_group) ? 0 : 1; -+} -+ -+static gint -+filter_compare_func (const Filter *a, const Filter *b) -+{ -+ gint diff = a->priority - b->priority; -+ if (diff != 0) -+ return diff; -+ return g_strcmp0 (a->link_group, b->link_group); -+} -+ -+static void -+wp_filters_api_init (WpFiltersApi * self) -+{ -+} -+ -+static gboolean -+wp_filters_api_is_filter_enabled (WpFiltersApi * self, const gchar *direction, -+ const gchar *link_group) -+{ -+ WpDirection dir = WP_DIRECTION_INPUT; -+ GList *filters; -+ Filter *found = NULL; -+ -+ g_return_val_if_fail (direction, FALSE); -+ g_return_val_if_fail (link_group, FALSE); -+ -+ /* Get the filters for the given direction */ -+ if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -+ dir = WP_DIRECTION_OUTPUT; -+ filters = self->filters[dir]; -+ -+ /* Find the filter in the filters list */ -+ filters = g_list_find_custom (filters, link_group, -+ (GCompareFunc) filter_equal_func); -+ if (!filters) -+ return FALSE; -+ -+ found = filters->data; -+ return found->enabled; -+} -+ -+static gint -+wp_filters_api_get_filter_target (WpFiltersApi * self, const gchar *direction, -+ const gchar *link_group) -+{ -+ WpDirection dir = WP_DIRECTION_INPUT; -+ GList *filters; -+ Filter *found; -+ -+ g_return_val_if_fail (direction, -1); -+ g_return_val_if_fail (link_group, -1); -+ -+ /* Get the filters for the given direction */ -+ if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -+ dir = WP_DIRECTION_OUTPUT; -+ filters = self->filters[dir]; -+ -+ /* Find the filter in the filters list */ -+ filters = g_list_find_custom (filters, link_group, -+ (GCompareFunc) filter_equal_func); -+ if (!filters) -+ return -1; -+ found = filters->data; -+ if (!found->enabled) -+ return -1; -+ -+ /* Return the previous filter with matching target that is enabled */ -+ while ((filters = g_list_previous (filters))) { -+ Filter *prev = (Filter *) filters->data; -+ if ((prev->target == found->target || -+ (prev->target && found->target && -+ g_str_equal (prev->target, found->target))) && -+ prev->enabled) -+ return wp_proxy_get_bound_id (WP_PROXY (prev->node)); -+ } -+ -+ /* Find the target */ -+ if (found->target) { -+ WpNode *node = g_hash_table_lookup (self->targets, found->target); -+ if (node) -+ return wp_proxy_get_bound_id (WP_PROXY (node)); -+ } -+ -+ return -1; -+} -+ -+static gint -+wp_filters_api_get_filter_from_target (WpFiltersApi * self, -+ const gchar *direction, gint target_id) -+{ -+ WpDirection dir = WP_DIRECTION_INPUT; -+ GList *filters; -+ gboolean found = FALSE; -+ const gchar *target = NULL; -+ gint res = target_id; -+ -+ g_return_val_if_fail (direction, res); -+ -+ /* Get the filters for the given direction */ -+ if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -+ dir = WP_DIRECTION_OUTPUT; -+ filters = self->filters[dir]; -+ -+ /* Find the first target matching target_id */ -+ while (filters) { -+ Filter *f = (Filter *) filters->data; -+ gint f_target_id = wp_filters_api_get_filter_target (self, direction, -+ f->link_group); -+ if (f_target_id == target_id && f->enabled) { -+ target = f->target; -+ found = TRUE; -+ break; -+ } -+ -+ /* Advance */ -+ filters = g_list_next (filters); -+ } -+ -+ /* Just return if target was not found */ -+ if (!found) -+ return res; -+ -+ /* Get the last filter node ID of the target found */ -+ filters = self->filters[dir]; -+ while (filters) { -+ Filter *f = (Filter *) filters->data; -+ if ((f->target == target || -+ (f->target && target && g_str_equal (f->target, target))) && -+ f->enabled) -+ res = wp_proxy_get_bound_id (WP_PROXY (f->node)); -+ -+ /* Advance */ -+ filters = g_list_next (filters); -+ } -+ -+ return res; -+} -+ -+static gint -+wp_filters_api_get_default_filter (WpFiltersApi * self, const gchar *direction) -+{ -+ WpDirection dir = WP_DIRECTION_INPUT; -+ GList *filters; -+ -+ g_return_val_if_fail (direction, -1); -+ -+ /* Get the filters for the given direction */ -+ if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -+ dir = WP_DIRECTION_OUTPUT; -+ filters = self->filters[dir]; -+ -+ /* The default filter is the highest priority filter without target, this is -+ * the first filer that is enabled because the list is sorted by priority */ -+ while (filters) { -+ Filter *f = (Filter *) filters->data; -+ if (f->enabled && !f->target) -+ return wp_proxy_get_bound_id (WP_PROXY (f->node)); -+ -+ /* Advance */ -+ filters = g_list_next (filters); -+ } -+ -+ return -1; -+} -+ -+static void -+sync_changed (WpCore * core, GAsyncResult * res, WpFiltersApi * self) -+{ -+ g_autoptr (GError) error = NULL; -+ -+ if (!wp_core_sync_finish (core, res, &error)) { -+ wp_warning_object (self, "core sync error: %s", error->message); -+ return; -+ } -+ -+ g_signal_emit (self, signals[SIGNAL_CHANGED], 0); -+} -+ -+static WpNode * -+find_target_node (WpFiltersApi *self, WpSpaJson *props_json) -+{ -+ g_auto (GValue) item = G_VALUE_INIT; -+ g_autoptr (WpIterator) it = NULL; -+ g_autoptr (WpObjectInterest) interest = NULL; -+ -+ /* Make sure the properties are a JSON object */ -+ if (!props_json || !wp_spa_json_is_object (props_json)) { -+ wp_warning_object (self, "Target properties must be a JSON object"); -+ return NULL; -+ } -+ -+ /* Create the object intereset with the target properties */ -+ interest = wp_object_interest_new (WP_TYPE_NODE, NULL); -+ it = wp_spa_json_new_iterator (props_json); -+ for (; wp_iterator_next (it, &item); g_value_unset (&item)) { -+ WpSpaJson *j = g_value_get_boxed (&item); -+ g_autofree gchar *key = NULL; -+ WpSpaJson *value_json; -+ g_autofree gchar *value = NULL; -+ -+ key = wp_spa_json_parse_string (j); -+ g_value_unset (&item); -+ if (!wp_iterator_next (it, &item)) { -+ wp_warning_object (self, -+ "Could not get valid key-value pairs from target properties"); -+ break; -+ } -+ value_json = g_value_get_boxed (&item); -+ value = wp_spa_json_parse_string (value_json); -+ if (!value) { -+ wp_warning_object (self, -+ "Could not get '%s' value from target properties", key); -+ break; -+ } -+ -+ wp_object_interest_add_constraint (interest, WP_CONSTRAINT_TYPE_PW_PROPERTY, -+ key, WP_CONSTRAINT_VERB_MATCHES, g_variant_new_string (value)); -+ } -+ -+ return wp_object_manager_lookup_full (self->nodes_om, -+ wp_object_interest_ref (interest)); -+} -+ -+static gboolean -+reevaluate_targets (WpFiltersApi *self) -+{ -+ g_autoptr (WpMetadata) m = NULL; -+ const gchar *json_str; -+ g_autoptr (WpSpaJson) json = NULL; -+ g_auto (GValue) item = G_VALUE_INIT; -+ g_autoptr (WpIterator) it = NULL; -+ gboolean changed = FALSE; -+ -+ g_hash_table_remove_all (self->targets); -+ -+ /* Make sure the metadata exists */ -+ m = wp_object_manager_lookup (self->metadata_om, WP_TYPE_METADATA, NULL); -+ if (!m) -+ return FALSE; -+ -+ /* Don't update anything if the metadata value is not set */ -+ json_str = wp_metadata_find (m, 0, "filters.configured.targets", NULL); -+ if (!json_str) -+ return FALSE; -+ -+ /* Make sure the metadata value is an object */ -+ json = wp_spa_json_new_from_string (json_str); -+ if (!json || !wp_spa_json_is_object (json)) { -+ wp_warning_object (self, -+ "ignoring metadata value as it is not a JSON object: %s", json_str); -+ return FALSE; -+ } -+ -+ /* Find the target node for each target, and add it to the hash table */ -+ it = wp_spa_json_new_iterator (json); -+ for (; wp_iterator_next (it, &item); g_value_unset (&item)) { -+ WpSpaJson *j = g_value_get_boxed (&item); -+ g_autofree gchar *key = NULL; -+ WpSpaJson *props; -+ g_autoptr (WpNode) target = NULL; -+ WpNode *curr_target; -+ -+ key = wp_spa_json_parse_string (j); -+ g_value_unset (&item); -+ if (!wp_iterator_next (it, &item)) { -+ wp_warning_object (self, -+ "Could not get valid key-value pairs from target object"); -+ break; -+ } -+ props = g_value_get_boxed (&item); -+ -+ /* Get current target */ -+ curr_target = g_hash_table_lookup (self->targets, key); -+ -+ /* Find the node and insert it into the table if found */ -+ target = find_target_node (self, props); -+ if (target) { -+ /* Check if the target changed */ -+ if (curr_target) { -+ guint32 target_bound_id = wp_proxy_get_bound_id (WP_PROXY (target)); -+ guint32 curr_bound_id = wp_proxy_get_bound_id (WP_PROXY (curr_target)); -+ if (target_bound_id != curr_bound_id) -+ changed = TRUE; -+ } -+ -+ g_hash_table_insert (self->targets, g_strdup (key), -+ g_steal_pointer (&target)); -+ } else { -+ if (curr_target) -+ changed = TRUE; -+ } -+ } -+ -+ return changed; -+} -+ -+static gboolean -+update_values_from_metadata (WpFiltersApi * self, Filter *f) -+{ -+ g_autoptr (WpMetadata) m = NULL; -+ const gchar *f_stream_name; -+ const gchar *f_node_name; -+ const gchar *json_str; -+ g_autoptr (WpSpaJson) json = NULL; -+ g_auto (GValue) item = G_VALUE_INIT; -+ g_autoptr (WpIterator) it = NULL; -+ gboolean changed = FALSE; -+ -+ /* Make sure the metadata exists */ -+ m = wp_object_manager_lookup (self->metadata_om, WP_TYPE_METADATA, NULL); -+ if (!m) -+ return FALSE; -+ -+ /* Make sure both the stream and node are available */ -+ if (!f->stream || !f->node) -+ return FALSE; -+ f_stream_name = wp_pipewire_object_get_property ( -+ WP_PIPEWIRE_OBJECT (f->stream), PW_KEY_NODE_NAME); -+ f_node_name = wp_pipewire_object_get_property ( -+ WP_PIPEWIRE_OBJECT (f->node), PW_KEY_NODE_NAME); -+ -+ /* Don't update anything if the metadata value is not set */ -+ json_str = wp_metadata_find (m, 0, "filters.configured.filters", NULL); -+ if (!json_str) -+ return FALSE; -+ -+ /* Make sure the metadata value is an array */ -+ json = wp_spa_json_new_from_string (json_str); -+ if (!json || !wp_spa_json_is_array (json)) { -+ wp_warning_object (self, -+ "ignoring metadata value as it is not a JSON array: %s", json_str); -+ return FALSE; -+ } -+ -+ /* Find the filter values in the metadata */ -+ it = wp_spa_json_new_iterator (json); -+ for (; wp_iterator_next (it, &item); g_value_unset (&item)) { -+ WpSpaJson *j = g_value_get_boxed (&item); -+ g_autofree gchar *stream_name = NULL; -+ g_autofree gchar *node_name = NULL; -+ g_autofree gchar *direction = NULL; -+ g_autofree gchar *target = NULL; -+ g_autofree gchar *mode = NULL; -+ WpDirection dir = WP_DIRECTION_INPUT; -+ gint priority; -+ -+ if (!j || !wp_spa_json_is_object (j)) -+ continue; -+ -+ /* Parse mandatory fields */ -+ if (!wp_spa_json_object_get (j, "stream-name", "s", &stream_name, -+ "node-name", "s", &node_name, "direction", "s", &direction, NULL)) { -+ g_autofree gchar *str = wp_spa_json_to_string (j); -+ wp_warning_object (self, -+ "failed to parse stream-name, node-name and direction in filter: %s", -+ str); -+ continue; -+ } -+ -+ /* Make sure direction is valid */ -+ if (g_str_equal (direction, "input")) { -+ dir = WP_DIRECTION_INPUT; -+ } else if (g_str_equal (direction, "output")) { -+ dir = WP_DIRECTION_OUTPUT; -+ } else { -+ g_autofree gchar *str = wp_spa_json_to_string (j); -+ wp_warning_object (self, -+ "direction %s is not valid for filter: %s", direction, str); -+ } -+ -+ /* Find first filter matching stream-name, node-name and direction */ -+ if (g_str_equal (f_stream_name, stream_name) && -+ g_str_equal (f_node_name, node_name) && -+ f->direction == dir) { -+ -+ /* Update target */ -+ if (wp_spa_json_object_get (j, "target", "s", &target, NULL)) { -+ if (!f->target || !g_str_equal (f->target, target)) { -+ g_clear_pointer (&f->target, g_free); -+ f->target = g_strdup (target); -+ changed = TRUE; -+ } -+ } else { -+ if (f->target) { -+ g_clear_pointer (&f->target, g_free); -+ changed = TRUE; -+ } -+ } -+ -+ /* Update mode */ -+ if (wp_spa_json_object_get (j, "mode", "s", &mode, NULL)) { -+ if (g_str_equal (mode, "always")) { -+ if (!f->enabled) { -+ f->enabled = TRUE; -+ changed = TRUE; -+ } -+ } else if (g_str_equal (mode, "never")) { -+ if (f->enabled) { -+ f->enabled = FALSE; -+ changed = TRUE; -+ } -+ } else if (g_str_equal (mode, "playback-only")) { -+ if (f->enabled != (self->n_playback_stream_nodes > 0)) { -+ f->enabled = self->n_playback_stream_nodes > 0; -+ changed = TRUE; -+ } -+ } else if (g_str_equal (mode, "capture-only")) { -+ if (f->enabled != (self->n_capture_stream_nodes > 0)) { -+ f->enabled = self->n_capture_stream_nodes > 0; -+ changed = TRUE; -+ } -+ } else { -+ wp_warning_object (self, -+ "The '%s' value is not a valid for the 'mode' filter field", -+ mode); -+ } -+ } -+ -+ /* Update priority */ -+ if (wp_spa_json_object_get (j, "priority", "i", &priority, NULL)) { -+ if (f->priority != priority) { -+ f->priority = priority; -+ changed = TRUE; -+ } -+ } -+ break; -+ } -+ } -+ -+ return changed; -+} -+ -+static gboolean -+reevaluate_filters (WpFiltersApi *self, WpDirection direction) -+{ -+ GList *filters; -+ gboolean changed = FALSE; -+ -+ /* Update filter values */ -+ filters = self->filters[direction]; -+ while (filters) { -+ Filter *f = (Filter *) filters->data; -+ if (update_values_from_metadata (self, f)) -+ changed = TRUE; -+ filters = g_list_next (filters); -+ } -+ -+ /* Sort filters if changed */ -+ if (changed) -+ self->filters[direction] = g_list_sort (self->filters[direction], -+ (GCompareFunc) filter_compare_func); -+ -+ return changed; -+} -+ -+static void -+schedule_changed (WpFiltersApi * self) -+{ -+ g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); -+ g_return_if_fail (core); -+ -+ wp_core_sync_closure (core, NULL, g_cclosure_new_object ( -+ G_CALLBACK (sync_changed), G_OBJECT (self))); -+} -+ -+static void -+on_stream_node_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ const gchar* media_class = wp_pipewire_object_get_property (proxy, -+ PW_KEY_MEDIA_CLASS); -+ -+ if (g_str_equal (media_class, "Stream/Output/Audio")) -+ self->n_playback_stream_nodes++; -+ else if (g_str_equal (media_class, "Stream/Input/Audio")) -+ self->n_capture_stream_nodes++; -+} -+ -+static void -+on_stream_node_removed (WpObjectManager *om, WpPipewireObject *proxy, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ const gchar* media_class = wp_pipewire_object_get_property (proxy, -+ PW_KEY_MEDIA_CLASS); -+ -+ if (g_str_equal (media_class, "Stream/Output/Audio") && -+ self->n_playback_stream_nodes > 0) -+ self->n_playback_stream_nodes--; -+ else if (g_str_equal (media_class, "Stream/Input/Audio") && -+ self->n_capture_stream_nodes > 0) -+ self->n_capture_stream_nodes--; -+} -+ -+static void -+on_stream_nodes_changed (WpObjectManager *om, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ gboolean changed = FALSE; -+ -+ /* Reevaluate everything */ -+ for (guint i = 0; i < 2; i++) -+ if (reevaluate_filters (self, i)) -+ changed = TRUE; -+ -+ if (changed) -+ schedule_changed (self); -+} -+ -+static void -+on_node_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ reevaluate_targets (self); -+} -+ -+static void -+on_node_removed (WpObjectManager *om, WpPipewireObject *proxy, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ reevaluate_targets (self); -+} -+ -+static void -+on_filter_node_added (WpObjectManager *om, WpPipewireObject *proxy, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ const gchar *key; -+ WpDirection dir; -+ gboolean is_stream; -+ GList *found; -+ -+ /* Get direction */ -+ key = wp_pipewire_object_get_property (proxy, PW_KEY_MEDIA_CLASS); -+ if (!key) -+ return; -+ -+ if (g_str_equal (key, "Audio/Sink") || -+ g_str_equal (key, "Stream/Output/Audio")) { -+ dir = WP_DIRECTION_INPUT; -+ } else if (g_str_equal (key, "Audio/Source") || -+ g_str_equal (key, "Stream/Input/Audio")) { -+ dir = WP_DIRECTION_OUTPUT; -+ } else { -+ wp_debug_object (self, "ignoring node with media class: %s", key); -+ return; -+ } -+ -+ /* Check whether the proxy is a stream or not */ -+ is_stream = FALSE; -+ if (g_str_equal (key, "Stream/Output/Audio") || -+ g_str_equal (key, "Stream/Input/Audio")) -+ is_stream = TRUE; -+ -+ /* We use the link group as filter name */ -+ key = wp_pipewire_object_get_property (proxy, PW_KEY_NODE_LINK_GROUP); -+ if (!key) { -+ wp_debug_object (self, "ignoring node without link group"); -+ return; -+ } -+ -+ /* Check if the filter already exists, and add it if it does not exist */ -+ found = g_list_find_custom (self->filters[dir], key, -+ (GCompareFunc) filter_equal_func); -+ if (!found) { -+ Filter *f = filter_new (key, dir, is_stream, WP_NODE (proxy)); -+ update_values_from_metadata (self, f); -+ self->filters[dir] = g_list_insert_sorted (self->filters[dir], -+ f, (GCompareFunc) filter_compare_func); -+ } else { -+ Filter *f = found->data; -+ if (is_stream) { -+ g_clear_object (&f->stream); -+ f->stream = g_object_ref (WP_NODE (proxy)); -+ } else { -+ g_clear_object (&f->node); -+ f->node = g_object_ref (WP_NODE (proxy)); -+ } -+ update_values_from_metadata (self, f); -+ self->filters[dir] = g_list_sort (self->filters[dir], -+ (GCompareFunc) filter_compare_func); -+ } -+} -+ -+static void -+on_filter_node_removed (WpObjectManager *om, WpPipewireObject *proxy, -+ gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ -+ const gchar *key; -+ WpDirection dir; -+ GList *found; -+ -+ /* Get direction */ -+ key = wp_pipewire_object_get_property (proxy, PW_KEY_MEDIA_CLASS); -+ if (!key) -+ return; -+ -+ if (g_str_equal (key, "Audio/Sink") || -+ g_str_equal (key, "Stream/Output/Audio")) { -+ dir = WP_DIRECTION_INPUT; -+ } else if (g_str_equal (key, "Audio/Source") || -+ g_str_equal (key, "Stream/Input/Audio")) { -+ dir = WP_DIRECTION_OUTPUT; -+ } else { -+ wp_debug_object (self, "ignoring node with media class: %s", key); -+ return; -+ } -+ -+ /* We use the link group as filter name */ -+ key = wp_pipewire_object_get_property (proxy, PW_KEY_NODE_LINK_GROUP); -+ if (!key) { -+ wp_debug_object (self, "ignoring node without link group"); -+ return; -+ } -+ -+ /* Find and remove the filter */ -+ found = g_list_find_custom (self->filters[dir], key, -+ (GCompareFunc) filter_equal_func); -+ if (found) { -+ self->filters[dir] = g_list_remove (self->filters[dir], found->data); -+ } -+} -+ -+static void -+on_metadata_changed (WpMetadata *m, guint32 subject, -+ const gchar *key, const gchar *type, const gchar *value, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ gboolean changed = FALSE; -+ -+ /* Reevaluate everything */ -+ if (reevaluate_targets (self)) -+ changed = TRUE; -+ for (guint i = 0; i < 2; i++) -+ if (reevaluate_filters (self, i)) -+ changed = TRUE; -+ -+ if (changed) -+ schedule_changed (self); -+} -+ -+static void -+on_metadata_added (WpObjectManager *om, WpMetadata *metadata, gpointer d) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (d); -+ gboolean changed = FALSE; -+ -+ /* Handle the changed signal */ -+ g_signal_connect_object (metadata, "changed", -+ G_CALLBACK (on_metadata_changed), self, 0); -+ -+ /* Reevaluate everything */ -+ if (reevaluate_targets (self)) -+ changed = TRUE; -+ for (guint i = 0; i < 2; i++) -+ if (reevaluate_filters (self, i)) -+ changed = TRUE; -+ -+ if (changed) -+ schedule_changed (self); -+} -+ -+static void -+on_metadata_installed (WpObjectManager * om, WpFiltersApi * self) -+{ -+ g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); -+ -+ /* Create the stream nodes object manager */ -+ self->stream_nodes_om = wp_object_manager_new (); -+ wp_object_manager_add_interest (self->stream_nodes_om, WP_TYPE_NODE, -+ WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "Stream/*/Audio", -+ WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_LINK_GROUP, "-", -+ NULL); -+ wp_object_manager_request_object_features (self->stream_nodes_om, -+ WP_TYPE_NODE, WP_OBJECT_FEATURES_ALL); -+ g_signal_connect_object (self->stream_nodes_om, "object-added", -+ G_CALLBACK (on_stream_node_added), self, 0); -+ g_signal_connect_object (self->stream_nodes_om, "object-removed", -+ G_CALLBACK (on_stream_node_removed), self, 0); -+ g_signal_connect_object (self->stream_nodes_om, "objects-changed", -+ G_CALLBACK (on_stream_nodes_changed), self, 0); -+ wp_core_install_object_manager (core, self->stream_nodes_om); -+ -+ /* Create the nodes object manager */ -+ self->nodes_om = wp_object_manager_new (); -+ wp_object_manager_add_interest (self->nodes_om, WP_TYPE_NODE, -+ WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "#s", "Audio/*", -+ WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_LINK_GROUP, "-", -+ NULL); -+ wp_object_manager_request_object_features (self->nodes_om, -+ WP_TYPE_NODE, WP_OBJECT_FEATURES_ALL); -+ g_signal_connect_object (self->nodes_om, "object-added", -+ G_CALLBACK (on_node_added), self, 0); -+ g_signal_connect_object (self->nodes_om, "object-removed", -+ G_CALLBACK (on_node_removed), self, 0); -+ g_signal_connect_object (self->nodes_om, "objects-changed", -+ G_CALLBACK (schedule_changed), self, G_CONNECT_SWAPPED); -+ wp_core_install_object_manager (core, self->nodes_om); -+ -+ /* Create the filter nodes object manager */ -+ self->filter_nodes_om = wp_object_manager_new (); -+ wp_object_manager_add_interest (self->filter_nodes_om, WP_TYPE_NODE, -+ WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_NODE_LINK_GROUP, "+", -+ NULL); -+ wp_object_manager_request_object_features (self->filter_nodes_om, -+ WP_TYPE_NODE, WP_OBJECT_FEATURES_ALL); -+ g_signal_connect_object (self->filter_nodes_om, "object-added", -+ G_CALLBACK (on_filter_node_added), self, 0); -+ g_signal_connect_object (self->filter_nodes_om, "object-removed", -+ G_CALLBACK (on_filter_node_removed), self, 0); -+ g_signal_connect_object (self->filter_nodes_om, "objects-changed", -+ G_CALLBACK (schedule_changed), self, G_CONNECT_SWAPPED); -+ wp_core_install_object_manager (core, self->filter_nodes_om); -+ -+ wp_object_update_features (WP_OBJECT (self), WP_PLUGIN_FEATURE_ENABLED, 0); -+} -+ -+static void -+wp_filters_api_enable (WpPlugin * plugin, WpTransition * transition) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (plugin); -+ g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); -+ -+ self->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, -+ g_object_unref); -+ -+ /* Create the metadata object manager */ -+ self->metadata_om = wp_object_manager_new (); -+ wp_object_manager_add_interest (self->metadata_om, WP_TYPE_METADATA, -+ WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "metadata.name", "=s", "filters", -+ NULL); -+ wp_object_manager_request_object_features (self->metadata_om, -+ WP_TYPE_METADATA, WP_OBJECT_FEATURES_ALL); -+ g_signal_connect_object (self->metadata_om, "object-added", -+ G_CALLBACK (on_metadata_added), self, 0); -+ g_signal_connect_object (self->metadata_om, "installed", -+ G_CALLBACK (on_metadata_installed), self, 0); -+ wp_core_install_object_manager (core, self->metadata_om); -+} -+ -+static void -+wp_filters_api_disable (WpPlugin * plugin) -+{ -+ WpFiltersApi * self = WP_FILTERS_API (plugin); -+ -+ for (guint i = 0; i < 2; i++) { -+ if (self->filters[i]) { -+ g_list_free_full (self->filters[i], (GDestroyNotify) filter_free); -+ self->filters[i] = NULL; -+ } -+ } -+ g_clear_pointer (&self->targets, g_hash_table_unref); -+ -+ g_clear_object (&self->metadata_om); -+ g_clear_object (&self->stream_nodes_om); -+ g_clear_object (&self->nodes_om); -+ g_clear_object (&self->filter_nodes_om); -+} -+ -+static void -+wp_filters_api_class_init (WpFiltersApiClass * klass) -+{ -+ WpPluginClass *plugin_class = (WpPluginClass *) klass; -+ -+ plugin_class->enable = wp_filters_api_enable; -+ plugin_class->disable = wp_filters_api_disable; -+ -+ signals[ACTION_IS_FILTER_ENABLED] = g_signal_new_class_handler ( -+ "is-filter-enabled", G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, -+ (GCallback) wp_filters_api_is_filter_enabled, -+ NULL, NULL, NULL, -+ G_TYPE_BOOLEAN, 2, G_TYPE_STRING, G_TYPE_STRING); -+ -+ signals[ACTION_GET_FILTER_TARGET] = g_signal_new_class_handler ( -+ "get-filter-target", G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, -+ (GCallback) wp_filters_api_get_filter_target, -+ NULL, NULL, NULL, -+ G_TYPE_INT, 2, G_TYPE_STRING, G_TYPE_STRING); -+ -+ signals[ACTION_GET_FILTER_FROM_TARGET] = g_signal_new_class_handler ( -+ "get-filter-from-target", G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, -+ (GCallback) wp_filters_api_get_filter_from_target, -+ NULL, NULL, NULL, -+ G_TYPE_INT, 2, G_TYPE_STRING, G_TYPE_INT); -+ -+ signals[ACTION_GET_DEFAULT_FILTER] = g_signal_new_class_handler ( -+ "get-default-filter", G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, -+ (GCallback) wp_filters_api_get_default_filter, -+ NULL, NULL, NULL, -+ G_TYPE_INT, 1, G_TYPE_STRING); -+ -+ signals[SIGNAL_CHANGED] = g_signal_new ( -+ "changed", G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, -+ G_TYPE_NONE, 0); -+} -+ -+WP_PLUGIN_EXPORT gboolean -+wireplumber__module_init (WpCore * core, GVariant * args, GError ** error) -+{ -+ wp_plugin_register (g_object_new (wp_filters_api_get_type (), -+ "name", "filters-api", -+ "core", core, -+ NULL)); -+ return TRUE; -+} -diff --git a/modules/module-lua-scripting/api/json.c b/modules/module-lua-scripting/api/json.c -index 2883a021..3d486be8 100644 ---- a/modules/module-lua-scripting/api/json.c -+++ b/modules/module-lua-scripting/api/json.c -@@ -264,7 +264,7 @@ spa_json_array_new (lua_State *L) - break; - } - default: -- luaL_error (L, "Json does not support lua type ", -+ luaL_error (L, "Json does not support lua type %s", - lua_typename(L, lua_type(L, -1))); - break; - } -diff --git a/src/config/policy.lua.d/10-default-policy.lua b/src/config/policy.lua.d/10-default-policy.lua -index 83d0a3b2..d3621a73 100644 ---- a/src/config/policy.lua.d/10-default-policy.lua -+++ b/src/config/policy.lua.d/10-default-policy.lua -@@ -12,6 +12,9 @@ default_policy.policy = { - -- surround audio if echo-cancel is enabled. - ["filter.forward-format"] = false, - -+ -- Whether to enable smart filter policy or not (experimental feature) -+ ["filter.smart"] = false, -+ - -- Set to 'true' to disable channel splitting & merging on nodes and enable - -- passthrough of audio in the same format as the format of the device. - -- Note that this breaks JACK support; it is generally not recommended -@@ -71,6 +74,12 @@ function default_policy.enable() - -- API to access default nodes from scripts - load_module("default-nodes-api") - -+ -- Load smart filter policy if requested -+ if default_policy.policy["filter.smart"] then -+ load_script("filters-metadata.lua", default_policy.filters_metadata) -+ load_module("filters-api") -+ end -+ - -- API to access mixer controls, needed for volume ducking - load_module("mixer-api") - -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -new file mode 100644 -index 00000000..76aecad0 ---- /dev/null -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -0,0 +1,73 @@ -+-- The smart filter policy configuration. -+-- You need to enable "filter.smart" in 10-default-policy.lua -+-- -+ -+-- The default filter metadata configuration when wireplumber starts. They also -+-- can be changed at runtime. -+default_policy.filters_metadata = { -+ ["filters"] = { -+ -- Input filters (meant to be linked with Audio/Sink device nodes) -+ { -+ ["stream-name"] = "output.virtual-sink", -- loopback playback -+ ["node-name"] = "input.virtual-sink", -- loopback sink -+ ["direction"] = "input", -- can only be 'input' or 'output' -+ ["target"] = nil, -- if nil, the default node will be used as target -+ ["mode"] = "always", -- can be 'always', 'never', 'capture-only' or 'playback-only' -+ ["priority"] = 30, -+ }, -+ { -+ ["stream-name"] = "filter-chain-playback", -- filter-chain playback -+ ["node-name"] = "filter-chain-sink", -- filter-chain sink -+ ["direction"] = "input", -- can only be 'input' or 'output' -+ ["target"] = "speakers", -- if nil, the default node will be used as target -+ ["mode"] = "always", -- can be 'always', 'never', 'capture-only' or 'playback-only' -+ ["priority"] = 20, -+ }, -+ { -+ ["stream-name"] = "echo-cancel-playback", -- echo-cancel playback -+ ["node-name"] = "echo-cancel-sink", -- echo-cancel sink -+ ["direction"] = "input", -- can only be 'input' or 'output' -+ ["target"] = "speakers", -- if nil, the default node will be used as target -+ ["mode"] = "capture-only", -- can be 'always', 'never', 'playback-only' or 'capture-only' -+ ["priority"] = 10, -+ }, -+ -+ -- Output filters (meant to be linked with Audio/Source device nodes) -+ { -+ ["stream-name"] = "input.virtual-source", -- loopback capture -+ ["node-name"] = "output.virtual-source", -- loopback source -+ ["direction"] = "output", -- can only be 'input' or 'output' -+ ["target"] = nil, -- if nil, the default node will be used as target -+ ["mode"] = "always", -- can be 'always', 'never', 'playback-only' or 'capture-only' -+ ["priority"] = 30, -+ }, -+ { -+ ["stream-name"] = "filter-chain-capture", -- filter-chain capture -+ ["node-name"] = "filter-chain-source", -- filter-chain source -+ ["direction"] = "output", -- can only be 'input' or 'output' -+ ["target"] = "microphone", -- if nil, the default node will be used as target -+ ["mode"] = "capture-only", -- can be 'always', 'never', 'playback-only' or 'capture-only' -+ ["priority"] = 20, -+ }, -+ { -+ ["stream-name"] = "echo-cancel-capture", -- echo-cancel capture -+ ["node-name"] = "echo-cancel-source", -- echo-cancel source -+ ["direction"] = "output", -- can only be 'input' or 'output' -+ ["target"] = "microphone", -- if nil, the default node will be used as target -+ ["mode"] = "capture-only", -- can be 'always', 'never', 'playback-only' or 'capture-only' -+ ["priority"] = 10, -+ } -+ }, -+ -+ -- The target node properties (any node properties can be defined) -+ ["targets"] = { -+ ["speakers"] = { -+ ["media.class"] = "Audio/Sink", -+ ["alsa.card_name"] = "my-speakers-card-name", -+ }, -+ ["microphone"] = { -+ ["media.class"] = "Audio/Source", -+ ["alsa.card_name"] = "my-microphone-card-name", -+ } -+ } -+} -diff --git a/src/scripts/filters-metadata.lua b/src/scripts/filters-metadata.lua -new file mode 100644 -index 00000000..04e3e6c6 ---- /dev/null -+++ b/src/scripts/filters-metadata.lua -@@ -0,0 +1,39 @@ -+-- WirePlumber -+-- -+-- Copyright © 2023 Collabora Ltd. -+-- @author Julian Bouzas -+-- -+-- SPDX-License-Identifier: MIT -+ -+-- Receive script arguments -+local config = ... or {} -+config["filters"] = config["filters"] or {} -+config["targets"] = config["targets"] or {} -+ -+f_metadata = ImplMetadata("filters") -+f_metadata:activate(Features.ALL, function (m, e) -+ if e then -+ Log.warning("failed to activate filters metadata: " .. tostring(e)) -+ return -+ end -+ -+ Log.info("activated filters metadata") -+ -+ -- Set filters metadata -+ local filters = {} -+ for _, f in ipairs(config["filters"]) do -+ table.insert (filters, Json.Object (f)) -+ end -+ local filters_json = Json.Array (filters) -+ m:set (0, "filters.configured.filters", "Spa:String:JSON", -+ filters_json:to_string()) -+ -+ -- Set targets metadata -+ local targets = {} -+ for name, props in pairs(config["targets"]) do -+ targets[name] = Json.Object (props) -+ end -+ local targets_json = Json.Object (targets) -+ m:set (0, "filters.configured.targets", "Spa:String:JSON", -+ targets_json:to_string()) -+end) -diff --git a/src/scripts/policy-node.lua b/src/scripts/policy-node.lua -index 99ad8473..f249f343 100644 ---- a/src/scripts/policy-node.lua -+++ b/src/scripts/policy-node.lua -@@ -18,6 +18,7 @@ self.scanning = false - self.pending_rescan = false - self.events_skipped = false - self.pending_error_timer = nil -+self.filters_api = Plugin.find("filters-api") - - function rescan() - for si in linkables_om:iterate() do -@@ -437,9 +438,21 @@ function findDefaultLinkable (si) - local si_props = si.properties - local target_direction = getTargetDirection(si_props) - local def_node_id = getDefaultNode(si_props, target_direction) -- return linkables_om:lookup { -+ local si_target = linkables_om:lookup { - Constraint { "node.id", "=", tostring(def_node_id) } - } -+ -+ -- get origin filter from default target if filters API is enabled -+ if self.filters_api ~= nil and si_target ~= nil then -+ local target_node_id = si_target.properties["node.id"] -+ target_node_id = self.filters_api:call("get-filter-from-target", -+ target_direction, target_node_id) -+ si_target = linkables_om:lookup { -+ Constraint { "node.id", "=", tostring(target_node_id) } -+ } -+ end -+ -+ return si_target - end - - function checkPassthroughCompatibility (si, si_target) -@@ -468,12 +481,19 @@ function findBestLinkable (si) - } do - local si_target_props = si_target.properties - local si_target_node_id = si_target_props["node.id"] -+ local si_target_node = si:get_associated_proxy ("node") -+ local si_target_link_group = si_target_node.properties["node.link-group"] - local priority = tonumber(si_target_props["priority.session"]) or 0 - - Log.debug(string.format("Looking at: %s (%s)", - tostring(si_target_props["node.name"]), - tostring(si_target_node_id))) - -+ -- Never use a filter as a best linkable if filters API is loaded -+ if self.filters_api ~= nil and si_target_link_group ~= nil then -+ goto skip_linkable -+ end -+ - if not canLink (si_props, si_target) then - Log.debug("... cannot link, skip linkable") - goto skip_linkable -@@ -517,6 +537,25 @@ function findBestLinkable (si) - tostring(target_picked.properties["node.name"]), - tostring(target_picked.properties["node.id"]), - tostring(target_can_passthrough))) -+ -+ -- get origin filter from target if filters API is enabled -+ if self.filters_api ~= nil and target_picked ~= nil then -+ local target_node_id = target_picked.properties["node.id"] -+ target_node_id = self.filters_api:call("get-filter-from-target", -+ target_direction, target_node_id) -+ target_picked = linkables_om:lookup { -+ Constraint { "node.id", "=", tostring(target_node_id) } -+ } -+ if target_picked == nil then -+ return nil, nil -+ end -+ target_can_passthrough = checkPassthroughCompatibility (si, target_picked) -+ Log.info(string.format("... best filter picked: %s (%s), can_passthrough:%s", -+ tostring(target_picked.properties["node.name"]), -+ tostring(target_picked.properties["node.id"]), -+ tostring(target_can_passthrough))) -+ end -+ - return target_picked, target_can_passthrough - else - return nil, nil -@@ -564,6 +603,28 @@ function lookupLink (si_id, si_target_id) - return link - end - -+function checkFilter (si, handle_nonstreams) -+ -- handle all filter nodes if filters API is not loaded -+ if self.filters_api == nil then -+ return true -+ end -+ -+ -- always handle filters if handle_nonstreams is true, even if it is disabled -+ if handle_nonstreams then -+ return true -+ end -+ -+ -- always return true if this is not a filter -+ local node = si:get_associated_proxy ("node") -+ local link_group = node.properties["node.link-group"] -+ if link_group == nil then -+ return true -+ end -+ -+ local direction = getTargetDirection(si.properties) -+ return self.filters_api:call("is-filter-enabled", direction, link_group) -+end -+ - function checkLinkable(si, handle_nonstreams) - -- only handle stream session items - local si_props = si.properties -@@ -572,6 +633,11 @@ function checkLinkable(si, handle_nonstreams) - return false - end - -+ -- check filters -+ if not checkFilter (si, handle_nonstreams) then -+ return false -+ end -+ - -- Determine if we can handle item by this policy - if endpoints_om:get_n_objects () > 0 and - si_props["item.factory.name"] == "si-audio-adapter" then -@@ -652,6 +718,37 @@ function checkFollowDefault (si, si_target, has_node_defined_target) - end - end - -+function findFilterTarget (si) -+ local node = si:get_associated_proxy ("node") -+ local direction = getTargetDirection (si.properties) -+ local link_group = node.properties["node.link-group"] -+ local target_id = -1 -+ -+ -- always return nil if filters API is not loaded -+ if self.filters_api == nil then -+ return nil -+ end -+ -+ if link_group == nil then -+ -- if this is a client stream that is not a filter, link it to the highest -+ -- priority filter that does not have a group, if any. -+ target_id = self.filters_api:call("get-default-filter", direction) -+ else -+ -- if this is a filter, get its target -+ target_id = self.filters_api:call("get-filter-target", -+ direction, link_group) -+ end -+ -+ if (target_id == -1) then -+ return nil -+ end -+ -+ Log.info (".. filter target ID is " .. tostring(target_id)) -+ return linkables_om:lookup { -+ Constraint { "node.id", "=", tostring(target_id) } -+ } -+end -+ - function handleLinkable (si) - if checkPending () then - return -@@ -683,11 +780,19 @@ function handleLinkable (si) - local si_target, has_defined_target, has_node_defined_target - = findDefinedTarget(si_props) - local can_passthrough = si_target and canPassthrough(si, si_target) -- - if si_target and si_must_passthrough and not can_passthrough then - si_target = nil - end - -+ -- find filter target (always returns nil for non filters) -+ if si_target == nil then -+ si_target = findFilterTarget(si) -+ local can_passthrough = si_target and canPassthrough(si, si_target) -+ if si_target and si_must_passthrough and not can_passthrough then -+ si_target = nil -+ end -+ end -+ - -- if the client has seen a target that we haven't yet prepared, schedule - -- a rescan one more time and hope for the best - local si_id = si.id -@@ -876,6 +981,17 @@ if config.follow and default_nodes ~= nil then - end) - end - -+-- listen for filter node changes -+if self.filters_api ~= nil then -+ self.filters_api:connect("changed", function () -+ -- unlink all the filters and schedule rescan -+ for si in linkables_om:iterate { Constraint { "node.link-group", "+" } } do -+ unhandleLinkable (si) -+ end -+ scheduleRescan () -+ end) -+end -+ - -- listen for target.node metadata changes if config.move is enabled - if config.move then - metadata_om:connect("object-added", function (om, metadata) --- -2.42.0 - - -From bea7195a12ba1014f62e40f80c0f2e127a1aaa2e Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Thu, 23 Mar 2023 09:26:45 -0400 -Subject: [PATCH 03/11] config: do not restore stream target by default - ---- - src/config/main.lua.d/40-stream-defaults.lua | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/config/main.lua.d/40-stream-defaults.lua b/src/config/main.lua.d/40-stream-defaults.lua -index b869099b..d25aab0d 100644 ---- a/src/config/main.lua.d/40-stream-defaults.lua -+++ b/src/config/main.lua.d/40-stream-defaults.lua -@@ -6,7 +6,7 @@ stream_defaults.properties = { - ["restore-props"] = true, - - -- whether to restore the last stream target or not -- ["restore-target"] = true, -+ ["restore-target"] = false, - - -- the default channel volume for new streams whose props were never saved - -- previously. This is only used if "restore-props" is set to true. --- -2.42.0 - - -From f4d88774670d561b3a2d37c0d6534b204cdcb14f Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Thu, 23 Mar 2023 09:41:23 -0400 -Subject: [PATCH 04/11] config: enable smart filter policy - ---- - src/config/policy.lua.d/10-default-policy.lua | 2 +- - src/config/policy.lua.d/30-filters-config.lua | 5 +++-- - 2 files changed, 4 insertions(+), 3 deletions(-) - -diff --git a/src/config/policy.lua.d/10-default-policy.lua b/src/config/policy.lua.d/10-default-policy.lua -index d3621a73..412d47a8 100644 ---- a/src/config/policy.lua.d/10-default-policy.lua -+++ b/src/config/policy.lua.d/10-default-policy.lua -@@ -13,7 +13,7 @@ default_policy.policy = { - ["filter.forward-format"] = false, - - -- Whether to enable smart filter policy or not (experimental feature) -- ["filter.smart"] = false, -+ ["filter.smart"] = true, - - -- Set to 'true' to disable channel splitting & merging on nodes and enable - -- passthrough of audio in the same format as the format of the device. -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index 76aecad0..8d919fb7 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -63,11 +63,12 @@ default_policy.filters_metadata = { - ["targets"] = { - ["speakers"] = { - ["media.class"] = "Audio/Sink", -- ["alsa.card_name"] = "my-speakers-card-name", -+ ["alsa.card_name"] = "acp5x", -+ ["device.profile.description"] = "Speaker", - }, - ["microphone"] = { - ["media.class"] = "Audio/Source", -- ["alsa.card_name"] = "my-microphone-card-name", -+ ["alsa.card_name"] = "acp5x", - } - } - } --- -2.42.0 - - -From 30d26697a83ce11c4a2343c0ad4ddb113e64f72b Mon Sep 17 00:00:00 2001 -From: Ashok Sidipotu -Date: Wed, 12 Jul 2023 10:17:37 +0530 -Subject: [PATCH 05/11] policy-device-profile.lua: introduce user profile - priority list - ---- - src/config/main.lua.d/40-device-defaults.lua | 14 +++++++------- - 1 file changed, 7 insertions(+), 7 deletions(-) - -diff --git a/src/config/main.lua.d/40-device-defaults.lua b/src/config/main.lua.d/40-device-defaults.lua -index 91c4e189..b87eec51 100644 ---- a/src/config/main.lua.d/40-device-defaults.lua -+++ b/src/config/main.lua.d/40-device-defaults.lua -@@ -39,13 +39,13 @@ device_defaults.profile_priorities = { - }, - -- lower the index higher the priority - priorities = { -- -- "a2dp-sink-sbc", -- -- "a2dp-sink-aptx_ll", -- -- "a2dp-sink-aptx", -- -- "a2dp-sink-aptx_hd", -- -- "a2dp-sink-ldac", -- -- "a2dp-sink-aac", -- -- "a2dp-sink-sbc_xq", -+ "a2dp-sink-aptx_ll", -+ "a2dp-sink-aptx", -+ "a2dp-sink-aptx_hd", -+ "a2dp-sink-ldac", -+ "a2dp-sink-aac", -+ "a2dp-sink-sbc", -+ "a2dp-sink-sbc_xq", - } - }, - } --- -2.42.0 - - -From 4c6762c41a0d8f6298e2f35cea42a86ea3ef8ff9 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Fri, 3 Nov 2023 09:24:59 -0400 -Subject: [PATCH 06/11] m-filters-api: remove get-default-filter API - -This was redundant as you can get the default filter by calling -'get-filter-from-target' using the default target. The policy logic -should not change. ---- - modules/module-filters-api.c | 34 ---------------------------------- - src/scripts/policy-node.lua | 17 +++++++---------- - 2 files changed, 7 insertions(+), 44 deletions(-) - -diff --git a/modules/module-filters-api.c b/modules/module-filters-api.c -index 32c67c85..d7dc781a 100644 ---- a/modules/module-filters-api.c -+++ b/modules/module-filters-api.c -@@ -230,33 +230,6 @@ wp_filters_api_get_filter_from_target (WpFiltersApi * self, - return res; - } - --static gint --wp_filters_api_get_default_filter (WpFiltersApi * self, const gchar *direction) --{ -- WpDirection dir = WP_DIRECTION_INPUT; -- GList *filters; -- -- g_return_val_if_fail (direction, -1); -- -- /* Get the filters for the given direction */ -- if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -- dir = WP_DIRECTION_OUTPUT; -- filters = self->filters[dir]; -- -- /* The default filter is the highest priority filter without target, this is -- * the first filer that is enabled because the list is sorted by priority */ -- while (filters) { -- Filter *f = (Filter *) filters->data; -- if (f->enabled && !f->target) -- return wp_proxy_get_bound_id (WP_PROXY (f->node)); -- -- /* Advance */ -- filters = g_list_next (filters); -- } -- -- return -1; --} -- - static void - sync_changed (WpCore * core, GAsyncResult * res, WpFiltersApi * self) - { -@@ -881,13 +854,6 @@ wp_filters_api_class_init (WpFiltersApiClass * klass) - NULL, NULL, NULL, - G_TYPE_INT, 2, G_TYPE_STRING, G_TYPE_INT); - -- signals[ACTION_GET_DEFAULT_FILTER] = g_signal_new_class_handler ( -- "get-default-filter", G_TYPE_FROM_CLASS (klass), -- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, -- (GCallback) wp_filters_api_get_default_filter, -- NULL, NULL, NULL, -- G_TYPE_INT, 1, G_TYPE_STRING); -- - signals[SIGNAL_CHANGED] = g_signal_new ( - "changed", G_TYPE_FROM_CLASS (klass), - G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, -diff --git a/src/scripts/policy-node.lua b/src/scripts/policy-node.lua -index f249f343..c5a15ec6 100644 ---- a/src/scripts/policy-node.lua -+++ b/src/scripts/policy-node.lua -@@ -719,9 +719,6 @@ function checkFollowDefault (si, si_target, has_node_defined_target) - end - - function findFilterTarget (si) -- local node = si:get_associated_proxy ("node") -- local direction = getTargetDirection (si.properties) -- local link_group = node.properties["node.link-group"] - local target_id = -1 - - -- always return nil if filters API is not loaded -@@ -729,16 +726,16 @@ function findFilterTarget (si) - return nil - end - -+ -- always return nil if this is not a filter -+ local node = si:get_associated_proxy ("node") -+ local link_group = node.properties["node.link-group"] - if link_group == nil then -- -- if this is a client stream that is not a filter, link it to the highest -- -- priority filter that does not have a group, if any. -- target_id = self.filters_api:call("get-default-filter", direction) -- else -- -- if this is a filter, get its target -- target_id = self.filters_api:call("get-filter-target", -- direction, link_group) -+ return nil - end - -+ -- get the filter target -+ local direction = getTargetDirection (si.properties) -+ target_id = self.filters_api:call("get-filter-target", direction, link_group) - if (target_id == -1) then - return nil - end --- -2.42.0 - - -From 1acb97e5606dfd842a71c9a2de520ee665f17fd0 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Fri, 3 Nov 2023 11:44:57 -0400 -Subject: [PATCH 07/11] m-filters-api: add support for exclusive targets - -Filters whose target are exclusive won't be linked to the default device if the -target does not exist. ---- - modules/module-filters-api.c | 127 ++++++++++++------ - src/config/policy.lua.d/30-filters-config.lua | 18 ++- - src/scripts/filters-metadata.lua | 7 +- - src/scripts/policy-node.lua | 26 ++-- - 4 files changed, 120 insertions(+), 58 deletions(-) - -diff --git a/modules/module-filters-api.c b/modules/module-filters-api.c -index d7dc781a..8852a6c5 100644 ---- a/modules/module-filters-api.c -+++ b/modules/module-filters-api.c -@@ -49,6 +49,12 @@ struct _Filter { - }; - typedef struct _Filter Filter; - -+struct _Target { -+ gboolean exclusive; -+ WpNode *node; -+}; -+typedef struct _Target Target; -+ - static guint - get_filter_priority (const gchar *link_group) - { -@@ -87,6 +93,22 @@ filter_free (Filter *f) - g_free (f); - } - -+static Target * -+target_new (gboolean exclusive, WpNode *node) -+{ -+ Target *t = g_malloc0 (sizeof (Target)); -+ t->exclusive = exclusive; -+ t->node = node ? g_object_ref (node) : NULL; -+ return t; -+} -+ -+static void -+target_free (Target *t) -+{ -+ g_clear_object (&t->node); -+ g_free (t); -+} -+ - static gint - filter_equal_func (const Filter *f, const gchar *link_group) - { -@@ -133,16 +155,18 @@ wp_filters_api_is_filter_enabled (WpFiltersApi * self, const gchar *direction, - return found->enabled; - } - --static gint -+static WpSpaJson * - wp_filters_api_get_filter_target (WpFiltersApi * self, const gchar *direction, - const gchar *link_group) - { - WpDirection dir = WP_DIRECTION_INPUT; - GList *filters; - Filter *found; -+ g_autoptr (WpSpaJson) res = wp_spa_json_new_object ( -+ "exclusive", "b", FALSE, "bound_id", "i", -1, NULL); - -- g_return_val_if_fail (direction, -1); -- g_return_val_if_fail (link_group, -1); -+ g_return_val_if_fail (direction, g_steal_pointer (&res)); -+ g_return_val_if_fail (link_group, g_steal_pointer (&res)); - - /* Get the filters for the given direction */ - if (g_str_equal (direction, "output") || g_str_equal (direction, "Output")) -@@ -153,10 +177,10 @@ wp_filters_api_get_filter_target (WpFiltersApi * self, const gchar *direction, - filters = g_list_find_custom (filters, link_group, - (GCompareFunc) filter_equal_func); - if (!filters) -- return -1; -+ return g_steal_pointer (&res); - found = filters->data; - if (!found->enabled) -- return -1; -+ return g_steal_pointer (&res); - - /* Return the previous filter with matching target that is enabled */ - while ((filters = g_list_previous (filters))) { -@@ -164,18 +188,27 @@ wp_filters_api_get_filter_target (WpFiltersApi * self, const gchar *direction, - if ((prev->target == found->target || - (prev->target && found->target && - g_str_equal (prev->target, found->target))) && -- prev->enabled) -- return wp_proxy_get_bound_id (WP_PROXY (prev->node)); -+ prev->enabled) { -+ return wp_spa_json_new_object ( -+ "exclusive", "b", FALSE, -+ "bound_id", "i", wp_proxy_get_bound_id (WP_PROXY (prev->node)), -+ NULL); -+ } - } - - /* Find the target */ - if (found->target) { -- WpNode *node = g_hash_table_lookup (self->targets, found->target); -- if (node) -- return wp_proxy_get_bound_id (WP_PROXY (node)); -+ Target *t = g_hash_table_lookup (self->targets, found->target); -+ if (t) { -+ return wp_spa_json_new_object ( -+ "exclusive", "b", t->exclusive, -+ "bound_id", "i", -+ t->node ? (gint)wp_proxy_get_bound_id (WP_PROXY (t->node)) : -1, -+ NULL); -+ } - } - -- return -1; -+ return g_steal_pointer (&res); - } - - static gint -@@ -198,12 +231,17 @@ wp_filters_api_get_filter_from_target (WpFiltersApi * self, - /* Find the first target matching target_id */ - while (filters) { - Filter *f = (Filter *) filters->data; -- gint f_target_id = wp_filters_api_get_filter_target (self, direction, -- f->link_group); -- if (f_target_id == target_id && f->enabled) { -- target = f->target; -- found = TRUE; -- break; -+ if (f->enabled) { -+ gint f_target_id; -+ g_autoptr (WpSpaJson) f_target = wp_filters_api_get_filter_target (self, -+ direction, f->link_group); -+ if (f_target && wp_spa_json_is_object (f_target) && -+ wp_spa_json_object_get (f_target, "bound_id", "i", &f_target_id, NULL) -+ && f_target_id == target_id) { -+ target = f->target; -+ found = TRUE; -+ break; -+ } - } - - /* Advance */ -@@ -323,9 +361,11 @@ reevaluate_targets (WpFiltersApi *self) - for (; wp_iterator_next (it, &item); g_value_unset (&item)) { - WpSpaJson *j = g_value_get_boxed (&item); - g_autofree gchar *key = NULL; -- WpSpaJson *props; -- g_autoptr (WpNode) target = NULL; -- WpNode *curr_target; -+ WpSpaJson *value; -+ gboolean exclusive = FALSE; -+ g_autoptr (WpSpaJson) props = NULL; -+ g_autoptr (WpNode) target_node = NULL; -+ Target *curr_target; - - key = wp_spa_json_parse_string (j); - g_value_unset (&item); -@@ -334,27 +374,36 @@ reevaluate_targets (WpFiltersApi *self) - "Could not get valid key-value pairs from target object"); - break; - } -- props = g_value_get_boxed (&item); -+ value = g_value_get_boxed (&item); -+ if (!value || !wp_spa_json_is_object (value)) { -+ wp_warning_object (self, "Target value must be a JSON object"); -+ break; -+ } - -- /* Get current target */ -- curr_target = g_hash_table_lookup (self->targets, key); -+ /* Get exclusive */ -+ wp_spa_json_object_get (value, "exclusive", "b", &exclusive, NULL); - -- /* Find the node and insert it into the table if found */ -- target = find_target_node (self, props); -- if (target) { -- /* Check if the target changed */ -- if (curr_target) { -- guint32 target_bound_id = wp_proxy_get_bound_id (WP_PROXY (target)); -- guint32 curr_bound_id = wp_proxy_get_bound_id (WP_PROXY (curr_target)); -- if (target_bound_id != curr_bound_id) -- changed = TRUE; -- } -+ /* Get target node */ -+ wp_spa_json_object_get (value, "props", "J", &props, NULL); -+ if (props) -+ target_node = find_target_node (self, props); - -- g_hash_table_insert (self->targets, g_strdup (key), -- g_steal_pointer (&target)); -- } else { -- if (curr_target) -+ /* Update values if target exists in the table, otherwise add new target */ -+ curr_target = g_hash_table_lookup (self->targets, key); -+ if (curr_target) { -+ if (curr_target->exclusive != exclusive) { -+ curr_target->exclusive = exclusive; -+ changed = TRUE; -+ } -+ if (curr_target->node != target_node) { -+ g_clear_object (&curr_target->node); -+ curr_target->node = g_steal_pointer (&target_node); - changed = TRUE; -+ } -+ } else { -+ g_hash_table_insert (self->targets, g_strdup (key), -+ target_new (exclusive, target_node)); -+ changed = TRUE; - } - } - -@@ -790,7 +839,7 @@ wp_filters_api_enable (WpPlugin * plugin, WpTransition * transition) - g_autoptr (WpCore) core = wp_object_get_core (WP_OBJECT (self)); - - self->targets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, -- g_object_unref); -+ (GDestroyNotify) target_free); - - /* Create the metadata object manager */ - self->metadata_om = wp_object_manager_new (); -@@ -845,7 +894,7 @@ wp_filters_api_class_init (WpFiltersApiClass * klass) - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - (GCallback) wp_filters_api_get_filter_target, - NULL, NULL, NULL, -- G_TYPE_INT, 2, G_TYPE_STRING, G_TYPE_STRING); -+ WP_TYPE_SPA_JSON, 2, G_TYPE_STRING, G_TYPE_STRING); - - signals[ACTION_GET_FILTER_FROM_TARGET] = g_signal_new_class_handler ( - "get-filter-from-target", G_TYPE_FROM_CLASS (klass), -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index 8d919fb7..bf4b8d75 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -59,16 +59,22 @@ default_policy.filters_metadata = { - } - }, - -- -- The target node properties (any node properties can be defined) -+ -- The filter targets - ["targets"] = { - ["speakers"] = { -- ["media.class"] = "Audio/Sink", -- ["alsa.card_name"] = "acp5x", -- ["device.profile.description"] = "Speaker", -+ ["exclusive"] = false, -+ ["props"] = { -+ ["media.class"] = "Audio/Sink", -+ ["alsa.card_name"] = "acp5x", -+ ["device.profile.description"] = "Speaker", -+ } - }, - ["microphone"] = { -- ["media.class"] = "Audio/Source", -- ["alsa.card_name"] = "acp5x", -+ ["exclusive"] = false, -+ ["props"] = { -+ ["media.class"] = "Audio/Source", -+ ["alsa.card_name"] = "acp5x", -+ } - } - } - } -diff --git a/src/scripts/filters-metadata.lua b/src/scripts/filters-metadata.lua -index 04e3e6c6..49b4d0e8 100644 ---- a/src/scripts/filters-metadata.lua -+++ b/src/scripts/filters-metadata.lua -@@ -30,8 +30,11 @@ f_metadata:activate(Features.ALL, function (m, e) - - -- Set targets metadata - local targets = {} -- for name, props in pairs(config["targets"]) do -- targets[name] = Json.Object (props) -+ for name, value in pairs(config["targets"]) do -+ targets[name] = Json.Object { -+ exclusive = value.exclusive and true or false, -+ props = Json.Object (value.props) -+ } - end - local targets_json = Json.Object (targets) - m:set (0, "filters.configured.targets", "Spa:String:JSON", -diff --git a/src/scripts/policy-node.lua b/src/scripts/policy-node.lua -index c5a15ec6..035c3006 100644 ---- a/src/scripts/policy-node.lua -+++ b/src/scripts/policy-node.lua -@@ -719,31 +719,31 @@ function checkFollowDefault (si, si_target, has_node_defined_target) - end - - function findFilterTarget (si) -- local target_id = -1 -- - -- always return nil if filters API is not loaded - if self.filters_api == nil then -- return nil -+ return nil, false - end - - -- always return nil if this is not a filter - local node = si:get_associated_proxy ("node") - local link_group = node.properties["node.link-group"] - if link_group == nil then -- return nil -+ return nil, false - end - - -- get the filter target - local direction = getTargetDirection (si.properties) -- target_id = self.filters_api:call("get-filter-target", direction, link_group) -- if (target_id == -1) then -- return nil -+ local target_json = self.filters_api:call("get-filter-target", direction, link_group) -+ if target_json == nil then -+ return nil, false - end -+ target = target_json:parse() - -- Log.info (".. filter target ID is " .. tostring(target_id)) -+ Log.info (".. filter target ID is " .. tostring(target.bound_id) .. -+ " (" .. tostring (target.exclusive) .. ")") - return linkables_om:lookup { -- Constraint { "node.id", "=", tostring(target_id) } -- } -+ Constraint { "node.id", "=", tostring(target.bound_id) } -+ }, target.exclusive - end - - function handleLinkable (si) -@@ -783,7 +783,11 @@ function handleLinkable (si) - - -- find filter target (always returns nil for non filters) - if si_target == nil then -- si_target = findFilterTarget(si) -+ si_target, exclusive = findFilterTarget(si) -+ -- don't fallback if filter target is not found and exclusive is true -+ if si_target == nil and exclusive then -+ return -+ end - local can_passthrough = si_target and canPassthrough(si, si_target) - if si_target and si_must_passthrough and not can_passthrough then - si_target = nil --- -2.42.0 - - -From 0010f03fafa2841f7618dad4b609466b6dad11c7 Mon Sep 17 00:00:00 2001 -From: Julian Bouzas -Date: Mon, 6 Nov 2023 14:33:34 -0500 -Subject: [PATCH 08/11] policy-bluetooth: remove application names array and - use BT loopback filter - -Uses a BT loopback filter to know when an application wants to capture audio -from the current BT device. If the BT loopback filter is used, wireplumber will -automatically switch the device to HSP/HFP profile, otherwise the BT device -profile is always set to A2DP. ---- - src/config/policy.lua.d/10-default-policy.lua | 10 -- - src/config/policy.lua.d/30-filters-config.lua | 17 ++- - src/config/wireplumber.conf | 20 +++ - src/scripts/policy-bluetooth.lua | 139 ++++++++++-------- - 4 files changed, 110 insertions(+), 76 deletions(-) - -diff --git a/src/config/policy.lua.d/10-default-policy.lua b/src/config/policy.lua.d/10-default-policy.lua -index 412d47a8..7d4ea77c 100644 ---- a/src/config/policy.lua.d/10-default-policy.lua -+++ b/src/config/policy.lua.d/10-default-policy.lua -@@ -33,16 +33,6 @@ bluetooth_policy.policy = { - - -- Whether to use headset profile in the presence of an input stream. - ["media-role.use-headset-profile"] = true, -- -- -- Application names correspond to application.name in stream properties. -- -- Applications which do not set media.role but which should be considered -- -- for role based profile switching can be specified here. -- ["media-role.applications"] = { -- "Firefox", "Chromium input", "Google Chrome input", "Brave input", -- "Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine", -- "Telegram Desktop", "telegram-desktop", "linphone", "Mumble", -- "WEBRTC VoiceEngine", "Skype", "Firefox Developer Edition", -- }, - } - - dsp_policy = {} -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index bf4b8d75..88429cdc 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -33,11 +33,19 @@ default_policy.filters_metadata = { - }, - - -- Output filters (meant to be linked with Audio/Source device nodes) -+ { -+ ["stream-name"] = "virtual-bluetooth-source-in", -- loopback bluetooth capture -+ ["node-name"] = "virtual-bluetooth-source-out", -- loopback bluetooth source -+ ["direction"] = "output", -- can only be 'input' or 'output' -+ ["target"] = "bluetooth-source", -- if nil, the default node will be used as target -+ ["mode"] = "always", -- can be 'always', 'never', 'playback-only' or 'capture-only' -+ ["priority"] = 30, -+ }, - { - ["stream-name"] = "input.virtual-source", -- loopback capture - ["node-name"] = "output.virtual-source", -- loopback source - ["direction"] = "output", -- can only be 'input' or 'output' -- ["target"] = nil, -- if nil, the default node will be used as target -+ ["target"] = "microphone", -- if nil, the default node will be used as target - ["mode"] = "always", -- can be 'always', 'never', 'playback-only' or 'capture-only' - ["priority"] = 30, - }, -@@ -75,6 +83,13 @@ default_policy.filters_metadata = { - ["media.class"] = "Audio/Source", - ["alsa.card_name"] = "acp5x", - } -+ }, -+ ["bluetooth-source"] = { -+ ["exclusive"] = true, -+ ["props"] = { -+ ["media.class"] = "Audio/Source", -+ ["device.api"] = "bluez5" -+ } - } - } - } -diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf -index 85d7be12..4c9dd568 100644 ---- a/src/config/wireplumber.conf -+++ b/src/config/wireplumber.conf -@@ -77,6 +77,26 @@ context.modules = [ - - # Provides factories to make SPA node objects. - { name = libpipewire-module-spa-node-factory } -+ -+ # Virtual Bluetooth Source -+ { -+ name = libpipewire-module-loopback -+ args = { -+ capture.props = { -+ node.name = virtual-bluetooth-source-in -+ node.description = "Virtual Bluetooth Source In" -+ audio.position = [ MONO ] -+ stream.dont-remix = true -+ node.passive = true -+ } -+ playback.props = { -+ node.name = virtual-bluetooth-source-out -+ node.description = "Virtual Bluetooth Source Out" -+ audio.position = [ MONO ] -+ media.class = Audio/Source -+ } -+ } -+ } - ] - - wireplumber.components = [ -diff --git a/src/scripts/policy-bluetooth.lua b/src/scripts/policy-bluetooth.lua -index f8f69a14..7aecb8b0 100644 ---- a/src/scripts/policy-bluetooth.lua -+++ b/src/scripts/policy-bluetooth.lua -@@ -26,7 +26,6 @@ - - local config = ... - local use_persistent_storage = config["use-persistent-storage"] or false --local applications = {} - local use_headset_profile = config["media-role.use-headset-profile"] or false - local profile_restore_timeout_msec = 2000 - -@@ -41,17 +40,6 @@ local last_profiles = {} - local active_streams = {} - local previous_streams = {} - --for _, value in ipairs(config["media-role.applications"] or {}) do -- applications[value] = true --end -- --metadata_om = ObjectManager { -- Interest { -- type = "metadata", -- Constraint { "metadata.name", "=", "default" }, -- } --} -- - devices_om = ObjectManager { - Interest { - type = "device", -@@ -68,6 +56,16 @@ streams_om = ObjectManager { - } - } - -+nodes_om = ObjectManager { -+ Interest { -+ type = "node", -+ Constraint { "node.name", "=", "virtual-bluetooth-source-out", type = "pw-global" }, -+ Constraint { "media.class", "matches", "Audio/Source", type = "pw-global" }, -+ } -+} -+ -+links_om = ObjectManager { Interest { type = "link" } } -+ - local function parseParam(param_to_parse, id) - local param = param_to_parse:parse() - if param.pod_type == "Object" and param.object_id == id then -@@ -117,19 +115,6 @@ local function isSwitched(device) - return getSavedLastProfile(device) ~= nil - end - --local function isBluez5AudioSink(sink_name) -- if sink_name and string.find(sink_name, "bluez_output.") ~= nil then -- return true -- end -- return false --end -- --local function isBluez5DefaultAudioSink() -- local metadata = metadata_om:lookup() -- local default_audio_sink = metadata:find(0, "default.audio.sink") -- return isBluez5AudioSink(default_audio_sink) --end -- - local function findProfile(device, index, name) - for p in device:iterate_params("EnumProfile") do - local profile = parseParam(p, "EnumProfile") -@@ -228,7 +213,6 @@ local function switchProfile() - end - - local cur_profile_name = getCurrentProfile(device) -- saveLastProfile(device, cur_profile_name) - - _, index, name = findProfile(device, nil, cur_profile_name) - if hasProfileInputRoute(device, index) then -@@ -251,6 +235,8 @@ local function switchProfile() - index = index - } - -+ saveLastProfile(device, cur_profile_name) -+ - Log.info("Setting profile of '" - .. device.properties["device.description"] - .. "' from: " .. cur_profile_name -@@ -270,8 +256,6 @@ local function restoreProfile() - local profile_name = getSavedLastProfile(device) - local cur_profile_name = getCurrentProfile(device) - -- saveLastProfile(device, nil) -- - if cur_profile_name then - Log.info("Setting saved headset profile to: " .. cur_profile_name) - saveHeadsetProfile(device, cur_profile_name) -@@ -286,6 +270,8 @@ local function restoreProfile() - index = index - } - -+ saveLastProfile(device, nil) -+ - Log.info("Restoring profile of '" - .. device.properties["device.description"] - .. "' from: " .. cur_profile_name -@@ -312,18 +298,14 @@ local function triggerRestoreProfile() - end) - end - ---- We consider a Stream of interest to have role Communication if it has ---- media.role set to Communication in props or it is in our list of ---- applications as these applications do not set media.role correctly or at ---- all. --local function checkStreamStatus(stream) -- local app_name = stream.properties["application.name"] -- local stream_role = stream.properties["media.role"] -+function parseBool(var) -+ return var and (var:lower() == "true" or var == "1") -+end - -- if not (stream_role == "Communication" or applications[app_name]) then -- return false -- end -- if not isBluez5DefaultAudioSink() then -+local function checkStreamStatus (stream) -+ -- Ignore monitor streams -+ local is_monitor = parseBool (stream.properties["stream.monitor"]) -+ if is_monitor then - return false - end - -@@ -334,7 +316,25 @@ local function checkStreamStatus(stream) - return false - end - -- return true -+ -- Make sure the virtual BT filter node exists -+ local node = nodes_om:lookup () -+ if node == nil then -+ return false -+ end -+ -+ -- Check if the stream is linked to the bluetooth loopback filter -+ local stream_id = tonumber(stream["bound-id"]) -+ local bt_out_id = tonumber(node["bound-id"]) -+ for l in links_om:iterate() do -+ local p = l.properties -+ local out_id = tonumber(p["link.output.node"]) -+ local in_id = tonumber(p["link.input.node"]) -+ if in_id == stream_id and out_id == bt_out_id then -+ return true -+ end -+ end -+ -+ return false - end - - local function handleStream(stream) -@@ -361,38 +361,47 @@ local function handleAllStreams() - end - end - --streams_om:connect("object-added", function (_, stream) -- stream:connect("state-changed", function (stream, old_state, cur_state) -- handleStream(stream) -- end) -- stream:connect("params-changed", handleStream) -- handleStream(stream) --end) -- --streams_om:connect("object-removed", function (_, stream) -- active_streams[stream["bound-id"]] = nil -- previous_streams[stream["bound-id"]] = nil -- triggerRestoreProfile() --end) -- - devices_om:connect("object-added", function (_, device) - -- Devices are unswitched initially -- if isSwitched(device) then -- saveLastProfile(device, nil) -- end -+ saveLastProfile(device, nil) - handleAllStreams() - end) - --metadata_om:connect("object-added", function (_, metadata) -- metadata:connect("changed", function (m, subject, key, t, value) -- if (use_headset_profile and subject == 0 and key == "default.audio.sink" -- and isBluez5AudioSink(value)) then -- -- If bluez sink is set as default, rescan for active input streams -- handleAllStreams() -+links_om:connect("object-added", function (_, link) -+ if handleAllStreams then -+ local p = link.properties -+ for stream in streams_om:iterate { -+ Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" }, -+ Constraint { "stream.monitor", "!", "true" } -+ } do -+ local in_id = tonumber(p["link.input.node"]) -+ local stream_id = tonumber(stream["bound-id"]) -+ if in_id == stream_id then -+ handleStream(stream) -+ end - end -- end) -+ end -+end) -+ -+links_om:connect("object-removed", function (_, link) -+ if handleAllStreams then -+ local p = link.properties -+ for stream in streams_om:iterate { -+ Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" }, -+ Constraint { "stream.monitor", "!", "true" } -+ } do -+ local in_id = tonumber(p["link.input.node"]) -+ local stream_id = tonumber(stream["bound-id"]) -+ if in_id == stream_id then -+ active_streams[stream["bound-id"]] = nil -+ previous_streams[stream["bound-id"]] = nil -+ triggerRestoreProfile() -+ end -+ end -+ end - end) - --metadata_om:activate() - devices_om:activate() - streams_om:activate() -+nodes_om:activate() -+links_om:activate() --- -2.42.0 - - -From ae7cde6dd7c7660da5bbd07f27fe18a624c8c67d Mon Sep 17 00:00:00 2001 -From: Ethan Geller -Date: Mon, 13 Nov 2023 22:10:05 -0800 -Subject: [PATCH 09/11] fix speaker tunings for galileo - ---- - src/config/policy.lua.d/30-filters-config.lua | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index 88429cdc..a4b617d8 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -73,7 +73,7 @@ default_policy.filters_metadata = { - ["exclusive"] = false, - ["props"] = { - ["media.class"] = "Audio/Sink", -- ["alsa.card_name"] = "acp5x", -+ ["alsa.card_name"] = "sof-nau8821-max", - ["device.profile.description"] = "Speaker", - } - }, --- -2.42.0 - - -From 0ed6dc783bcb4033fb82c0f9b3e2c26977c3e19e Mon Sep 17 00:00:00 2001 -From: Ethan Geller -Date: Wed, 15 Nov 2023 14:32:56 -0800 -Subject: [PATCH 10/11] Revert "policy-bluetooth: remove application names - array and use BT loopback filter" - -This reverts commit 5a760629b6e81268383f32406119fbb4ac3a42b0. ---- - src/config/policy.lua.d/10-default-policy.lua | 10 ++ - src/config/policy.lua.d/30-filters-config.lua | 17 +-- - src/config/wireplumber.conf | 20 --- - src/scripts/policy-bluetooth.lua | 139 ++++++++---------- - 4 files changed, 76 insertions(+), 110 deletions(-) - -diff --git a/src/config/policy.lua.d/10-default-policy.lua b/src/config/policy.lua.d/10-default-policy.lua -index 7d4ea77c..412d47a8 100644 ---- a/src/config/policy.lua.d/10-default-policy.lua -+++ b/src/config/policy.lua.d/10-default-policy.lua -@@ -33,6 +33,16 @@ bluetooth_policy.policy = { - - -- Whether to use headset profile in the presence of an input stream. - ["media-role.use-headset-profile"] = true, -+ -+ -- Application names correspond to application.name in stream properties. -+ -- Applications which do not set media.role but which should be considered -+ -- for role based profile switching can be specified here. -+ ["media-role.applications"] = { -+ "Firefox", "Chromium input", "Google Chrome input", "Brave input", -+ "Microsoft Edge input", "Vivaldi input", "ZOOM VoiceEngine", -+ "Telegram Desktop", "telegram-desktop", "linphone", "Mumble", -+ "WEBRTC VoiceEngine", "Skype", "Firefox Developer Edition", -+ }, - } - - dsp_policy = {} -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index a4b617d8..37badd80 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -33,19 +33,11 @@ default_policy.filters_metadata = { - }, - - -- Output filters (meant to be linked with Audio/Source device nodes) -- { -- ["stream-name"] = "virtual-bluetooth-source-in", -- loopback bluetooth capture -- ["node-name"] = "virtual-bluetooth-source-out", -- loopback bluetooth source -- ["direction"] = "output", -- can only be 'input' or 'output' -- ["target"] = "bluetooth-source", -- if nil, the default node will be used as target -- ["mode"] = "always", -- can be 'always', 'never', 'playback-only' or 'capture-only' -- ["priority"] = 30, -- }, - { - ["stream-name"] = "input.virtual-source", -- loopback capture - ["node-name"] = "output.virtual-source", -- loopback source - ["direction"] = "output", -- can only be 'input' or 'output' -- ["target"] = "microphone", -- if nil, the default node will be used as target -+ ["target"] = nil, -- if nil, the default node will be used as target - ["mode"] = "always", -- can be 'always', 'never', 'playback-only' or 'capture-only' - ["priority"] = 30, - }, -@@ -83,13 +75,6 @@ default_policy.filters_metadata = { - ["media.class"] = "Audio/Source", - ["alsa.card_name"] = "acp5x", - } -- }, -- ["bluetooth-source"] = { -- ["exclusive"] = true, -- ["props"] = { -- ["media.class"] = "Audio/Source", -- ["device.api"] = "bluez5" -- } - } - } - } -diff --git a/src/config/wireplumber.conf b/src/config/wireplumber.conf -index 4c9dd568..85d7be12 100644 ---- a/src/config/wireplumber.conf -+++ b/src/config/wireplumber.conf -@@ -77,26 +77,6 @@ context.modules = [ - - # Provides factories to make SPA node objects. - { name = libpipewire-module-spa-node-factory } -- -- # Virtual Bluetooth Source -- { -- name = libpipewire-module-loopback -- args = { -- capture.props = { -- node.name = virtual-bluetooth-source-in -- node.description = "Virtual Bluetooth Source In" -- audio.position = [ MONO ] -- stream.dont-remix = true -- node.passive = true -- } -- playback.props = { -- node.name = virtual-bluetooth-source-out -- node.description = "Virtual Bluetooth Source Out" -- audio.position = [ MONO ] -- media.class = Audio/Source -- } -- } -- } - ] - - wireplumber.components = [ -diff --git a/src/scripts/policy-bluetooth.lua b/src/scripts/policy-bluetooth.lua -index 7aecb8b0..f8f69a14 100644 ---- a/src/scripts/policy-bluetooth.lua -+++ b/src/scripts/policy-bluetooth.lua -@@ -26,6 +26,7 @@ - - local config = ... - local use_persistent_storage = config["use-persistent-storage"] or false -+local applications = {} - local use_headset_profile = config["media-role.use-headset-profile"] or false - local profile_restore_timeout_msec = 2000 - -@@ -40,6 +41,17 @@ local last_profiles = {} - local active_streams = {} - local previous_streams = {} - -+for _, value in ipairs(config["media-role.applications"] or {}) do -+ applications[value] = true -+end -+ -+metadata_om = ObjectManager { -+ Interest { -+ type = "metadata", -+ Constraint { "metadata.name", "=", "default" }, -+ } -+} -+ - devices_om = ObjectManager { - Interest { - type = "device", -@@ -56,16 +68,6 @@ streams_om = ObjectManager { - } - } - --nodes_om = ObjectManager { -- Interest { -- type = "node", -- Constraint { "node.name", "=", "virtual-bluetooth-source-out", type = "pw-global" }, -- Constraint { "media.class", "matches", "Audio/Source", type = "pw-global" }, -- } --} -- --links_om = ObjectManager { Interest { type = "link" } } -- - local function parseParam(param_to_parse, id) - local param = param_to_parse:parse() - if param.pod_type == "Object" and param.object_id == id then -@@ -115,6 +117,19 @@ local function isSwitched(device) - return getSavedLastProfile(device) ~= nil - end - -+local function isBluez5AudioSink(sink_name) -+ if sink_name and string.find(sink_name, "bluez_output.") ~= nil then -+ return true -+ end -+ return false -+end -+ -+local function isBluez5DefaultAudioSink() -+ local metadata = metadata_om:lookup() -+ local default_audio_sink = metadata:find(0, "default.audio.sink") -+ return isBluez5AudioSink(default_audio_sink) -+end -+ - local function findProfile(device, index, name) - for p in device:iterate_params("EnumProfile") do - local profile = parseParam(p, "EnumProfile") -@@ -213,6 +228,7 @@ local function switchProfile() - end - - local cur_profile_name = getCurrentProfile(device) -+ saveLastProfile(device, cur_profile_name) - - _, index, name = findProfile(device, nil, cur_profile_name) - if hasProfileInputRoute(device, index) then -@@ -235,8 +251,6 @@ local function switchProfile() - index = index - } - -- saveLastProfile(device, cur_profile_name) -- - Log.info("Setting profile of '" - .. device.properties["device.description"] - .. "' from: " .. cur_profile_name -@@ -256,6 +270,8 @@ local function restoreProfile() - local profile_name = getSavedLastProfile(device) - local cur_profile_name = getCurrentProfile(device) - -+ saveLastProfile(device, nil) -+ - if cur_profile_name then - Log.info("Setting saved headset profile to: " .. cur_profile_name) - saveHeadsetProfile(device, cur_profile_name) -@@ -270,8 +286,6 @@ local function restoreProfile() - index = index - } - -- saveLastProfile(device, nil) -- - Log.info("Restoring profile of '" - .. device.properties["device.description"] - .. "' from: " .. cur_profile_name -@@ -298,14 +312,18 @@ local function triggerRestoreProfile() - end) - end - --function parseBool(var) -- return var and (var:lower() == "true" or var == "1") --end -+-- We consider a Stream of interest to have role Communication if it has -+-- media.role set to Communication in props or it is in our list of -+-- applications as these applications do not set media.role correctly or at -+-- all. -+local function checkStreamStatus(stream) -+ local app_name = stream.properties["application.name"] -+ local stream_role = stream.properties["media.role"] - --local function checkStreamStatus (stream) -- -- Ignore monitor streams -- local is_monitor = parseBool (stream.properties["stream.monitor"]) -- if is_monitor then -+ if not (stream_role == "Communication" or applications[app_name]) then -+ return false -+ end -+ if not isBluez5DefaultAudioSink() then - return false - end - -@@ -316,25 +334,7 @@ local function checkStreamStatus (stream) - return false - end - -- -- Make sure the virtual BT filter node exists -- local node = nodes_om:lookup () -- if node == nil then -- return false -- end -- -- -- Check if the stream is linked to the bluetooth loopback filter -- local stream_id = tonumber(stream["bound-id"]) -- local bt_out_id = tonumber(node["bound-id"]) -- for l in links_om:iterate() do -- local p = l.properties -- local out_id = tonumber(p["link.output.node"]) -- local in_id = tonumber(p["link.input.node"]) -- if in_id == stream_id and out_id == bt_out_id then -- return true -- end -- end -- -- return false -+ return true - end - - local function handleStream(stream) -@@ -361,47 +361,38 @@ local function handleAllStreams() - end - end - --devices_om:connect("object-added", function (_, device) -- -- Devices are unswitched initially -- saveLastProfile(device, nil) -- handleAllStreams() -+streams_om:connect("object-added", function (_, stream) -+ stream:connect("state-changed", function (stream, old_state, cur_state) -+ handleStream(stream) -+ end) -+ stream:connect("params-changed", handleStream) -+ handleStream(stream) - end) - --links_om:connect("object-added", function (_, link) -- if handleAllStreams then -- local p = link.properties -- for stream in streams_om:iterate { -- Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" }, -- Constraint { "stream.monitor", "!", "true" } -- } do -- local in_id = tonumber(p["link.input.node"]) -- local stream_id = tonumber(stream["bound-id"]) -- if in_id == stream_id then -- handleStream(stream) -- end -- end -+streams_om:connect("object-removed", function (_, stream) -+ active_streams[stream["bound-id"]] = nil -+ previous_streams[stream["bound-id"]] = nil -+ triggerRestoreProfile() -+end) -+ -+devices_om:connect("object-added", function (_, device) -+ -- Devices are unswitched initially -+ if isSwitched(device) then -+ saveLastProfile(device, nil) - end -+ handleAllStreams() - end) - --links_om:connect("object-removed", function (_, link) -- if handleAllStreams then -- local p = link.properties -- for stream in streams_om:iterate { -- Constraint { "media.class", "matches", "Stream/Input/Audio", type = "pw-global" }, -- Constraint { "stream.monitor", "!", "true" } -- } do -- local in_id = tonumber(p["link.input.node"]) -- local stream_id = tonumber(stream["bound-id"]) -- if in_id == stream_id then -- active_streams[stream["bound-id"]] = nil -- previous_streams[stream["bound-id"]] = nil -- triggerRestoreProfile() -- end -+metadata_om:connect("object-added", function (_, metadata) -+ metadata:connect("changed", function (m, subject, key, t, value) -+ if (use_headset_profile and subject == 0 and key == "default.audio.sink" -+ and isBluez5AudioSink(value)) then -+ -- If bluez sink is set as default, rescan for active input streams -+ handleAllStreams() - end -- end -+ end) - end) - -+metadata_om:activate() - devices_om:activate() - streams_om:activate() --nodes_om:activate() --links_om:activate() --- -2.42.0 - - -From 043390080937c05df74a48eaff5a9713ff0ea12f Mon Sep 17 00:00:00 2001 -From: Ethan Geller -Date: Wed, 15 Nov 2023 14:35:00 -0800 -Subject: [PATCH 11/11] fix filter chain targeting for mic. - ---- - src/config/policy.lua.d/30-filters-config.lua | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/config/policy.lua.d/30-filters-config.lua b/src/config/policy.lua.d/30-filters-config.lua -index 37badd80..8e8725fc 100644 ---- a/src/config/policy.lua.d/30-filters-config.lua -+++ b/src/config/policy.lua.d/30-filters-config.lua -@@ -73,7 +73,7 @@ default_policy.filters_metadata = { - ["exclusive"] = false, - ["props"] = { - ["media.class"] = "Audio/Source", -- ["alsa.card_name"] = "acp5x", -+ ["alsa.card_name"] = "sof-nau8821-max", - } - } - } --- -2.42.0 - diff --git a/spec_files/wireplumber/wireplumber.spec b/spec_files/wireplumber/wireplumber.spec deleted file mode 100644 index 40939e76..00000000 --- a/spec_files/wireplumber/wireplumber.spec +++ /dev/null @@ -1,272 +0,0 @@ -Name: wireplumber -Version: 0.5.2 -Release: 1%{?dist}.bazzite.{{{ git_dir_version }}} -Summary: A modular session/policy manager for PipeWire - -License: MIT -URL: https://pipewire.pages.freedesktop.org/wireplumber/ -Source0: https://gitlab.freedesktop.org/pipewire/%{name}/-/archive/%{version}/%{name}-%{version}.tar.bz2 - -## upstream patches - -## upstreamable patches - -## fedora patches - -## valve patches -Patch0: valve.patch - -BuildRequires: gettext -BuildRequires: meson gcc pkgconfig -BuildRequires: pkgconfig(glib-2.0) -BuildRequires: pkgconfig(gobject-2.0) -BuildRequires: pkgconfig(gmodule-2.0) -BuildRequires: pkgconfig(gio-unix-2.0) -BuildRequires: pkgconfig(libspa-0.2) >= 0.2 -BuildRequires: pkgconfig(libpipewire-0.3) >= 0.3.26 -BuildRequires: pkgconfig(systemd) -BuildRequires: systemd-devel >= 184 -BuildRequires: pkgconfig(lua) -BuildRequires: gobject-introspection-devel -BuildRequires: python3-lxml doxygen -BuildRequires: systemd-rpm-macros -%{?systemd_ordering} - -# Make sure that we have -libs package in the same version -Requires: %{name}-libs%{?_isa} = %{version}-%{release} - -Requires: steamdeck-dsp - -Provides: pipewire-session-manager -Conflicts: pipewire-session-manager - -%package libs -Summary: Libraries for WirePlumber clients -Recommends: %{name}%{?_isa} = %{version}-%{release} - -%description libs -This package contains the runtime libraries for any application that wishes -to interface with WirePlumber. - -%package devel -Summary: Development files for %{name} -Requires: %{name}%{?_isa} = %{version}-%{release} -Requires: %{name}-libs%{?_isa} = %{version}-%{release} - -%description devel -The %{name}-devel package contains libraries and header files for -developing applications that use %{name}. - -%package doc -Summary: Documentation for %{name} -Recommends: %{name}%{?_isa} = %{version}-%{release} - -%description doc -This package contains the documentation for WirePlumber. - -%description -WirePlumber is a modular session/policy manager for PipeWire and a -GObject-based high-level library that wraps PipeWire's API, providing -convenience for writing the daemon's modules as well as external tools for -managing PipeWire. - -%prep -%autosetup -p1 - -%build -%meson -Dsystem-lua=true \ - -Ddoc=disabled \ - -Dsystemd=enabled \ - -Dsystemd-user-service=true \ - -Dintrospection=enabled \ - -Delogind=disabled -%meson_build - -%install -%meson_install - -# Create local config skeleton -mkdir -p %{buildroot}%{_sysconfdir}/wireplumber/{bluetooth.lua.d,common,main.lua.d,policy.lua.d} - -# Create missing empty system config dirs for other packages to drop files in -mkdir -p %{buildroot}%{_datadir}/wireplumber/wireplumber.conf.d - -%find_lang %{name} - -%posttrans -%systemd_user_post %{name}.service - -%preun -%systemd_user_preun %{name}.service - -%triggerun -- fedora-release < 35 -# When upgrading to Fedora Linux 35, transition to WirePlumber by default -if [ -x "/bin/systemctl" ]; then - /bin/systemctl --no-reload preset --global %{name}.service || : -fi - -%files -%license LICENSE -%{_bindir}/wireplumber -%{_bindir}/wpctl -%{_bindir}/wpexec -%dir %{_sysconfdir}/wireplumber -%dir %{_sysconfdir}/wireplumber/bluetooth.lua.d -%dir %{_sysconfdir}/wireplumber/common -%dir %{_sysconfdir}/wireplumber/main.lua.d -%dir %{_sysconfdir}/wireplumber/policy.lua.d -%{_datadir}/wireplumber/ -%{_datadir}/zsh/site-functions/_wpctl -%{_userunitdir}/wireplumber.service -%{_userunitdir}/wireplumber@.service - -%files libs -f %{name}.lang -%license LICENSE -%dir %{_libdir}/wireplumber-0.5/ -%{_libdir}/wireplumber-0.5/libwireplumber-*.so -%{_libdir}/libwireplumber-0.5.so.* -%{_libdir}/girepository-1.0/Wp-0.5.typelib - -%files devel -%{_includedir}/wireplumber-0.5/ -%{_libdir}/libwireplumber-0.5.so -%{_libdir}/pkgconfig/wireplumber-0.5.pc -%{_datadir}/gir-1.0/Wp-0.5.gir - -%files doc -%{_datadir}/doc/wireplumber/ - -%changelog -* Mon Apr 22 2024 Wim Taymans - 0.5.2-1 -- wireplumber 0.5.2 - -* Thu Apr 18 2024 Neal Gompa - 0.5.1-2 -- Backport support for loading external WpConf from disk - -* Tue Apr 02 2024 Wim Taymans - 0.5.1-1 -- wireplumber 0.5.1 - -* Mon Mar 25 2024 Adam Williamson - 0.5.0-2 -- Backport MR #620 to fix issues with bluetooth headsets etc. -- Resolves: rhbz#2269343 - -* Mon Mar 18 2024 Wim Taymans - 0.5.0-1 -- wireplumber 0.5.0 - -* Mon Mar 11 2024 Wim Taymans - 0.4.90-1 -- wireplumber 0.4.90 - -* Thu Jan 25 2024 Wim Taymans - 0.4.81-1 -- wireplumber 0.4.81 - -* Mon Dec 4 2023 Wim Taymans - 0.4.17-1 -- wireplumber 0.4.17 - -* Mon Dec 4 2023 Hector Martin - 0.4.16-2 -- Create and own /usr/share/wireplumber/wireplumber.conf.d - -* Thu Nov 23 2023 Wim Taymans - 0.4.16-1 -- wireplumber 0.4.16 - -* Tue Nov 7 2023 Hector Martin - 0.4.15-2 -- Add upstream patch to enable node hiding - -* Thu Oct 12 2023 Wim Taymans - 0.4.15-1 -- wireplumber 0.4.15 - -* Fri Sep 08 2023 Peter Hutterer -- SPDX migration: mark as done - -* Sat Jul 22 2023 Fedora Release Engineering - 0.4.14-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild - -* Thu Mar 9 2023 Wim Taymans - 0.4.14-1 -- wireplumber 0.4.14 - -* Sat Jan 21 2023 Fedora Release Engineering - 0.4.13-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild - -* Tue Dec 13 2022 Wim Taymans - 0.4.13-1 -- wireplumber 0.4.13 - -* Fri Oct 07 2022 Wim Taymans - 0.4.12-1 -- wireplumber 0.4.12 - -* Thu Aug 04 2022 Ville-Pekka Vainio - 0.4.11-4 -- Add two patches to fix a rescan loop with Bluetooth - -* Sat Jul 23 2022 Fedora Release Engineering - 0.4.11-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild - -* Wed Jul 13 2022 Wim Taymans - 0.4.11-2 -- Add patch to avoid crashes in VM -- Add patch to avoid dbus crash -- Resolves: rhbz#2104986 - -* Tue Jul 5 2022 Wim Taymans - 0.4.11-1 -- wireplumber 0.4.11 - -* Tue May 10 2022 Wim Taymans - 0.4.10-1 -- wireplumber 0.4.10 - -* Tue Mar 22 2022 Wim Taymans - 0.4.9-1 -- wireplumber 0.4.9 - -* Wed Mar 16 2022 Frantisek Zatloukal - 0.4.8-3 -- Backport e429db7e8c266045aee25e153fb2308bd61fe233 to fix sound on aarch64 - -* Mon Mar 7 2022 Wim Taymans - 0.4.8-2 -- Add patch to fix openal and WINE format negotiation. - -* Mon Feb 7 2022 Wim Taymans - 0.4.8-1 -- wireplumber 0.4.8 - -* Sat Jan 22 2022 Fedora Release Engineering - 0.4.7-3 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild - -* Fri Jan 14 2022 Wim Taymans - 0.4.7-2 -- Add patch to fix default device. - -* Thu Jan 13 2022 Wim Taymans - 0.4.7-1 -- wireplumber 0.4.7 - -* Fri Jan 07 2022 Wim Taymans - 0.4.6-1 -- wireplumber 0.4.6 - -* Fri Nov 19 2021 Wim Taymans - 0.4.5-3 -- Add some upstream patches for OBS audio output capture and - device switching. - -* Wed Nov 17 2021 Peter Hutterer - 0.4.5-2 -- Move the systemd scriptlet to posttrans so we can dnf swap with - media-session (#2022584) - -* Thu Nov 11 2021 Wim Taymans - 0.4.5-1 -- wireplumber 0.4.5 - -* Tue Nov 02 2021 Neal Gompa - 0.4.4-3 -- Try again for WirePlumber preset upgrades to F35+ (#2016253) - -* Sun Oct 24 2021 Neal Gompa - 0.4.4-2 -- Ensure WirePlumber activates on upgrade to F35+ (#2016253) - -* Fri Oct 15 2021 Wim Taymans - 0.4.4-1 -- wireplumber 0.4.4 - -* Wed Oct 13 2021 Neal Gompa - 0.4.3-3 -- Fix config setup in file list (#2013861) - -* Mon Oct 11 2021 Peter Hutterer - 0.4.3-2 -- Fix segfault due to a typo (#2012606) - -* Fri Oct 08 2021 Wim Taymans - 0.4.3-1 -- wireplumber 0.4.3 - -* Wed Sep 01 2021 Peter Hutterer - 0.4.2-1 -- wireplumber 0.4.2 - -* Fri Jul 23 2021 Fedora Release Engineering - 0.4.1-2 -- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild - -* Tue Jul 06 2021 Peter Hutterer 0.4.1-1 -- Initial package (#1976012)