diff --git a/lib/Drawing/StyleManager.vala b/lib/Drawing/StyleManager.vala index b32e82697..bdd180de6 100644 --- a/lib/Drawing/StyleManager.vala +++ b/lib/Drawing/StyleManager.vala @@ -12,6 +12,11 @@ public class Gala.Drawing.StyleManager : Object { LIGHT } + public enum ReducedMotion { + NO_PREFERENCE, + REDUCE, + } + [DBus (name="org.freedesktop.Accounts")] private interface Accounts : Object { public abstract async string find_user_by_name (string name) throws IOError, DBusError; @@ -28,8 +33,16 @@ public class Gala.Drawing.StyleManager : Object { public abstract int accent_color { get; set; } } + [DBus (name = "org.freedesktop.portal.Settings")] + interface Portal : Object { + public signal void setting_changed (string namespace, string key, Variant value); + public abstract async Variant read_one (string namespace, string key) throws DBusError, IOError; + } + private const string FDO_ACCOUNTS_NAME = "org.freedesktop.Accounts"; private const string FDO_ACCOUNTS_PATH = "/org/freedesktop/Accounts"; + private const string PORTAL_NAME = "org.freedesktop.portal.Desktop"; + private const string PORTAL_PATH = "/org/freedesktop/portal/desktop"; private const uint8 ACCENT_COLOR_ALPHA = 64; private static GLib.Once instance; @@ -43,9 +56,11 @@ public class Gala.Drawing.StyleManager : Object { #else public Cogl.Color theme_accent_color { get; private set; default = { 0, 0, 0, ACCENT_COLOR_ALPHA }; } #endif + public ReducedMotion reduced_motion { get; private set; default = NO_PREFERENCE; } private PantheonAccountsService? pantheon_proxy; private SettingsDaemonAccountsService? settings_daemon_proxy; + private Portal? portal_proxy; construct { Bus.watch_name ( @@ -56,6 +71,12 @@ public class Gala.Drawing.StyleManager : Object { settings_daemon_proxy = null; } ); + + Bus.watch_name ( + SESSION, PORTAL_NAME, NONE, + () => on_portal_appeared.begin (), + () => portal_proxy = null + ); } private async void connect_to_accounts_service () { @@ -141,4 +162,37 @@ public class Gala.Drawing.StyleManager : Object { return 0; } + + private async void on_portal_appeared () { + try { + portal_proxy = yield Bus.get_proxy (SESSION, PORTAL_NAME, PORTAL_PATH); + } catch (Error e) { + warning ("Could not connect to portal: %s", e.message); + return; + } + + portal_proxy.setting_changed.connect (on_setting_changed); + + try { + var variant = yield portal_proxy.read_one ("org.freedesktop.appearance", "reduced-motion"); + reduced_motion = (ReducedMotion) variant.get_uint32 (); + } catch (Error e) { + warning ("Could not read reduced-motion setting from portal: %s", e.message); + } + } + + private void on_setting_changed (string namespace, string key, Variant value) { + if (namespace != "org.freedesktop.appearance") { + return; + } + + switch (key) { + case "reduced-motion": + reduced_motion = (ReducedMotion) value.get_uint32 (); + break; + + default: + break; + } + } } diff --git a/lib/Gestures/GestureController.vala b/lib/Gestures/GestureController.vala index 3d5fa5555..c83ac0077 100644 --- a/lib/Gestures/GestureController.vala +++ b/lib/Gestures/GestureController.vala @@ -260,7 +260,7 @@ public class Gala.GestureController : Object { return; } - if (!Meta.Prefs.get_gnome_animations ()) { + if (!Meta.Prefs.get_gnome_animations () || Utils.should_reduce_motion ()) { target.propagate (COMMIT, action, clamped_to); progress = clamped_to; finished (); diff --git a/lib/TransitionBuilder.vala b/lib/TransitionBuilder.vala index 453eff3a7..ad056857a 100644 --- a/lib/TransitionBuilder.vala +++ b/lib/TransitionBuilder.vala @@ -6,11 +6,30 @@ */ public class Gala.TransitionBuilder : Object { + public enum ReducedMotionBehavior { + /** + * Adding the transition will be ignored i.e. a no op. + * If you don't want the transition to run but still the final + * value to be applied, use {@link APPLY} instead. + */ + IGNORE, + /** + * The transition will not animate but the final value + * will be applied immediately. + */ + APPLY, + /** + * The transition will run normally. + */ + RUN, + } + public Clutter.Actor actor { private get; construct; } public uint duration { private get; construct; } public Clutter.AnimationMode animation_mode { private get; construct; } private Clutter.TransitionGroup group; + private bool transition_added = false; public TransitionBuilder (Clutter.Actor actor, uint duration, Clutter.AnimationMode animation_mode) { Object (actor: actor, duration: duration, animation_mode: animation_mode); @@ -23,14 +42,22 @@ public class Gala.TransitionBuilder : Object { }; } - public void add_property (string name, Value to) { + public void add_property (string name, Value to, ReducedMotionBehavior reduced_motion_behavior) { Value from = {}; actor.get_property (name, ref from); - add_property_with_from (name, from, to); + add_property_with_from (name, from, to, reduced_motion_behavior); } - public void add_property_with_from (string name, Value from, Value to) requires (from.type () == to.type ()) { - if (!Meta.Prefs.get_gnome_animations ()) { + public void add_property_with_from ( + string name, Value from, Value to, ReducedMotionBehavior reduced_motion_behavior + ) requires (from.type () == to.type ()) { + var reduce_motion = Utils.should_reduce_motion (); + + if (reduce_motion && reduced_motion_behavior == IGNORE) { + return; + } + + if (!Meta.Prefs.get_gnome_animations () || (reduce_motion && reduced_motion_behavior == APPLY)) { actor.set_property (name, to); return; } @@ -46,6 +73,8 @@ public class Gala.TransitionBuilder : Object { progress_mode = animation_mode, }; group.add_transition (property_transition); + + transition_added = true; } /** @@ -54,7 +83,7 @@ public class Gala.TransitionBuilder : Object { * the actor was destroyed or remove all transitions was called). */ public async bool run () { - if (!Meta.Prefs.get_gnome_animations ()) { + if (!transition_added) { return true; } diff --git a/lib/Utils.vala b/lib/Utils.vala index 20e04e927..36991d4bc 100644 --- a/lib/Utils.vala +++ b/lib/Utils.vala @@ -364,5 +364,9 @@ namespace Gala { return display.get_monitor_scale (monitor_index); } } + + public static bool should_reduce_motion () { + return Drawing.StyleManager.get_instance ().reduced_motion == REDUCE; + } } } diff --git a/src/Misc/DesktopIntegration.vala b/src/Misc/DesktopIntegration.vala index 8bd28808c..73b6a180b 100644 --- a/src/Misc/DesktopIntegration.vala +++ b/src/Misc/DesktopIntegration.vala @@ -201,9 +201,9 @@ public class Gala.DesktopIntegration : GLib.Object { unowned var display = wm.get_display (); #if HAS_MUTTER49 - if (window.is_maximized () || !Meta.Prefs.get_gnome_animations ()) { + if (window.is_maximized () || !Meta.Prefs.get_gnome_animations () || Utils.should_reduce_motion ()) { #else - if (window.get_maximized () == BOTH || !Meta.Prefs.get_gnome_animations ()) { + if (window.get_maximized () == BOTH || !Meta.Prefs.get_gnome_animations () || Utils.should_reduce_motion ()) { #endif // If user has "Flash screen" enabled, make sure to respect it when shake animation is not played InternalUtils.bell_notify (display); diff --git a/src/Misc/NotificationStack.vala b/src/Misc/NotificationStack.vala index 20e3ba1a1..6eb8c5f59 100644 --- a/src/Misc/NotificationStack.vala +++ b/src/Misc/NotificationStack.vala @@ -61,7 +61,7 @@ public class Gala.NotificationStack : Object { var window_rect = window.get_frame_rect (); window.stick (); - if (Meta.Prefs.get_gnome_animations ()) { + if (Meta.Prefs.get_gnome_animations () && !Utils.should_reduce_motion ()) { // Don't flicker at the beginning of the animation notification.opacity = 0; notification.rotation_angle_x = 90; @@ -134,7 +134,7 @@ public class Gala.NotificationStack : Object { continue; } - if (Meta.Prefs.get_gnome_animations ()) { + if (Meta.Prefs.get_gnome_animations () && !Utils.should_reduce_motion ()) { actor.save_easing_state (); actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_BACK); actor.set_easing_duration (200); @@ -143,7 +143,7 @@ public class Gala.NotificationStack : Object { move_window (actor, -1, (int)y); - if (Meta.Prefs.get_gnome_animations ()) { + if (Meta.Prefs.get_gnome_animations () && !Utils.should_reduce_motion ()) { actor.restore_easing_state (); } @@ -168,8 +168,8 @@ public class Gala.NotificationStack : Object { update_positions (); var builder = new TransitionBuilder (notification, AnimationDuration.CLOSE, EASE_IN_QUAD); - builder.add_property ("opacity", 0u); - builder.add_property ("x", notification.x + stack_width); + builder.add_property ("opacity", 0u, RUN); + builder.add_property ("x", notification.x + stack_width, IGNORE); return yield builder.run (); } diff --git a/src/Misc/Zoom.vala b/src/Misc/Zoom.vala index fa1bad3fc..d02e20357 100644 --- a/src/Misc/Zoom.vala +++ b/src/Misc/Zoom.vala @@ -97,7 +97,7 @@ public class Gala.Zoom : Object, GestureTarget, RootTarget { case UPDATE: var target_zoom = (float) progress * 10 + 1; - if (!Meta.Prefs.get_gnome_animations ()) { + if (!Meta.Prefs.get_gnome_animations () || Utils.should_reduce_motion ()) { var delta = target_zoom - current_zoom; if (delta.abs () >= SHORTCUT_DELTA - float.EPSILON) { target_zoom = current_zoom + ((delta > 0) ? SHORTCUT_DELTA : -SHORTCUT_DELTA); diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 5543b36ae..07d2e9429 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -876,7 +876,7 @@ namespace Gala { // must wait for size_changed to get updated frame_rect // as which_change is not passed to size_changed, save it as instance variable public override void size_change (Meta.WindowActor actor, Meta.SizeChange which_change, Mtk.Rectangle old_frame_rect, Mtk.Rectangle old_buffer_rect) { - if (actor.meta_window.window_type != NORMAL || !Meta.Prefs.get_gnome_animations ()) { + if (actor.meta_window.window_type != NORMAL || !Meta.Prefs.get_gnome_animations () || Utils.should_reduce_motion ()) { size_change_completed (actor); return; } @@ -971,10 +971,10 @@ namespace Gala { actor.set_pivot_point (0.0f, 0.0f); var actor_transition_builder = new TransitionBuilder (actor, AnimationDuration.SNAP, EASE_IN_OUT_QUAD); - actor_transition_builder.add_property_with_from ("scale-x", actor_scale_x, 1.0); - actor_transition_builder.add_property_with_from ("scale-y", actor_scale_y, 1.0); - actor_transition_builder.add_property_with_from ("translation-x", translation_x, 0.0f); - actor_transition_builder.add_property_with_from ("translation-y", translation_y, 0.0f); + actor_transition_builder.add_property_with_from ("scale-x", actor_scale_x, 1.0, APPLY); + actor_transition_builder.add_property_with_from ("scale-y", actor_scale_y, 1.0, APPLY); + actor_transition_builder.add_property_with_from ("translation-x", translation_x, 0.0f, APPLY); + actor_transition_builder.add_property_with_from ("translation-y", translation_y, 0.0f, APPLY); yield actor_transition_builder.run (); @@ -1011,16 +1011,16 @@ namespace Gala { (actor.y - icon.y) / (icon.height - actor.height) ); - builder.add_property ("scale-x", (double) (icon.width / actor.width)); - builder.add_property ("scale-y", (double) (icon.height / actor.height)); + builder.add_property ("scale-x", (double) (icon.width / actor.width), IGNORE); + builder.add_property ("scale-y", (double) (icon.height / actor.height), IGNORE); } else { actor.set_pivot_point (0.5f, 1.0f); - builder.add_property ("scale-x", 0.0); - builder.add_property ("scale-y", 0.0); + builder.add_property ("scale-x", 0.0, IGNORE); + builder.add_property ("scale-y", 0.0, IGNORE); } - builder.add_property ("opacity", 0u); + builder.add_property ("opacity", 0u, RUN); yield builder.run (); @@ -1048,9 +1048,9 @@ namespace Gala { actor.set_pivot_point (0.5f, 1.0f); var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_OUT_EXPO); - builder.add_property_with_from ("scale-x", 0.01, 1.0); - builder.add_property_with_from ("scale-y", 0.1, 1.0); - builder.add_property_with_from ("opacity", 0U, 255U); + builder.add_property_with_from ("scale-x", 0.01, 1.0, APPLY); + builder.add_property_with_from ("scale-y", 0.1, 1.0, APPLY); + builder.add_property_with_from ("opacity", 0U, 255U, RUN); yield builder.run (); @@ -1091,9 +1091,9 @@ namespace Gala { actor.set_pivot_point (0.5f, 1.0f); var builder = new TransitionBuilder (actor, AnimationDuration.HIDE, EASE_OUT_EXPO); - builder.add_property_with_from ("scale-x", 0.01, 1.0); - builder.add_property_with_from ("scale-y", 0.1, 1.0); - builder.add_property_with_from ("opacity", 0U, 255U); + builder.add_property_with_from ("scale-x", 0.01, 1.0, APPLY); + builder.add_property_with_from ("scale-y", 0.1, 1.0, APPLY); + builder.add_property_with_from ("opacity", 0U, 255U, RUN); yield builder.run (); break; @@ -1103,9 +1103,9 @@ namespace Gala { actor.set_pivot_point (0.5f, 0.5f); var builder = new TransitionBuilder (actor, 200, EASE_OUT_QUAD); - builder.add_property_with_from ("scale-x", 1.05, 1.0); - builder.add_property_with_from ("scale-y", 1.05, 1.0); - builder.add_property_with_from ("opacity", 0U, 255U); + builder.add_property_with_from ("scale-x", 1.05, 1.0, APPLY); + builder.add_property_with_from ("scale-y", 1.05, 1.0, APPLY); + builder.add_property_with_from ("opacity", 0U, 255U, RUN); yield builder.run (); break; @@ -1134,9 +1134,9 @@ namespace Gala { actor.show (); var builder = new TransitionBuilder (actor, AnimationDuration.CLOSE, LINEAR); - builder.add_property ("scale-x", 0.8); - builder.add_property ("scale-y", 0.8); - builder.add_property ("opacity", 0U); + builder.add_property ("scale-x", 0.8, IGNORE); + builder.add_property ("scale-y", 0.8, IGNORE); + builder.add_property ("opacity", 0U, RUN); yield builder.run (); Utils.clear_window_cache (window); @@ -1147,9 +1147,9 @@ namespace Gala { actor.set_pivot_point (0.5f, 0.5f); var builder = new TransitionBuilder (actor, 150, EASE_OUT_QUAD); - builder.add_property ("scale-x", 1.05); - builder.add_property ("scale-y", 1.05); - builder.add_property ("opacity", 0U); + builder.add_property ("scale-x", 1.05, IGNORE); + builder.add_property ("scale-y", 1.05, IGNORE); + builder.add_property ("opacity", 0U, RUN); yield builder.run (); break;