Claude/gnome wallpaper auto rotation r1ailq #1

Merged
Quinta0 merged 12 commits from claude/gnome-wallpaper-auto-rotation-r1ailq into main 2026-08-04 20:53:28 +02:00
3 changed files with 69 additions and 23 deletions
Showing only changes of commit 4a5ef4fe86 - Show all commits
+33
View File
@@ -0,0 +1,33 @@
/**
* Loads the GObject-Introspection bindings the live wallpaper feature needs
* (Gst, GstApp for the appsink signals, Cogl for uploading decoded frames)
* and initializes GStreamer. Shared by extension.js (to actually run the
* pipeline) and prefs.js (to tell the user up front why the feature is
* greyed out), since either process may be missing the system packages
* that provide these typelibs.
*
* Throws with a descriptive message on failure; never caches a *failure*,
* since the user may install the missing packages and reopen preferences
* without restarting the shell.
*/
export async function loadGstreamerModules() {
const [{default: Gst}, , {default: Cogl}] = await Promise.all([
import('gi://Gst?version=1.0'),
import('gi://GstApp?version=1.0'),
import('gi://Cogl'),
]);
if (!Gst.is_initialized())
Gst.init(null);
return {Gst, Cogl};
}
export const GSTREAMER_INSTALL_HINT =
'Install GStreamer’s "base" and "good" plugin sets (with their GObject-Introspection data), ' +
'which provide playback and GIF decoding:\n' +
'• Arch: sudo pacman -S gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav\n' +
'• Debian/Ubuntu: sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good ' +
'gir1.2-gst-plugins-base-1.0\n' +
'• Fedora: sudo dnf install gstreamer1-plugins-base gstreamer1-plugins-good gobject-introspection\n' +
'Then restart GNOME Shell (log out and back in on Wayland).';
+3 -16
View File
@@ -4,6 +4,7 @@ import Clutter from 'gi://Clutter';
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';
import {loadGstreamerModules, GSTREAMER_INSTALL_HINT} from './gstreamerAvailability.js';
/** /**
* Plays a video or animated GIF file as a looping animated background. * Plays a video or animated GIF file as a looping animated background.
@@ -88,28 +89,14 @@ export class LiveWallpaperManager {
async _loadGstreamer() { async _loadGstreamer() {
try { try {
// GstApp's binding itself is unused, but importing it registers const {Gst, Cogl} = await loadGstreamerModules();
// GstApp.AppSink with GJS so appsink elements expose 'new-sample'
// and the 'pull-sample' action signal instead of being wrapped
// as a generic, capability-less Gst.Element.
const [{default: Gst}, , {default: Cogl}] = await Promise.all([
import('gi://Gst?version=1.0'),
import('gi://GstApp?version=1.0'),
import('gi://Cogl'),
]);
this._Gst = Gst; this._Gst = Gst;
this._Cogl = Cogl; this._Cogl = Cogl;
if (!Gst.is_initialized())
Gst.init(null);
this._available = true; this._available = true;
} catch (e) { } catch (e) {
this._available = false; this._available = false;
this._logger.warn( this._logger.warn(
'Live wallpaper unavailable: GStreamer/Cogl introspection bindings could not be loaded ' + `Live wallpaper unavailable: GStreamer/Cogl introspection bindings could not be loaded (${e.message ?? e}).\n${GSTREAMER_INSTALL_HINT}`);
`(${e.message ?? e}). Install GStreamer with its "good"/"base" plugin sets to enable this feature.`);
} }
} }
+33 -7
View File
@@ -6,15 +6,17 @@ import Gdk from 'gi://Gdk';
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
import {SettingsKey} from './lib/settingsKeys.js'; import {SettingsKey} from './lib/settingsKeys.js';
import {loadGstreamerModules, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js';
export default class BenthicBloomPreferences extends ExtensionPreferences { export default class BenthicBloomPreferences extends ExtensionPreferences {
fillPreferencesWindow(window) { async fillPreferencesWindow(window) {
const settings = this.getSettings(); const settings = this.getSettings();
const gstreamerError = await loadGstreamerModules().then(() => null, e => e.message ?? String(e));
window.set_default_size(640, 720); window.set_default_size(640, 720);
window.add(this._buildGeneralPage(settings)); window.add(this._buildGeneralPage(settings));
window.add(this._buildRotationPage(settings)); window.add(this._buildRotationPage(settings));
window.add(this._buildLiveWallpaperPage(settings)); window.add(this._buildLiveWallpaperPage(settings, gstreamerError));
window.add(this._buildOledPage(settings)); window.add(this._buildOledPage(settings));
window.add(this._buildAboutPage()); window.add(this._buildAboutPage());
} }
@@ -170,9 +172,21 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
// --- Live wallpaper page -------------------------------------------- // --- Live wallpaper page --------------------------------------------
_buildLiveWallpaperPage(settings) { _buildLiveWallpaperPage(settings, gstreamerError) {
const page = new Adw.PreferencesPage({title: _('Live Wallpaper'), icon_name: 'video-x-generic-symbolic'}); const page = new Adw.PreferencesPage({title: _('Live Wallpaper'), icon_name: 'video-x-generic-symbolic'});
if (gstreamerError) {
const warningGroup = new Adw.PreferencesGroup();
const warningRow = new Adw.ActionRow({
title: _('GStreamer Not Found'),
subtitle: `${_('Live wallpapers will stay disabled until this is fixed:')} ${gstreamerError}\n\n${GSTREAMER_INSTALL_HINT}`,
css_classes: ['warning'],
});
warningRow.subtitle_lines = 0;
warningGroup.add(warningRow);
page.add(warningGroup);
}
const group = new Adw.PreferencesGroup({ const group = new Adw.PreferencesGroup({
title: _('Video Wallpaper'), title: _('Video Wallpaper'),
description: _( description: _(
@@ -221,17 +235,19 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
fileRow.add_suffix(chooseButton); fileRow.add_suffix(chooseButton);
group.add(fileRow); group.add(fileRow);
group.add(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'));
group.add(this._spinRow( group.add(muteRow);
const rateRow = this._spinRow(
{ {
title: _('Playback Speed'), title: _('Playback Speed'),
subtitle: _('Multiplier, e.g. 0.5 for half speed, 2.0 for double speed'), subtitle: _('Multiplier, e.g. 0.5 for half speed, 2.0 for double speed'),
lower: 0.1, upper: 4.0, step: 0.1, page: 0.5, digits: 1, lower: 0.1, upper: 4.0, step: 0.1, page: 0.5, digits: 1,
}, },
() => settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE), () => settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE),
value => settings.set_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE, value))); value => settings.set_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE, value));
group.add(rateRow);
const powerGroup = new Adw.PreferencesGroup({title: _('Power Saving')}); const powerGroup = new Adw.PreferencesGroup({title: _('Power Saving')});
page.add(powerGroup); page.add(powerGroup);
@@ -242,6 +258,16 @@ export default class BenthicBloomPreferences extends ExtensionPreferences {
settings, SettingsKey.LIVE_WALLPAPER_PAUSE_WHEN_FULLSCREEN, settings, SettingsKey.LIVE_WALLPAPER_PAUSE_WHEN_FULLSCREEN,
_('Pause When Fullscreen'), _('Stop video playback while a window is fullscreen'))); _('Pause When Fullscreen'), _('Stop video playback while a window is fullscreen')));
// The enable switch itself stays usable so the setting can be
// prepared ahead of time, but everything that only matters once
// GStreamer is actually driving playback is greyed out.
if (gstreamerError) {
fileRow.sensitive = false;
muteRow.sensitive = false;
rateRow.sensitive = false;
powerGroup.sensitive = false;
}
return page; return page;
} }