diff --git a/README.md b/README.md index c21639d..e956ac1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,10 @@ protection against OLED burn-in. desktop background. Frames are decoded with GStreamer and rendered directly by GNOME Shell's own Clutter stage (no external, incompatible Clutter build involved). Playback can automatically pause on battery - power or while a window is fullscreen. + power or while a window is fullscreen. Automatic rotation is + automatically suspended while a live wallpaper is actually on screen + (and resumes afterward), since changing the static wallpaper would + otherwise repaint over the video. - **OLED burn-in protection** — three independent, individually toggled techniques: - *Pixel shifting*: nudges the background a few pixels on a slow drift diff --git a/extension.js b/extension.js index c1edf0e..54fa334 100644 --- a/extension.js +++ b/extension.js @@ -14,7 +14,19 @@ export default class BenthicBloomExtension extends Extension { this._logger = new Logger(this._settings, this.metadata.name); this._rotationManager = new RotationManager(this._settings, this._logger); - this._liveWallpaperManager = new LiveWallpaperManager(this._settings, this._logger); + this._liveWallpaperManager = new LiveWallpaperManager(this._settings, this._logger, { + // Rotation changes the static picture-uri GSettings key, which + // makes the shell repaint its own background actor on top of + // the live wallpaper's. Suspend rotation while a live + // wallpaper is actually on screen, independent of any + // user-initiated pause. + onActiveChanged: active => { + if (active) + this._rotationManager.suspend(); + else + this._rotationManager.unsuspend(); + }, + }); this._oledProtectionManager = new OledProtectionManager(this._settings, this._logger, { forceNextWallpaper: maxSeconds => { if (this._rotationManager.secondsSinceLastChange >= maxSeconds) { diff --git a/lib/indicator.js b/lib/indicator.js index de3fadb..1396b82 100644 --- a/lib/indicator.js +++ b/lib/indicator.js @@ -28,6 +28,7 @@ class BenthicBloomIndicator extends PanelMenu.Button { nextItem.connect('activate', () => { rotationManager.next().catch(e => console.error(`[BenthicBloom] ${e.message ?? e}`)); }); + nextItem.setSensitive(!settings.get_boolean(SettingsKey.LIVE_WALLPAPER_ENABLED)); this.menu.addMenuItem(nextItem); const liveToggle = new PopupMenu.PopupSwitchMenuItem( @@ -58,7 +59,9 @@ class BenthicBloomIndicator extends PanelMenu.Button { rotationToggle.setToggleState(settings.get_boolean(SettingsKey.ROTATION_ENABLED)); }), settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_ENABLED}`, () => { - liveToggle.setToggleState(settings.get_boolean(SettingsKey.LIVE_WALLPAPER_ENABLED)); + const enabled = settings.get_boolean(SettingsKey.LIVE_WALLPAPER_ENABLED); + liveToggle.setToggleState(enabled); + nextItem.setSensitive(!enabled); }), settings.connect(`changed::${SettingsKey.OLED_PROTECTION_ENABLED}`, () => { oledToggle.setToggleState(settings.get_boolean(SettingsKey.OLED_PROTECTION_ENABLED)); diff --git a/lib/liveWallpaper.js b/lib/liveWallpaper.js index 96cf267..335c67c 100644 --- a/lib/liveWallpaper.js +++ b/lib/liveWallpaper.js @@ -21,9 +21,10 @@ import {loadGstreamerModules, GSTREAMER_INSTALL_HINT} from './gstreamerAvailabil * that belongs to gnome-shell itself. */ export class LiveWallpaperManager { - constructor(settings, logger) { + constructor(settings, logger, {onActiveChanged} = {}) { this._settings = settings; this._logger = logger; + this._onActiveChanged = onActiveChanged ?? (() => {}); this._available = false; this._active = false; @@ -57,6 +58,13 @@ export class LiveWallpaperManager { return this._active; } + _setActive(active) { + if (this._active === active) + return; + this._active = active; + this._onActiveChanged(active); + } + async enable() { await this._loadGstreamer(); @@ -149,7 +157,7 @@ export class LiveWallpaperManager { this._applyMute(); this._playbin.set_state(Gst.State.PLAYING); - this._active = true; + this._setActive(true); this._paused = false; this._connectPowerWatches(); this._logger.debug(`Live wallpaper started: ${path}`); @@ -336,7 +344,7 @@ export class LiveWallpaperManager { this._videoWidth = 0; this._videoHeight = 0; - this._active = false; + this._setActive(false); this._paused = false; } } diff --git a/lib/rotationManager.js b/lib/rotationManager.js index b61ed7f..ec2b2e1 100644 --- a/lib/rotationManager.js +++ b/lib/rotationManager.js @@ -31,6 +31,7 @@ export class RotationManager { this._timeoutId = 0; this._settingsSignals = []; this._paused = false; + this._suspended = false; this._lastChangeTime = GLib.get_monotonic_time(); this._currentPath = null; this._transitionOverlays = []; @@ -89,7 +90,7 @@ export class RotationManager { _restartTimer() { this._clearTimer(); - if (this._paused || !this._settings.get_boolean(SettingsKey.ROTATION_ENABLED)) + if (this._paused || this._suspended || !this._settings.get_boolean(SettingsKey.ROTATION_ENABLED)) return; const interval = Math.max( @@ -118,7 +119,30 @@ export class RotationManager { this._restartTimer(); } + /** + * Distinct from user-initiated pause(): called while a live wallpaper + * is actually covering the desktop, so rotation doesn't keep changing + * a static image nobody can see (which also churns the shell's own + * background actor on top of the live wallpaper's). Resuming restores + * whatever the user's own pause() state was, rather than forcing + * rotation back on. + */ + suspend() { + this._suspended = true; + this._clearTimer(); + } + + unsuspend() { + this._suspended = false; + this._restartTimer(); + } + async next({forced = false} = {}) { + if (this._suspended) { + this._logger.debug('Rotation suspended while live wallpaper is active'); + return; + } + if (this._images.length === 0) await this._reloadImages(); @@ -144,7 +168,7 @@ export class RotationManager { } async previous() { - if (this._images.length === 0) + if (this._suspended || this._images.length === 0) return; const mode = this._settings.get_string(SettingsKey.ROTATION_MODE);