Build the live wallpaper sink bin explicitly instead of via string parsing

User testing pinned the bug precisely: our try/catch is airtight now,
and the diagnostics showed "new-sample signal fired 0 times" — the
appsink genuinely never receives a single sample inside gnome-shell's
process, while an equivalent pipeline (videoconvert ! videoscale !
RGBA caps ! appsink) played correctly standalone via gst-launch-1.0.

Replaces Gst.parse_bin_from_description()'s gst-launch mini-language
parsing (with its automatic unlinked-pad ghosting) with explicit
ElementFactory.make() + Bin.add() + element.link() + a manually
created GhostPad. This removes the string-parsing path as a variable
entirely and, importantly, surfaces any link() failure as a thrown
error instead of failing silently, which the old code path had no way
to report even if that patch were the whole cachet of the problem.

Also fixes a related warning ("Trying to dispose element ..., but it
is in PLAYING instead of the NULL state") from _stop() dropping the
playbin reference immediately after requesting the NULL state, before
the (async) transition actually completed — now waits briefly via
get_state() for it to settle first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
This commit is contained in:
Claude
2026-08-04 01:11:29 +00:00
parent 14078f0466
commit 5157f4a1c9
+66 -6
View File
@@ -128,6 +128,61 @@ export class LiveWallpaperManager {
this._start();
}
/**
* Builds videoconvert ! videoscale ! capsfilter(RGBA) ! appsink using
* explicit element creation, linking, and a ghost pad, rather than
* Gst.parse_bin_from_description()'s gst-launch mini-language. Both
* are meant to be equivalent, but a user's testing showed the
* string-parsed version's appsink never fired 'new-sample' even once
* inside gnome-shell's process while an equivalent standalone
* gst-launch-1.0 pipeline worked fine — this removes the string
* parsing (and its automatic ghost-pad detection) as a variable, and
* surfaces link() failures explicitly instead of failing silently.
*/
_buildSinkBin() {
const Gst = this._Gst;
const videoconvert = Gst.ElementFactory.make('videoconvert', 'benthicbloom-convert');
const videoscale = Gst.ElementFactory.make('videoscale', 'benthicbloom-scale');
const capsfilter = Gst.ElementFactory.make('capsfilter', 'benthicbloom-capsfilter');
const appsink = Gst.ElementFactory.make('appsink', 'benthicbloom-appsink');
if (!videoconvert || !videoscale || !capsfilter || !appsink) {
throw new Error(
'Failed to create one or more GStreamer elements ' +
'(videoconvert/videoscale/capsfilter/appsink) — a required plugin is likely missing');
}
capsfilter.set_property('caps', Gst.Caps.from_string('video/x-raw,format=RGBA'));
appsink.set_property('emit-signals', true);
appsink.set_property('max-buffers', 2);
appsink.set_property('drop', true);
appsink.set_property('sync', true);
const sinkBin = new Gst.Bin({name: 'benthicbloom-sinkbin'});
sinkBin.add(videoconvert);
sinkBin.add(videoscale);
sinkBin.add(capsfilter);
sinkBin.add(appsink);
if (!videoconvert.link(videoscale))
throw new Error('Failed to link videoconvert -> videoscale');
if (!videoscale.link(capsfilter))
throw new Error('Failed to link videoscale -> capsfilter');
if (!capsfilter.link(appsink))
throw new Error('Failed to link capsfilter -> appsink');
const sinkPad = videoconvert.get_static_pad('sink');
const ghostPad = Gst.GhostPad.new('sink', sinkPad);
if (!ghostPad)
throw new Error('Failed to create ghost pad for live wallpaper sink bin');
ghostPad.set_active(true);
sinkBin.add_pad(ghostPad);
this._appsink = appsink;
return sinkBin;
}
_start() {
const path = this._settings.get_string(SettingsKey.LIVE_WALLPAPER_PATH);
if (!this._available || !path)
@@ -143,11 +198,7 @@ export class LiveWallpaperManager {
Main.layoutManager._backgroundGroup.add_child(this._actor);
this._layoutActor();
const sinkBin = Gst.parse_bin_from_description(
'videoconvert ! videoscale ! video/x-raw,format=RGBA ! ' +
'appsink name=benthicbloom_sink emit-signals=true max-buffers=2 drop=true sync=true',
true);
this._appsink = sinkBin.get_by_name('benthicbloom_sink');
const sinkBin = this._buildSinkBin();
this._appsink.connect('new-sample', sink => this._onNewSample(sink));
this._playbin = Gst.ElementFactory.make('playbin', 'benthicbloom-live-wallpaper');
@@ -381,13 +432,22 @@ export class LiveWallpaperManager {
}
if (this._playbin) {
const Gst = this._Gst;
const bus = this._playbin.get_bus();
if (this._busWatchId) {
bus.disconnect(this._busWatchId);
this._busWatchId = 0;
}
bus.remove_signal_watch();
this._playbin.set_state(this._Gst.State.NULL);
this._playbin.set_state(Gst.State.NULL);
// Block briefly for the (normally fast) transition to actually
// finish before dropping our reference — otherwise the element
// can get disposed mid-transition, which GStreamer logs as
// "Trying to dispose element ..., but it is in PLAYING instead
// of the NULL state".
this._playbin.get_state(200 * Gst.MSECOND);
this._playbin = null;
this._appsink = null;
}