more oled features and system freeze fixes
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
<g fill="none" stroke="#2e3436" stroke-width="1.3" stroke-linecap="round">
|
<g fill="none" stroke="#2e3436" stroke-width="1.6" stroke-linecap="round">
|
||||||
<line x1="3.5" y1="2" x2="3.5" y2="14"/>
|
<line x1="3.5" y1="2" x2="3.5" y2="14"/>
|
||||||
<line x1="8" y1="2" x2="8" y2="14"/>
|
<line x1="8" y1="2" x2="8" y2="14"/>
|
||||||
<line x1="12.5" y1="2" x2="12.5" y2="14"/>
|
<line x1="12.5" y1="2" x2="12.5" y2="14"/>
|
||||||
</g>
|
</g>
|
||||||
<circle cx="3.5" cy="5.5" r="1.7" fill="#2e3436"/>
|
<circle cx="3.5" cy="5.3" r="2" fill="#2e3436"/>
|
||||||
<circle cx="8" cy="10.5" r="1.7" fill="#2e3436"/>
|
<circle cx="8" cy="10.7" r="2" fill="#2e3436"/>
|
||||||
<circle cx="12.5" cy="7" r="1.7" fill="#2e3436"/>
|
<circle cx="12.5" cy="7" r="2" fill="#2e3436"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 459 B |
@@ -1,4 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
<rect x="1.5" y="3" width="13" height="10" rx="1.8" fill="none" stroke="#2e3436" stroke-width="1.3"/>
|
<rect x="1.3" y="2.8" width="13.4" height="10.4" rx="2" fill="none" stroke="#2e3436" stroke-width="1.5"/>
|
||||||
<path d="M 6.6 5.8 L 6.6 10.2 L 10.4 8 Z" fill="#2e3436"/>
|
<path d="M 6.2 5.3 L 6.2 10.7 L 10.7 8 Z" fill="#2e3436"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 260 B |
@@ -1,6 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
<path d="M 3.11 6.22 A 5.2 5.2 0 0 1 13.12 7.10" fill="none" stroke="#2e3436" stroke-width="1.4" stroke-linecap="round"/>
|
<path d="M 12.8 5.6 A 5.3 5.3 0 1 1 11.4 2.9" fill="none" stroke="#2e3436" stroke-width="1.6" stroke-linecap="round"/>
|
||||||
<path d="M 12.89 9.78 A 5.2 5.2 0 0 1 2.88 8.90" fill="none" stroke="#2e3436" stroke-width="1.4" stroke-linecap="round"/>
|
<path d="M 13.9,2.6 13.5,6.4 10.2,4.5 Z" fill="#2e3436"/>
|
||||||
<path d="M 13.52,9.36 11.38,7.00 14.73,6.41 Z" fill="#2e3436"/>
|
|
||||||
<path d="M 2.48,6.64 4.62,9.00 1.27,9.59 Z" fill="#2e3436"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 272 B |
+169
-9
@@ -1,6 +1,7 @@
|
|||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import Graphene from 'gi://Graphene';
|
||||||
import St from 'gi://St';
|
import St from 'gi://St';
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
@@ -89,6 +90,9 @@ export class LiveWallpaperManager {
|
|||||||
this._noFrameWatchdogId = 0;
|
this._noFrameWatchdogId = 0;
|
||||||
this._imageStrategyIndex = 0;
|
this._imageStrategyIndex = 0;
|
||||||
this._frameErrorCount = 0;
|
this._frameErrorCount = 0;
|
||||||
|
|
||||||
|
this._transitionOverlay = null;
|
||||||
|
this._fadeInPending = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAvailable() {
|
get isAvailable() {
|
||||||
@@ -113,6 +117,7 @@ export class LiveWallpaperManager {
|
|||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_ENABLED}`, () => this._sync()),
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_ENABLED}`, () => this._sync()),
|
||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PATH}`, () => this._sync()),
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PATH}`, () => this._sync()),
|
||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_MUTED}`, () => this._applyMute()),
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_MUTED}`, () => this._applyMute()),
|
||||||
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_DISPLAY_MODE}`, () => this._applyDisplayMode()),
|
||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE}`, () => this._applyPlaybackRate()),
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE}`, () => this._applyPlaybackRate()),
|
||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PAUSE_ON_BATTERY}`, () => this._updatePauseState()),
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PAUSE_ON_BATTERY}`, () => this._updatePauseState()),
|
||||||
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PAUSE_WHEN_FULLSCREEN}`, () => this._updatePauseState())
|
this._settings.connect(`changed::${SettingsKey.LIVE_WALLPAPER_PAUSE_WHEN_FULLSCREEN}`, () => this._updatePauseState())
|
||||||
@@ -134,6 +139,7 @@ export class LiveWallpaperManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._stop();
|
this._stop();
|
||||||
|
this._clearTransition();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadGstreamer() {
|
async _loadGstreamer() {
|
||||||
@@ -157,12 +163,27 @@ export class LiveWallpaperManager {
|
|||||||
if (shouldRun)
|
if (shouldRun)
|
||||||
this._active ? this._restart() : this._start();
|
this._active ? this._restart() : this._start();
|
||||||
else if (this._active)
|
else if (this._active)
|
||||||
this._stop();
|
this._stop({animate: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switching to a different file while already active. If transitions
|
||||||
|
* are on, the outgoing actor (already showing the old file's last
|
||||||
|
* frame) is detached before _stop() would destroy it, so it can be
|
||||||
|
* kept on screen and eased away by _playTransition() after _start()
|
||||||
|
* has the new one playing underneath — a crossfade between the two.
|
||||||
|
*/
|
||||||
_restart() {
|
_restart() {
|
||||||
|
const transitionsOn = this._settings.get_boolean(SettingsKey.TRANSITION_ENABLED);
|
||||||
|
const outgoingActor = transitionsOn ? this._actor : null;
|
||||||
|
if (outgoingActor)
|
||||||
|
this._actor = null;
|
||||||
|
|
||||||
this._stop();
|
this._stop();
|
||||||
this._start();
|
this._start();
|
||||||
|
|
||||||
|
if (outgoingActor)
|
||||||
|
this._playTransition(outgoingActor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -233,11 +254,15 @@ export class LiveWallpaperManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this._actor = new Clutter.Actor({
|
this._actor = new Clutter.Actor({
|
||||||
content_gravity: Clutter.ContentGravity.RESIZE_ASPECT,
|
|
||||||
reactive: false,
|
reactive: false,
|
||||||
|
pivot_point: new Graphene.Point({x: 0.5, y: 0.5}),
|
||||||
});
|
});
|
||||||
Main.layoutManager._backgroundGroup.add_child(this._actor);
|
Main.layoutManager._backgroundGroup.add_child(this._actor);
|
||||||
this._layoutActor();
|
this._applyDisplayMode();
|
||||||
|
|
||||||
|
this._fadeInPending = this._settings.get_boolean(SettingsKey.TRANSITION_ENABLED);
|
||||||
|
if (this._fadeInPending)
|
||||||
|
this._actor.opacity = 0;
|
||||||
|
|
||||||
const sinkBin = this._buildSinkBin();
|
const sinkBin = this._buildSinkBin();
|
||||||
|
|
||||||
@@ -312,9 +337,17 @@ export class LiveWallpaperManager {
|
|||||||
try {
|
try {
|
||||||
this._updateFrame(mapInfo.data, width, height);
|
this._updateFrame(mapInfo.data, width, height);
|
||||||
this._frameCount++;
|
this._frameCount++;
|
||||||
if (this._frameCount === 1)
|
if (this._frameCount === 1) {
|
||||||
this._logger.debug(`Live wallpaper: first frame received (${width}x${height})`);
|
this._logger.debug(`Live wallpaper: first frame received (${width}x${height})`);
|
||||||
else if (this._frameCount % 120 === 0)
|
if (this._fadeInPending) {
|
||||||
|
this._fadeInPending = false;
|
||||||
|
this._actor.ease({
|
||||||
|
opacity: 255,
|
||||||
|
duration: this._settings.get_uint(SettingsKey.TRANSITION_DURATION_MS),
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (this._frameCount % 120 === 0)
|
||||||
this._logger.debug(`Live wallpaper: ${this._frameCount} frames rendered so far`);
|
this._logger.debug(`Live wallpaper: ${this._frameCount} frames rendered so far`);
|
||||||
} finally {
|
} finally {
|
||||||
buffer.unmap(mapInfo);
|
buffer.unmap(mapInfo);
|
||||||
@@ -346,6 +379,7 @@ export class LiveWallpaperManager {
|
|||||||
this._image = strategy.create(width, height);
|
this._image = strategy.create(width, height);
|
||||||
this._videoWidth = width;
|
this._videoWidth = width;
|
||||||
this._videoHeight = height;
|
this._videoHeight = height;
|
||||||
|
this._layoutActor();
|
||||||
}
|
}
|
||||||
strategy.upload(this._image, this._Cogl, data, width, height);
|
strategy.upload(this._image, this._Cogl, data, width, height);
|
||||||
this._actor.set_content(this._image);
|
this._actor.set_content(this._image);
|
||||||
@@ -414,11 +448,61 @@ export class LiveWallpaperManager {
|
|||||||
Gst.SeekType.SET, position, Gst.SeekType.NONE, -1);
|
Gst.SeekType.SET, position, Gst.SeekType.NONE, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clutter's ContentGravity enum covers fit/stretch/center natively, but
|
||||||
|
* has no "cover" (scale-and-crop) value, so "fill" mode is done by hand:
|
||||||
|
* the actor is overscanned to the smallest size that fully covers the
|
||||||
|
* stage while preserving the video's aspect ratio, centered, and then
|
||||||
|
* clipped back down to the stage bounds.
|
||||||
|
*/
|
||||||
|
_applyDisplayMode() {
|
||||||
|
if (!this._actor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const mode = this._settings.get_string(SettingsKey.LIVE_WALLPAPER_DISPLAY_MODE);
|
||||||
|
switch (mode) {
|
||||||
|
case 'stretch':
|
||||||
|
this._actor.content_gravity = Clutter.ContentGravity.RESIZE_FILL;
|
||||||
|
break;
|
||||||
|
case 'center':
|
||||||
|
this._actor.content_gravity = Clutter.ContentGravity.CENTER;
|
||||||
|
break;
|
||||||
|
case 'fill':
|
||||||
|
this._actor.content_gravity = Clutter.ContentGravity.RESIZE_FILL;
|
||||||
|
break;
|
||||||
|
case 'fit':
|
||||||
|
default:
|
||||||
|
this._actor.content_gravity = Clutter.ContentGravity.RESIZE_ASPECT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._layoutActor();
|
||||||
|
}
|
||||||
|
|
||||||
_layoutActor() {
|
_layoutActor() {
|
||||||
if (!this._actor)
|
if (!this._actor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const stageWidth = global.stage.width;
|
||||||
|
const stageHeight = global.stage.height;
|
||||||
|
const mode = this._settings.get_string(SettingsKey.LIVE_WALLPAPER_DISPLAY_MODE);
|
||||||
|
|
||||||
|
if (mode === 'fill' && this._videoWidth > 0 && this._videoHeight > 0) {
|
||||||
|
const scale = Math.max(stageWidth / this._videoWidth, stageHeight / this._videoHeight);
|
||||||
|
const scaledWidth = this._videoWidth * scale;
|
||||||
|
const scaledHeight = this._videoHeight * scale;
|
||||||
|
const offsetX = (stageWidth - scaledWidth) / 2;
|
||||||
|
const offsetY = (stageHeight - scaledHeight) / 2;
|
||||||
|
|
||||||
|
this._actor.set_position(offsetX, offsetY);
|
||||||
|
this._actor.set_size(scaledWidth, scaledHeight);
|
||||||
|
this._actor.set_clip(-offsetX, -offsetY, stageWidth, stageHeight);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._actor.remove_clip();
|
||||||
this._actor.set_position(0, 0);
|
this._actor.set_position(0, 0);
|
||||||
this._actor.set_size(global.stage.width, global.stage.height);
|
this._actor.set_size(stageWidth, stageHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectPowerWatches() {
|
_connectPowerWatches() {
|
||||||
@@ -502,7 +586,15 @@ export class LiveWallpaperManager {
|
|||||||
this._playbin.set_state(paused ? this._Gst.State.PAUSED : this._Gst.State.PLAYING);
|
this._playbin.set_state(paused ? this._Gst.State.PAUSED : this._Gst.State.PLAYING);
|
||||||
}
|
}
|
||||||
|
|
||||||
_stop() {
|
/**
|
||||||
|
* `animate: true` is for a user-visible stop (toggling the setting off,
|
||||||
|
* or the path being cleared) — the last frame stays on screen and eases
|
||||||
|
* away per the shared Transitions setting, revealing the static
|
||||||
|
* wallpaper beneath. Left off for restarts (which hand the actor to
|
||||||
|
* _playTransition() themselves, see _restart()), errors, and
|
||||||
|
* disable(), where an immediate teardown is what's wanted.
|
||||||
|
*/
|
||||||
|
_stop({animate = false} = {}) {
|
||||||
this._disconnectPowerWatches();
|
this._disconnectPowerWatches();
|
||||||
|
|
||||||
if (this._noFrameWatchdogId) {
|
if (this._noFrameWatchdogId) {
|
||||||
@@ -536,13 +628,81 @@ export class LiveWallpaperManager {
|
|||||||
this._appsink = null;
|
this._appsink = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._actor?.destroy();
|
if (animate && this._actor && this._settings.get_boolean(SettingsKey.TRANSITION_ENABLED)) {
|
||||||
this._actor = null;
|
this._playTransition(this._actor);
|
||||||
|
this._actor = null;
|
||||||
|
} else {
|
||||||
|
this._actor?.destroy();
|
||||||
|
this._actor = null;
|
||||||
|
}
|
||||||
this._image = null;
|
this._image = null;
|
||||||
this._videoWidth = 0;
|
this._videoWidth = 0;
|
||||||
this._videoHeight = 0;
|
this._videoHeight = 0;
|
||||||
|
this._fadeInPending = false;
|
||||||
|
|
||||||
this._setActive(false);
|
this._setActive(false);
|
||||||
this._paused = false;
|
this._paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eases `overlay` — an actor that was just detached from active duty,
|
||||||
|
* still showing its last frame — off screen per the Transitions
|
||||||
|
* setting (fade/slide/zoom/random, same styles and duration as static
|
||||||
|
* rotation), then destroys it. Raising it above everything else first
|
||||||
|
* means this works whether it's revealing a freshly started live
|
||||||
|
* wallpaper actor underneath (switch) or the static desktop background
|
||||||
|
* (stop) — both are already in place by the time this runs.
|
||||||
|
*/
|
||||||
|
_playTransition(overlay) {
|
||||||
|
this._transitionOverlay?.destroy();
|
||||||
|
this._transitionOverlay = overlay;
|
||||||
|
|
||||||
|
Main.layoutManager._backgroundGroup.set_child_above_sibling(overlay, null);
|
||||||
|
|
||||||
|
const durationMs = this._settings.get_uint(SettingsKey.TRANSITION_DURATION_MS);
|
||||||
|
let style = this._settings.get_string(SettingsKey.TRANSITION_STYLE);
|
||||||
|
if (style === 'random')
|
||||||
|
style = ['fade', 'slide', 'zoom'][Math.floor(Math.random() * 3)];
|
||||||
|
|
||||||
|
const onComplete = () => {
|
||||||
|
if (this._transitionOverlay === overlay)
|
||||||
|
this._transitionOverlay = null;
|
||||||
|
overlay.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (style) {
|
||||||
|
case 'slide':
|
||||||
|
overlay.ease({
|
||||||
|
x: overlay.x - overlay.width,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'zoom':
|
||||||
|
overlay.ease({
|
||||||
|
scale_x: 1.15,
|
||||||
|
scale_y: 1.15,
|
||||||
|
opacity: 0,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_IN_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'fade':
|
||||||
|
default:
|
||||||
|
overlay.ease({
|
||||||
|
opacity: 0,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearTransition() {
|
||||||
|
this._transitionOverlay?.destroy();
|
||||||
|
this._transitionOverlay = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
import Meta from 'gi://Meta';
|
import Meta from 'gi://Meta';
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import Graphene from 'gi://Graphene';
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
import {SettingsKey} from './settingsKeys.js';
|
import {SettingsKey} from './settingsKeys.js';
|
||||||
@@ -13,6 +14,11 @@ const SHIFT_PATTERN = [
|
|||||||
|
|
||||||
const FORCE_ROTATION_CHECK_SECONDS = 60;
|
const FORCE_ROTATION_CHECK_SECONDS = 60;
|
||||||
|
|
||||||
|
// Extra margin, in pixels, added on top of the configured shift amount when
|
||||||
|
// overscanning background actors — rounding/timing slack so a step never
|
||||||
|
// exposes a sliver of empty stage at the trailing edge.
|
||||||
|
const OVERSCAN_SAFETY_PX = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces OLED burn-in risk through three independent techniques:
|
* Reduces OLED burn-in risk through three independent techniques:
|
||||||
* - pixel shifting: nudges the background actors a few px on a slow cycle
|
* - pixel shifting: nudges the background actors a few px on a slow cycle
|
||||||
@@ -105,6 +111,7 @@ export class OledProtectionManager {
|
|||||||
const [dx, dy] = SHIFT_PATTERN[this._shiftStep];
|
const [dx, dy] = SHIFT_PATTERN[this._shiftStep];
|
||||||
|
|
||||||
for (const actor of this._backgroundActors()) {
|
for (const actor of this._backgroundActors()) {
|
||||||
|
this._ensureOverscan(actor, amount);
|
||||||
actor.ease({
|
actor.ease({
|
||||||
translation_x: dx * amount,
|
translation_x: dx * amount,
|
||||||
translation_y: dy * amount,
|
translation_y: dy * amount,
|
||||||
@@ -114,12 +121,36 @@ export class OledProtectionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background actors (the real desktop background, and our own live
|
||||||
|
* wallpaper actor in most display modes) are sized to exactly cover
|
||||||
|
* the stage with no slack, so nudging one by a few pixels for burn-in
|
||||||
|
* protection would drag its edge inward and expose bare stage on the
|
||||||
|
* side it moved away from. Scaling the actor up slightly around its
|
||||||
|
* center — by just enough margin to cover the configured shift amount
|
||||||
|
* — keeps every edge overscanned past the stage bounds no matter which
|
||||||
|
* step of the shift pattern is active.
|
||||||
|
*/
|
||||||
|
_ensureOverscan(actor, amount) {
|
||||||
|
const width = actor.width;
|
||||||
|
const height = actor.height;
|
||||||
|
if (width <= 0 || height <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const margin = amount + OVERSCAN_SAFETY_PX;
|
||||||
|
actor.pivot_point = new Graphene.Point({x: 0.5, y: 0.5});
|
||||||
|
actor.scale_x = 1 + (2 * margin) / width;
|
||||||
|
actor.scale_y = 1 + (2 * margin) / height;
|
||||||
|
}
|
||||||
|
|
||||||
_resetShift() {
|
_resetShift() {
|
||||||
this._shiftStep = 0;
|
this._shiftStep = 0;
|
||||||
for (const actor of this._backgroundActors()) {
|
for (const actor of this._backgroundActors()) {
|
||||||
actor.ease({
|
actor.ease({
|
||||||
translation_x: 0,
|
translation_x: 0,
|
||||||
translation_y: 0,
|
translation_y: 0,
|
||||||
|
scale_x: 1,
|
||||||
|
scale_y: 1,
|
||||||
duration: 500,
|
duration: 500,
|
||||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
});
|
});
|
||||||
|
|||||||
+48
-16
@@ -2,6 +2,7 @@ import GLib from 'gi://GLib';
|
|||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import St from 'gi://St';
|
import St from 'gi://St';
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import Graphene from 'gi://Graphene';
|
||||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||||
|
|
||||||
import {SettingsKey} from './settingsKeys.js';
|
import {SettingsKey} from './settingsKeys.js';
|
||||||
@@ -40,6 +41,7 @@ export class RotationManager {
|
|||||||
enable() {
|
enable() {
|
||||||
this._settingsSignals.push(
|
this._settingsSignals.push(
|
||||||
this._settings.connect(`changed::${SettingsKey.WALLPAPER_FOLDERS}`, () => this._reloadImages()),
|
this._settings.connect(`changed::${SettingsKey.WALLPAPER_FOLDERS}`, () => this._reloadImages()),
|
||||||
|
this._settings.connect(`changed::${SettingsKey.WALLPAPER_RESCAN_REQUEST}`, () => this._reloadImages()),
|
||||||
this._settings.connect(`changed::${SettingsKey.ROTATION_ENABLED}`, () => this._restartTimer()),
|
this._settings.connect(`changed::${SettingsKey.ROTATION_ENABLED}`, () => this._restartTimer()),
|
||||||
this._settings.connect(`changed::${SettingsKey.ROTATION_INTERVAL_SECONDS}`, () => this._restartTimer()),
|
this._settings.connect(`changed::${SettingsKey.ROTATION_INTERVAL_SECONDS}`, () => this._restartTimer()),
|
||||||
this._settings.connect(`changed::${SettingsKey.ROTATION_MODE}`, () => this._onModeChanged())
|
this._settings.connect(`changed::${SettingsKey.ROTATION_MODE}`, () => this._onModeChanged())
|
||||||
@@ -185,7 +187,7 @@ export class RotationManager {
|
|||||||
const uri = GLib.filename_to_uri(path, null);
|
const uri = GLib.filename_to_uri(path, null);
|
||||||
|
|
||||||
if (this._settings.get_boolean(SettingsKey.TRANSITION_ENABLED) && previousPath)
|
if (this._settings.get_boolean(SettingsKey.TRANSITION_ENABLED) && previousPath)
|
||||||
this._playCrossfade(previousPath);
|
this._playTransition(previousPath);
|
||||||
|
|
||||||
this._backgroundSettings.set_string('picture-uri', uri);
|
this._backgroundSettings.set_string('picture-uri', uri);
|
||||||
if (this._backgroundSettings.settings_schema.has_key('picture-uri-dark'))
|
if (this._backgroundSettings.settings_schema.has_key('picture-uri-dark'))
|
||||||
@@ -200,15 +202,19 @@ export class RotationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The real background actor doesn't crossfade on its own, so we paint the
|
* The real background actor doesn't animate changes on its own, so we
|
||||||
* *old* image full-screen on a throwaway overlay right as the new image
|
* paint the *old* image full-screen on a throwaway overlay right as the
|
||||||
* is set underneath, then fade the overlay out to reveal it.
|
* new image is set underneath, then animate the overlay away to reveal
|
||||||
|
* it. Which animation runs is picked per monitor by style.
|
||||||
*/
|
*/
|
||||||
_playCrossfade(previousPath) {
|
_playTransition(previousPath) {
|
||||||
this._clearTransitionOverlays();
|
this._clearTransitionOverlays();
|
||||||
|
|
||||||
const durationMs = this._settings.get_uint(SettingsKey.TRANSITION_DURATION_MS);
|
const durationMs = this._settings.get_uint(SettingsKey.TRANSITION_DURATION_MS);
|
||||||
const uri = GLib.filename_to_uri(previousPath, null).replace(/"/g, '%22');
|
const uri = GLib.filename_to_uri(previousPath, null).replace(/"/g, '%22');
|
||||||
|
let style = this._settings.get_string(SettingsKey.TRANSITION_STYLE);
|
||||||
|
if (style === 'random')
|
||||||
|
style = ['fade', 'slide', 'zoom'][Math.floor(Math.random() * 3)];
|
||||||
|
|
||||||
for (const monitor of Main.layoutManager.monitors) {
|
for (const monitor of Main.layoutManager.monitors) {
|
||||||
const overlay = new St.Widget({
|
const overlay = new St.Widget({
|
||||||
@@ -217,23 +223,49 @@ export class RotationManager {
|
|||||||
y: monitor.y,
|
y: monitor.y,
|
||||||
width: monitor.width,
|
width: monitor.width,
|
||||||
height: monitor.height,
|
height: monitor.height,
|
||||||
|
pivot_point: new Graphene.Point({x: 0.5, y: 0.5}),
|
||||||
style: `background-image: url("${uri}"); background-size: cover; background-position: center;`,
|
style: `background-image: url("${uri}"); background-size: cover; background-position: center;`,
|
||||||
opacity: 255,
|
opacity: 255,
|
||||||
});
|
});
|
||||||
Main.layoutManager._backgroundGroup.add_child(overlay);
|
Main.layoutManager._backgroundGroup.add_child(overlay);
|
||||||
this._transitionOverlays.push(overlay);
|
this._transitionOverlays.push(overlay);
|
||||||
|
|
||||||
overlay.ease({
|
const onComplete = () => {
|
||||||
opacity: 0,
|
overlay.destroy();
|
||||||
duration: durationMs,
|
const idx = this._transitionOverlays.indexOf(overlay);
|
||||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
if (idx >= 0)
|
||||||
onComplete: () => {
|
this._transitionOverlays.splice(idx, 1);
|
||||||
overlay.destroy();
|
};
|
||||||
const idx = this._transitionOverlays.indexOf(overlay);
|
|
||||||
if (idx >= 0)
|
switch (style) {
|
||||||
this._transitionOverlays.splice(idx, 1);
|
case 'slide':
|
||||||
},
|
overlay.ease({
|
||||||
});
|
x: monitor.x - monitor.width,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'zoom':
|
||||||
|
overlay.ease({
|
||||||
|
scale_x: 1.15,
|
||||||
|
scale_y: 1.15,
|
||||||
|
opacity: 0,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_IN_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'fade':
|
||||||
|
default:
|
||||||
|
overlay.ease({
|
||||||
|
opacity: 0,
|
||||||
|
duration: durationMs,
|
||||||
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||||
|
onComplete,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export const SettingsKey = Object.freeze({
|
|||||||
SHOW_INDICATOR: 'show-indicator',
|
SHOW_INDICATOR: 'show-indicator',
|
||||||
DEBUG_LOGGING: 'debug-logging',
|
DEBUG_LOGGING: 'debug-logging',
|
||||||
WALLPAPER_FOLDERS: 'wallpaper-folders',
|
WALLPAPER_FOLDERS: 'wallpaper-folders',
|
||||||
|
WALLPAPER_RESCAN_REQUEST: 'wallpaper-rescan-request',
|
||||||
APPLY_TO_LOCK_SCREEN: 'apply-to-lock-screen',
|
APPLY_TO_LOCK_SCREEN: 'apply-to-lock-screen',
|
||||||
|
|
||||||
ROTATION_ENABLED: 'rotation-enabled',
|
ROTATION_ENABLED: 'rotation-enabled',
|
||||||
@@ -10,10 +11,12 @@ export const SettingsKey = Object.freeze({
|
|||||||
CURRENT_INDEX: 'current-index',
|
CURRENT_INDEX: 'current-index',
|
||||||
TRANSITION_ENABLED: 'transition-enabled',
|
TRANSITION_ENABLED: 'transition-enabled',
|
||||||
TRANSITION_DURATION_MS: 'transition-duration-ms',
|
TRANSITION_DURATION_MS: 'transition-duration-ms',
|
||||||
|
TRANSITION_STYLE: 'transition-style',
|
||||||
|
|
||||||
LIVE_WALLPAPER_ENABLED: 'live-wallpaper-enabled',
|
LIVE_WALLPAPER_ENABLED: 'live-wallpaper-enabled',
|
||||||
LIVE_WALLPAPER_PATH: 'live-wallpaper-path',
|
LIVE_WALLPAPER_PATH: 'live-wallpaper-path',
|
||||||
LIVE_WALLPAPER_FOLDERS: 'live-wallpaper-folders',
|
LIVE_WALLPAPER_FOLDERS: 'live-wallpaper-folders',
|
||||||
|
LIVE_WALLPAPER_DISPLAY_MODE: 'live-wallpaper-display-mode',
|
||||||
LIVE_WALLPAPER_MUTED: 'live-wallpaper-muted',
|
LIVE_WALLPAPER_MUTED: 'live-wallpaper-muted',
|
||||||
LIVE_WALLPAPER_PLAYBACK_RATE: 'live-wallpaper-playback-rate',
|
LIVE_WALLPAPER_PLAYBACK_RATE: 'live-wallpaper-playback-rate',
|
||||||
LIVE_WALLPAPER_PAUSE_ON_BATTERY: 'live-wallpaper-pause-on-battery',
|
LIVE_WALLPAPER_PAUSE_ON_BATTERY: 'live-wallpaper-pause-on-battery',
|
||||||
|
|||||||
@@ -9,6 +9,25 @@ import {SettingsKey} from './lib/settingsKeys.js';
|
|||||||
import {checkGstreamerBaseAvailable, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js';
|
import {checkGstreamerBaseAvailable, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js';
|
||||||
import {listMediaFilesInFolders} from './lib/wallpaperSource.js';
|
import {listMediaFilesInFolders} from './lib/wallpaperSource.js';
|
||||||
|
|
||||||
|
const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
|
||||||
|
|
||||||
|
// GNOME's own picture-options values, exposed here so wallpaper fill mode
|
||||||
|
// can be controlled without inventing a parallel setting of our own.
|
||||||
|
// Built lazily (not at module top level) because gettext can only be
|
||||||
|
// called once the extension is registered, which hasn't happened yet
|
||||||
|
// while this module is still being evaluated on import.
|
||||||
|
function pictureOptions() {
|
||||||
|
return [
|
||||||
|
{value: 'zoom', label: _('Fill (Crop to Zoom)')},
|
||||||
|
{value: 'scaled', label: _('Fit (No Cropping)')},
|
||||||
|
{value: 'stretched', label: _('Stretch')},
|
||||||
|
{value: 'centered', label: _('Centered')},
|
||||||
|
{value: 'wallpaper', label: _('Tiled')},
|
||||||
|
{value: 'spanned', label: _('Spanned Across Monitors')},
|
||||||
|
{value: 'none', label: _('None (Background Color Only)')},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
export default class BenthicBloomPreferences extends ExtensionPreferences {
|
export default class BenthicBloomPreferences extends ExtensionPreferences {
|
||||||
async fillPreferencesWindow(window) {
|
async fillPreferencesWindow(window) {
|
||||||
const settings = this.getSettings();
|
const settings = this.getSettings();
|
||||||
@@ -19,8 +38,10 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// blank/broken-image placeholder in the page switcher.
|
// blank/broken-image placeholder in the page switcher.
|
||||||
Gtk.IconTheme.get_for_display(window.get_display()).add_search_path(`${this.path}/icons`);
|
Gtk.IconTheme.get_for_display(window.get_display()).add_search_path(`${this.path}/icons`);
|
||||||
|
|
||||||
|
const backgroundSettings = new Gio.Settings({schema_id: BACKGROUND_SCHEMA});
|
||||||
|
|
||||||
window.set_default_size(640, 720);
|
window.set_default_size(640, 720);
|
||||||
window.add(this._buildGeneralPage(settings));
|
window.add(this._buildGeneralPage(settings, backgroundSettings));
|
||||||
window.add(this._buildRotationPage(settings));
|
window.add(this._buildRotationPage(settings));
|
||||||
window.add(this._buildLiveWallpaperPage(settings, gstreamerError));
|
window.add(this._buildLiveWallpaperPage(settings, gstreamerError));
|
||||||
window.add(this._buildOledPage(settings));
|
window.add(this._buildOledPage(settings));
|
||||||
@@ -47,7 +68,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
|
|
||||||
// --- General page ------------------------------------------------
|
// --- General page ------------------------------------------------
|
||||||
|
|
||||||
_buildGeneralPage(settings) {
|
_buildGeneralPage(settings, backgroundSettings) {
|
||||||
const page = new Adw.PreferencesPage({title: _('General'), icon_name: 'bb-general-symbolic'});
|
const page = new Adw.PreferencesPage({title: _('General'), icon_name: 'bb-general-symbolic'});
|
||||||
|
|
||||||
const behaviorGroup = new Adw.PreferencesGroup({title: _('Behavior')});
|
const behaviorGroup = new Adw.PreferencesGroup({title: _('Behavior')});
|
||||||
@@ -62,6 +83,23 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
settings, SettingsKey.DEBUG_LOGGING,
|
settings, SettingsKey.DEBUG_LOGGING,
|
||||||
_('Debug Logging'), _('Print verbose diagnostics to the system log (journalctl -f)')));
|
_('Debug Logging'), _('Print verbose diagnostics to the system log (journalctl -f)')));
|
||||||
|
|
||||||
|
const displayGroup = new Adw.PreferencesGroup({
|
||||||
|
title: _('Display'),
|
||||||
|
description: _('How the wallpaper image is fitted to your screen, e.g. cropped to fill it'),
|
||||||
|
});
|
||||||
|
page.add(displayGroup);
|
||||||
|
const pictureOptionsList = pictureOptions();
|
||||||
|
const fillModeRow = new Adw.ComboRow({
|
||||||
|
title: _('Fill Mode'),
|
||||||
|
model: new Gtk.StringList({strings: pictureOptionsList.map(o => o.label)}),
|
||||||
|
});
|
||||||
|
const currentOption = backgroundSettings.get_string('picture-options');
|
||||||
|
fillModeRow.selected = Math.max(0, pictureOptionsList.findIndex(o => o.value === currentOption));
|
||||||
|
fillModeRow.connect('notify::selected', () => {
|
||||||
|
backgroundSettings.set_string('picture-options', pictureOptionsList[fillModeRow.selected].value);
|
||||||
|
});
|
||||||
|
displayGroup.add(fillModeRow);
|
||||||
|
|
||||||
const foldersGroup = new Adw.PreferencesGroup({
|
const foldersGroup = new Adw.PreferencesGroup({
|
||||||
title: _('Wallpaper Folders'),
|
title: _('Wallpaper Folders'),
|
||||||
description: _('Images found directly inside these folders are used for rotation'),
|
description: _('Images found directly inside these folders are used for rotation'),
|
||||||
@@ -71,15 +109,18 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
const folderList = this._buildFolderListBox(settings, SettingsKey.WALLPAPER_FOLDERS, _('No folders added yet'));
|
const folderList = this._buildFolderListBox(settings, SettingsKey.WALLPAPER_FOLDERS, _('No folders added yet'));
|
||||||
foldersGroup.add(folderList);
|
foldersGroup.add(folderList);
|
||||||
|
|
||||||
const addButton = new Gtk.Button({
|
const buttonRow = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL, spacing: 6, margin_top: 6});
|
||||||
label: _('Add Folder…'),
|
foldersGroup.add(buttonRow);
|
||||||
halign: Gtk.Align.START,
|
|
||||||
margin_top: 6,
|
const addButton = new Gtk.Button({label: _('Add Folder…'), css_classes: ['flat']});
|
||||||
css_classes: ['flat'],
|
|
||||||
});
|
|
||||||
addButton.connect('clicked', () => this._pickFolderForList(
|
addButton.connect('clicked', () => this._pickFolderForList(
|
||||||
settings, SettingsKey.WALLPAPER_FOLDERS, folderList, _('No folders added yet')));
|
settings, SettingsKey.WALLPAPER_FOLDERS, folderList, _('No folders added yet')));
|
||||||
foldersGroup.add(addButton);
|
buttonRow.append(addButton);
|
||||||
|
|
||||||
|
const refreshButton = new Gtk.Button({label: _('Refresh'), css_classes: ['flat']});
|
||||||
|
refreshButton.connect('clicked', () => settings.set_uint(
|
||||||
|
SettingsKey.WALLPAPER_RESCAN_REQUEST, settings.get_uint(SettingsKey.WALLPAPER_RESCAN_REQUEST) + 1));
|
||||||
|
buttonRow.append(refreshButton);
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
@@ -174,7 +215,17 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
page.add(transitionGroup);
|
page.add(transitionGroup);
|
||||||
transitionGroup.add(this._switchRow(
|
transitionGroup.add(this._switchRow(
|
||||||
settings, SettingsKey.TRANSITION_ENABLED,
|
settings, SettingsKey.TRANSITION_ENABLED,
|
||||||
_('Crossfade'), _('Smoothly fade between wallpapers instead of switching instantly')));
|
_('Enable Transition'), _('Play an animation between wallpapers instead of switching instantly')));
|
||||||
|
|
||||||
|
const styles = ['fade', 'slide', 'zoom', 'random'];
|
||||||
|
const styleLabels = [_('Fade'), _('Slide'), _('Zoom'), _('Random')];
|
||||||
|
const styleRow = new Adw.ComboRow({title: _('Style'), model: new Gtk.StringList({strings: styleLabels})});
|
||||||
|
styleRow.selected = Math.max(0, styles.indexOf(settings.get_string(SettingsKey.TRANSITION_STYLE)));
|
||||||
|
styleRow.connect('notify::selected', () => {
|
||||||
|
settings.set_string(SettingsKey.TRANSITION_STYLE, styles[styleRow.selected]);
|
||||||
|
});
|
||||||
|
transitionGroup.add(styleRow);
|
||||||
|
|
||||||
transitionGroup.add(this._spinRow(
|
transitionGroup.add(this._spinRow(
|
||||||
{title: _('Fade Duration'), subtitle: _('Milliseconds'), lower: 200, upper: 5000, step: 100, page: 500},
|
{title: _('Fade Duration'), subtitle: _('Milliseconds'), lower: 200, upper: 5000, step: 100, page: 500},
|
||||||
() => settings.get_uint(SettingsKey.TRANSITION_DURATION_MS),
|
() => settings.get_uint(SettingsKey.TRANSITION_DURATION_MS),
|
||||||
@@ -251,6 +302,19 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
fileRow.add_suffix(chooseButton);
|
fileRow.add_suffix(chooseButton);
|
||||||
group.add(fileRow);
|
group.add(fileRow);
|
||||||
|
|
||||||
|
const liveDisplayModes = ['fit', 'fill', 'stretch', 'center'];
|
||||||
|
const liveDisplayLabels = [_('Fit (Letterboxed)'), _('Fill (Crop to Cover)'), _('Stretch'), _('Center')];
|
||||||
|
const displayModeRow = new Adw.ComboRow({
|
||||||
|
title: _('Display Mode'),
|
||||||
|
model: new Gtk.StringList({strings: liveDisplayLabels}),
|
||||||
|
});
|
||||||
|
displayModeRow.selected = Math.max(
|
||||||
|
0, liveDisplayModes.indexOf(settings.get_string(SettingsKey.LIVE_WALLPAPER_DISPLAY_MODE)));
|
||||||
|
displayModeRow.connect('notify::selected', () => {
|
||||||
|
settings.set_string(SettingsKey.LIVE_WALLPAPER_DISPLAY_MODE, liveDisplayModes[displayModeRow.selected]);
|
||||||
|
});
|
||||||
|
group.add(displayModeRow);
|
||||||
|
|
||||||
const muteRow = this._switchRow(
|
const muteRow = this._switchRow(
|
||||||
settings, SettingsKey.LIVE_WALLPAPER_MUTED,
|
settings, SettingsKey.LIVE_WALLPAPER_MUTED,
|
||||||
_('Mute Audio'), _('Play video wallpapers without sound'));
|
_('Mute Audio'), _('Play video wallpapers without sound'));
|
||||||
@@ -283,15 +347,17 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, _('No folders added yet'), refreshMedia);
|
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, _('No folders added yet'), refreshMedia);
|
||||||
foldersGroup.add(liveFolderList);
|
foldersGroup.add(liveFolderList);
|
||||||
|
|
||||||
const addFolderButton = new Gtk.Button({
|
const liveButtonRow = new Gtk.Box({orientation: Gtk.Orientation.HORIZONTAL, spacing: 6, margin_top: 6});
|
||||||
label: _('Add Folder…'),
|
foldersGroup.add(liveButtonRow);
|
||||||
halign: Gtk.Align.START,
|
|
||||||
margin_top: 6,
|
const addFolderButton = new Gtk.Button({label: _('Add Folder…'), css_classes: ['flat']});
|
||||||
css_classes: ['flat'],
|
|
||||||
});
|
|
||||||
addFolderButton.connect('clicked', () => this._pickFolderForList(
|
addFolderButton.connect('clicked', () => this._pickFolderForList(
|
||||||
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, liveFolderList, _('No folders added yet'), refreshMedia));
|
settings, SettingsKey.LIVE_WALLPAPER_FOLDERS, liveFolderList, _('No folders added yet'), refreshMedia));
|
||||||
foldersGroup.add(addFolderButton);
|
liveButtonRow.append(addFolderButton);
|
||||||
|
|
||||||
|
const refreshMediaButton = new Gtk.Button({label: _('Refresh'), css_classes: ['flat']});
|
||||||
|
refreshMediaButton.connect('clicked', refreshMedia);
|
||||||
|
liveButtonRow.append(refreshMediaButton);
|
||||||
|
|
||||||
refreshMedia();
|
refreshMedia();
|
||||||
|
|
||||||
@@ -309,6 +375,7 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
// GStreamer is actually driving playback is greyed out.
|
// GStreamer is actually driving playback is greyed out.
|
||||||
if (gstreamerError) {
|
if (gstreamerError) {
|
||||||
fileRow.sensitive = false;
|
fileRow.sensitive = false;
|
||||||
|
displayModeRow.sensitive = false;
|
||||||
muteRow.sensitive = false;
|
muteRow.sensitive = false;
|
||||||
rateRow.sensitive = false;
|
rateRow.sensitive = false;
|
||||||
foldersGroup.sensitive = false;
|
foldersGroup.sensitive = false;
|
||||||
@@ -394,9 +461,12 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
settings, SettingsKey.OLED_PIXEL_SHIFT_ENABLED,
|
settings, SettingsKey.OLED_PIXEL_SHIFT_ENABLED,
|
||||||
_('Enable Pixel Shifting'), _('Periodically nudge the background by a few pixels')));
|
_('Enable Pixel Shifting'), _('Periodically nudge the background by a few pixels')));
|
||||||
shiftGroup.add(this._spinRow(
|
shiftGroup.add(this._spinRow(
|
||||||
{title: _('Shift Interval'), subtitle: _('Seconds between each shift step'), lower: 10, upper: 600, step: 5, page: 30},
|
{
|
||||||
() => settings.get_uint(SettingsKey.OLED_PIXEL_SHIFT_INTERVAL_SECONDS),
|
title: _('Shift Interval'), subtitle: _('Minutes between each shift step'),
|
||||||
value => settings.set_uint(SettingsKey.OLED_PIXEL_SHIFT_INTERVAL_SECONDS, Math.round(value))));
|
lower: 0.5, upper: 10, step: 0.5, page: 1, digits: 1,
|
||||||
|
},
|
||||||
|
() => settings.get_uint(SettingsKey.OLED_PIXEL_SHIFT_INTERVAL_SECONDS) / 60,
|
||||||
|
value => settings.set_uint(SettingsKey.OLED_PIXEL_SHIFT_INTERVAL_SECONDS, Math.round(value * 60))));
|
||||||
shiftGroup.add(this._spinRow(
|
shiftGroup.add(this._spinRow(
|
||||||
{title: _('Shift Amount'), subtitle: _('Pixels'), lower: 1, upper: 10, step: 1, page: 1},
|
{title: _('Shift Amount'), subtitle: _('Pixels'), lower: 1, upper: 10, step: 1, page: 1},
|
||||||
() => settings.get_uint(SettingsKey.OLED_PIXEL_SHIFT_AMOUNT_PX),
|
() => settings.get_uint(SettingsKey.OLED_PIXEL_SHIFT_AMOUNT_PX),
|
||||||
@@ -408,9 +478,9 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
|
|||||||
settings, SettingsKey.OLED_DIM_ON_IDLE_ENABLED,
|
settings, SettingsKey.OLED_DIM_ON_IDLE_ENABLED,
|
||||||
_('Dim When Idle'), _('Lower brightness after a period of inactivity')));
|
_('Dim When Idle'), _('Lower brightness after a period of inactivity')));
|
||||||
dimGroup.add(this._spinRow(
|
dimGroup.add(this._spinRow(
|
||||||
{title: _('Idle Delay'), subtitle: _('Seconds of inactivity before dimming'), lower: 10, upper: 3600, step: 10, page: 60},
|
{title: _('Idle Delay'), subtitle: _('Minutes of inactivity before dimming'), lower: 1, upper: 60, step: 1, page: 5},
|
||||||
() => settings.get_uint(SettingsKey.OLED_DIM_IDLE_DELAY_SECONDS),
|
() => settings.get_uint(SettingsKey.OLED_DIM_IDLE_DELAY_SECONDS) / 60,
|
||||||
value => settings.set_uint(SettingsKey.OLED_DIM_IDLE_DELAY_SECONDS, Math.round(value))));
|
value => settings.set_uint(SettingsKey.OLED_DIM_IDLE_DELAY_SECONDS, Math.round(value * 60))));
|
||||||
dimGroup.add(this._spinRow(
|
dimGroup.add(this._spinRow(
|
||||||
{
|
{
|
||||||
title: _('Dimmed Brightness'), subtitle: _('0.0 = black, 0.9 = barely dimmed'),
|
title: _('Dimmed Brightness'), subtitle: _('0.0 = black, 0.9 = barely dimmed'),
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
<description>List of folder paths scanned for wallpaper images.</description>
|
<description>List of folder paths scanned for wallpaper images.</description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
<key name="wallpaper-rescan-request" type="u">
|
||||||
|
<default>0</default>
|
||||||
|
<summary>Wallpaper folder rescan request</summary>
|
||||||
|
<description>Internal counter bumped by the preferences UI to ask the running extension to rescan wallpaper folders for new files.</description>
|
||||||
|
</key>
|
||||||
|
|
||||||
<key name="apply-to-lock-screen" type="b">
|
<key name="apply-to-lock-screen" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Apply wallpaper to lock screen</summary>
|
<summary>Apply wallpaper to lock screen</summary>
|
||||||
@@ -67,6 +73,18 @@
|
|||||||
<description>Duration, in milliseconds, of the crossfade animation between wallpapers.</description>
|
<description>Duration, in milliseconds, of the crossfade animation between wallpapers.</description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
<key name="transition-style" type="s">
|
||||||
|
<choices>
|
||||||
|
<choice value="fade"/>
|
||||||
|
<choice value="slide"/>
|
||||||
|
<choice value="zoom"/>
|
||||||
|
<choice value="random"/>
|
||||||
|
</choices>
|
||||||
|
<default>"fade"</default>
|
||||||
|
<summary>Transition style</summary>
|
||||||
|
<description>Visual effect used when the wallpaper changes.</description>
|
||||||
|
</key>
|
||||||
|
|
||||||
<key name="live-wallpaper-enabled" type="b">
|
<key name="live-wallpaper-enabled" type="b">
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
<summary>Enable live wallpaper</summary>
|
<summary>Enable live wallpaper</summary>
|
||||||
@@ -85,6 +103,18 @@
|
|||||||
<description>List of folder paths scanned for videos and animated GIFs to pick a live wallpaper from.</description>
|
<description>List of folder paths scanned for videos and animated GIFs to pick a live wallpaper from.</description>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
<key name="live-wallpaper-display-mode" type="s">
|
||||||
|
<choices>
|
||||||
|
<choice value="fit"/>
|
||||||
|
<choice value="fill"/>
|
||||||
|
<choice value="stretch"/>
|
||||||
|
<choice value="center"/>
|
||||||
|
</choices>
|
||||||
|
<default>"fit"</default>
|
||||||
|
<summary>Live wallpaper display mode</summary>
|
||||||
|
<description>How the video/GIF is fitted to the screen: fit (letterboxed), fill (cropped to cover), stretch (distorted to fill) or center (original size).</description>
|
||||||
|
</key>
|
||||||
|
|
||||||
<key name="live-wallpaper-muted" type="b">
|
<key name="live-wallpaper-muted" type="b">
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
<summary>Mute live wallpaper audio</summary>
|
<summary>Mute live wallpaper audio</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user