diff --git a/lib/gstreamerAvailability.js b/lib/gstreamerAvailability.js new file mode 100644 index 0000000..cf8ee59 --- /dev/null +++ b/lib/gstreamerAvailability.js @@ -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).'; diff --git a/lib/liveWallpaper.js b/lib/liveWallpaper.js index 58af89e..96cf267 100644 --- a/lib/liveWallpaper.js +++ b/lib/liveWallpaper.js @@ -4,6 +4,7 @@ import Clutter from 'gi://Clutter'; import * as Main from 'resource:///org/gnome/shell/ui/main.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. @@ -88,28 +89,14 @@ export class LiveWallpaperManager { async _loadGstreamer() { try { - // GstApp's binding itself is unused, but importing it registers - // 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'), - ]); - + const {Gst, Cogl} = await loadGstreamerModules(); this._Gst = Gst; this._Cogl = Cogl; - - if (!Gst.is_initialized()) - Gst.init(null); - this._available = true; } catch (e) { this._available = false; this._logger.warn( - 'Live wallpaper unavailable: GStreamer/Cogl introspection bindings could not be loaded ' + - `(${e.message ?? e}). Install GStreamer with its "good"/"base" plugin sets to enable this feature.`); + `Live wallpaper unavailable: GStreamer/Cogl introspection bindings could not be loaded (${e.message ?? e}).\n${GSTREAMER_INSTALL_HINT}`); } } diff --git a/prefs.js b/prefs.js index 27e5030..2c94ffb 100644 --- a/prefs.js +++ b/prefs.js @@ -6,15 +6,17 @@ import Gdk from 'gi://Gdk'; import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; import {SettingsKey} from './lib/settingsKeys.js'; +import {loadGstreamerModules, GSTREAMER_INSTALL_HINT} from './lib/gstreamerAvailability.js'; export default class BenthicBloomPreferences extends ExtensionPreferences { - fillPreferencesWindow(window) { + async fillPreferencesWindow(window) { const settings = this.getSettings(); + const gstreamerError = await loadGstreamerModules().then(() => null, e => e.message ?? String(e)); window.set_default_size(640, 720); window.add(this._buildGeneralPage(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._buildAboutPage()); } @@ -170,9 +172,21 @@ export default class BenthicBloomPreferences extends ExtensionPreferences { // --- Live wallpaper page -------------------------------------------- - _buildLiveWallpaperPage(settings) { + _buildLiveWallpaperPage(settings, gstreamerError) { 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({ title: _('Video Wallpaper'), description: _( @@ -221,17 +235,19 @@ export default class BenthicBloomPreferences extends ExtensionPreferences { fileRow.add_suffix(chooseButton); group.add(fileRow); - group.add(this._switchRow( + const muteRow = this._switchRow( settings, SettingsKey.LIVE_WALLPAPER_MUTED, - _('Mute Audio'), _('Play video wallpapers without sound'))); - group.add(this._spinRow( + _('Mute Audio'), _('Play video wallpapers without sound')); + group.add(muteRow); + const rateRow = this._spinRow( { title: _('Playback 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, }, () => 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')}); page.add(powerGroup); @@ -242,6 +258,16 @@ export default class BenthicBloomPreferences extends ExtensionPreferences { settings, SettingsKey.LIVE_WALLPAPER_PAUSE_WHEN_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; }