Commit Graph
10 Commits
Author SHA1 Message Date
Claude 2365d303f3 Fix frame upload: Clutter.Image isn't constructible from GJS anymore
The polling fix worked and pinpointed the real, final bug: appsink was
delivering frames correctly (confirmed by the polling infrastructure
itself running fine), but every single upload attempt threw
"(intermediate value).Image is not a constructor" from `new
Clutter.Image()`. In current Mutter, Clutter.Image apparently isn't
directly constructible via `new` from GJS anymore, and the resulting
exception was also flooding the log at ~30/sec with no rate limiting.

Adds St.ImageContent.new_with_preferred_size() + set_bytes(GLib.Bytes)
as the primary path — the same mechanism gnome-shell's own code uses
for uploading raw pixel buffers onto actors — with the old
Clutter.Image/set_data() path kept only as a fallback for older
shells. The two are tried in order per frame size change and "pinned"
once one works, avoiding a live per-frame strategy search. Also
rate-limits the poll error log to the 1st and every 300th failure
instead of every single one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 01:26:38 +00:00
Claude d5f6d3ecad Poll appsink instead of relying on the new-sample signal
The last round of testing ruled out the pipeline construction itself:
even with the sink bin rebuilt from explicit, individually-checked
link() calls (no thrown errors, playbin reaches PLAYING), the
'new-sample' signal still never fired inside gnome-shell — while an
equivalent pipeline worked fine standalone via gst-launch-1.0.

The remaining, gnome-shell-specific difference: appsink emits
'new-sample' from GStreamer's own streaming thread, not the main
thread. GJS's JS engine isn't safe to call into from an arbitrary
background thread, and a cross-thread signal emission can be silently
dropped rather than invoked or crashed on — which looks exactly like
"the signal never fires" from here, even though the pipeline is
actually running.

Replaces the signal entirely with polling: a ~30fps GLib.timeout_add()
on the main thread calls appsink.try_pull_sample(0), which is
explicitly documented as safe to call from any thread since we are now
the ones calling into GStreamer rather than the reverse. Also updates
the no-frames watchdog to report the poll count instead of a signal
fire count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 01:17:07 +00:00
Claude 5157f4a1c9 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
2026-08-04 01:11:29 +00:00
Claude 14078f0466 Close try/catch gap around appsink pull_sample()
Standalone gst-launch testing confirmed appsink-as-playbin-video-sink
works fine outside the extension (reaches PLAYING, negotiates caps,
runs to completion), narrowing the "no frames received" symptom down
to something inside our own new-sample handling in the shell process.

_onNewSample()'s try/catch didn't cover the sink.pull_sample() call
itself — if that specific call throws when invoked as a GJS signal
callback, the exception bypassed our logging entirely, falling back
to GJS's generic uncaught-exception path (which, like the
openPreferences case, may not mention "benthicbloom" and gets missed
by a filtered grep). Now the whole handler is covered, logs when the
signal fires for the first time, logs a null pull_sample() or failed
buffer.map() explicitly, and the watchdog reports the new-sample fire
count so we can tell "signal never fired" from "signal fired but
processing failed" on the next test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 01:04:07 +00:00
Claude 2688568fac Add live wallpaper pipeline diagnostics
A user's live wallpaper reached "started" with zero errors (Cogl and
GStreamer both loading fine, actor added to _backgroundGroup) but
never actually appeared on screen, and no code path here could tell
us why: the bus handler only looked at EOS/ERROR, silently discarding
WARNING and STATE_CHANGED messages where a stalled negotiation or
preroll would actually show up.

Adds: WARNING message logging, PLAYING/PAUSED/etc. state-changed
logging for the playbin itself, a frame counter with first-frame and
periodic debug logs, and a one-shot watchdog that logs an explicit
warning if zero frames arrive within 4s of set_state(PLAYING). Also
switched appsink's pull-sample from an emitted action signal to the
plain pull_sample() method, which is the more directly supported
GstApp.AppSink API and one less variable while debugging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:50:24 +00:00
Claude f7c05c2c76 Fix false-positive "GStreamer Not Found" warning in Preferences
The Preferences window's availability probe (added to surface missing
GStreamer deps in-UI) reused loadGstreamerModules(), which also checks
for Cogl. Cogl is Mutter's private library — its typelib is only
reachable from inside the actual gnome-shell process (which gets a
private GI search path), never from the separate, plain-GTK4
Preferences process. So the check always failed there with "Requiring
Cogl ... not found" even when live wallpapers were working correctly
in the real shell process, as confirmed by a user report where the
extension's own log showed live wallpaper playback starting cleanly
while Preferences simultaneously claimed GStreamer was missing.

Split into loadGstreamerModules() (Gst+GstApp+Cogl, used only by
liveWallpaper.js in the shell process) and a new lighter
checkGstreamerBaseAvailable() (Gst+GstApp only) for prefs.js, with a
note in the UI that a clean check there isn't a full guarantee since
the private half can't be verified from that process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:36:48 +00:00
Claude d180c90434 Suspend rotation while a live wallpaper is on screen
The extension's own log showed rotation kept firing ("Wallpaper
changed to ...") after live wallpaper playback had already started.
Each rotation change writes org.gnome.desktop.background's
picture-uri, which makes GNOME Shell repaint its own background actor
into the same _backgroundGroup our live wallpaper actor lives in,
landing on top of it and hiding the video/GIF entirely.

LiveWallpaperManager now takes an onActiveChanged callback, invoked
only on actual start/stop transitions, which extension.js uses to
suspend/unsuspend RotationManager. This is tracked separately from
user-initiated pause()/resume() so turning live wallpaper off restores
whatever rotation state the user actually had, and next()/previous()
now no-op while suspended so manual/forced-OLED wallpaper changes
can't sneak one in either. The indicator's "Next Wallpaper" item is
greyed out to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:33:01 +00:00
Claude 4a5ef4fe86 Surface missing GStreamer deps in Preferences instead of only logging
Previously, if the Gst/GstApp/Cogl GI typelibs couldn't be loaded (e.g.
gst-plugins-base/good not installed, only the bare gstreamer package),
the only sign was a warning buried in the shell's log — the live
wallpaper page looked fully functional and silently did nothing.

Preferences now probes for the same bindings the running extension
needs and shows a warning row with the exact import error plus
per-distro install commands, and greys out the controls that only
matter once GStreamer is actually available. Extracted the shared
probing logic into lib/gstreamerAvailability.js so extension.js and
prefs.js (separate processes) don't duplicate it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:26:53 +00:00
Claude 5e8884d01e Allow selecting animated GIFs as live wallpapers
The playback pipeline already typefinds by content and decodes GIFs
via GStreamer's decodebin like any other video, but the preferences
file picker filtered to video/* MIME types only, hiding .gif files.
Widen the filter to include image/gif and *.gif, add an "All files"
fallback, and update the related copy/docs to mention GIF support.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:18:43 +00:00
Claude c0ec4a59d5 Implement BenthicBloom GNOME 50 wallpaper extension
Adds a complete GNOME Shell 50 extension with folder-based wallpaper
auto-rotation (shuffle/sequential, crossfade transitions, lock screen
sync), GStreamer-backed live/video wallpapers rendered onto the shell's
own Clutter stage, and OLED burn-in protection (pixel shifting, idle
dimming, forced periodic rotation). Includes a top-bar quick-access
indicator, a libadwaita preferences window, GSettings schema, and a
Makefile for install/packaging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
2026-08-04 00:05:58 +00:00