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
Showing only changes of commit d5f6d3ecad - Show all commits
+34 -20
View File
@@ -50,7 +50,8 @@ export class LiveWallpaperManager {
this._upowerSignalId = 0; this._upowerSignalId = 0;
this._frameCount = 0; this._frameCount = 0;
this._newSampleCallCount = 0; this._pollCount = 0;
this._pollTimeoutId = 0;
this._noFrameWatchdogId = 0; this._noFrameWatchdogId = 0;
} }
@@ -154,7 +155,11 @@ export class LiveWallpaperManager {
} }
capsfilter.set_property('caps', Gst.Caps.from_string('video/x-raw,format=RGBA')); capsfilter.set_property('caps', Gst.Caps.from_string('video/x-raw,format=RGBA'));
appsink.set_property('emit-signals', true); // emit-signals is deliberately left off: appsink's 'new-sample' fires
// from GStreamer's streaming thread, not gnome-shell's main thread,
// and a cross-thread call into the JS engine can be silently dropped
// rather than invoked. We poll with try_pull_sample() from a main
// thread GLib timer instead, which is safe to call from any thread.
appsink.set_property('max-buffers', 2); appsink.set_property('max-buffers', 2);
appsink.set_property('drop', true); appsink.set_property('drop', true);
appsink.set_property('sync', true); appsink.set_property('sync', true);
@@ -199,7 +204,6 @@ export class LiveWallpaperManager {
this._layoutActor(); this._layoutActor();
const sinkBin = this._buildSinkBin(); const sinkBin = this._buildSinkBin();
this._appsink.connect('new-sample', sink => this._onNewSample(sink));
this._playbin = Gst.ElementFactory.make('playbin', 'benthicbloom-live-wallpaper'); this._playbin = Gst.ElementFactory.make('playbin', 'benthicbloom-live-wallpaper');
this._playbin.set_property('video-sink', sinkBin); this._playbin.set_property('video-sink', sinkBin);
@@ -214,16 +218,20 @@ export class LiveWallpaperManager {
this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`); this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`);
this._frameCount = 0; this._frameCount = 0;
this._newSampleCallCount = 0; this._pollCount = 0;
// ~30fps polling of the appsink from the main thread. See the
// comment on appsink's properties above for why this replaces
// a 'new-sample' signal handler.
this._pollTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 33, () => this._pollForSample());
this._noFrameWatchdogId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 4, () => { this._noFrameWatchdogId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 4, () => {
this._noFrameWatchdogId = 0; this._noFrameWatchdogId = 0;
if (this._active && this._frameCount === 0) { if (this._active && this._frameCount === 0) {
this._logger.warn( this._logger.warn(
`Live wallpaper: no frames received 4s after starting playback ` + `Live wallpaper: no frames received 4s after starting playback ` +
`(new-sample signal fired ${this._newSampleCallCount} times). ` + `(polled appsink ${this._pollCount} times). If that count is 0, the poll timer ` +
'If that count is 0, the signal itself never fired (check for a stalled ' + 'itself never ran; if it is nonzero, try_pull_sample() keeps returning nothing ' +
'pipeline above); if it is nonzero, pull_sample()/buffer processing is failing ' + '— check for a "Failed to poll live wallpaper frame" error above.');
'— check for a "Failed to process live wallpaper frame" error above.');
} }
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
@@ -238,18 +246,19 @@ export class LiveWallpaperManager {
} }
} }
_onNewSample(sink) { _pollForSample() {
if (!this._appsink)
return GLib.SOURCE_REMOVE;
const Gst = this._Gst; const Gst = this._Gst;
this._newSampleCallCount = (this._newSampleCallCount ?? 0) + 1; this._pollCount++;
if (this._newSampleCallCount === 1) if (this._pollCount === 1)
this._logger.debug('Live wallpaper: new-sample signal fired for the first time'); this._logger.debug('Live wallpaper: appsink polling started');
try { try {
const sample = sink.pull_sample(); const sample = this._appsink.try_pull_sample(0);
if (!sample) { if (!sample)
this._logger.debug('Live wallpaper: pull_sample() returned no sample'); return GLib.SOURCE_CONTINUE;
return Gst.FlowReturn.OK;
}
const buffer = sample.get_buffer(); const buffer = sample.get_buffer();
const structure = sample.get_caps().get_structure(0); const structure = sample.get_caps().get_structure(0);
@@ -259,7 +268,7 @@ export class LiveWallpaperManager {
const [ok, mapInfo] = buffer.map(Gst.MapFlags.READ); const [ok, mapInfo] = buffer.map(Gst.MapFlags.READ);
if (!ok) { if (!ok) {
this._logger.debug('Live wallpaper: buffer.map() failed'); this._logger.debug('Live wallpaper: buffer.map() failed');
return Gst.FlowReturn.OK; return GLib.SOURCE_CONTINUE;
} }
try { try {
@@ -273,10 +282,10 @@ export class LiveWallpaperManager {
buffer.unmap(mapInfo); buffer.unmap(mapInfo);
} }
} catch (e) { } catch (e) {
this._logger.error(e, 'Failed to process live wallpaper frame (new-sample handler)'); this._logger.error(e, 'Failed to poll live wallpaper frame');
} }
return Gst.FlowReturn.OK; return GLib.SOURCE_CONTINUE;
} }
_updateFrame(data, width, height) { _updateFrame(data, width, height) {
@@ -431,6 +440,11 @@ export class LiveWallpaperManager {
this._noFrameWatchdogId = 0; this._noFrameWatchdogId = 0;
} }
if (this._pollTimeoutId) {
GLib.source_remove(this._pollTimeoutId);
this._pollTimeoutId = 0;
}
if (this._playbin) { if (this._playbin) {
const Gst = this._Gst; const Gst = this._Gst;
const bus = this._playbin.get_bus(); const bus = this._playbin.get_bus();