Claude/gnome wallpaper auto rotation r1ailq #1
+54
-7
@@ -1,11 +1,36 @@
|
|||||||
import GLib from 'gi://GLib';
|
import GLib from 'gi://GLib';
|
||||||
import Gio from 'gi://Gio';
|
import Gio from 'gi://Gio';
|
||||||
import Clutter from 'gi://Clutter';
|
import Clutter from 'gi://Clutter';
|
||||||
|
import St from 'gi://St';
|
||||||
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';
|
import {loadGstreamerModules, GSTREAMER_INSTALL_HINT} from './gstreamerAvailability.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ways to get a decoded frame onto an actor, tried in order and "pinned"
|
||||||
|
* once one works. `new Clutter.Image()` failed with "is not a constructor"
|
||||||
|
* in real-world testing (Clutter.Image is apparently no longer directly
|
||||||
|
* constructible from GJS in current Mutter); St.ImageContent — the same
|
||||||
|
* mechanism gnome-shell's own code uses for uploading raw pixel buffers —
|
||||||
|
* is tried first, with the legacy Clutter.Image/set_data path kept only as
|
||||||
|
* a fallback for older shells.
|
||||||
|
*/
|
||||||
|
const FRAME_IMAGE_STRATEGIES = [
|
||||||
|
{
|
||||||
|
name: 'St.ImageContent',
|
||||||
|
create: (width, height) => St.ImageContent.new_with_preferred_size(width, height),
|
||||||
|
upload: (image, Cogl, data, width, height) =>
|
||||||
|
image.set_bytes(GLib.Bytes.new(data), Cogl.PixelFormat.RGBA_8888, width, height, width * 4),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clutter.Image',
|
||||||
|
create: () => new Clutter.Image(),
|
||||||
|
upload: (image, Cogl, data, width, height) =>
|
||||||
|
image.set_data(data, Cogl.PixelFormat.RGBA_8888, width, height, width * 4),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a video or animated GIF file as a looping animated background.
|
* Plays a video or animated GIF file as a looping animated background.
|
||||||
* `playbin` typefinds the source by content rather than extension, so GIFs
|
* `playbin` typefinds the source by content rather than extension, so GIFs
|
||||||
@@ -53,6 +78,8 @@ export class LiveWallpaperManager {
|
|||||||
this._pollCount = 0;
|
this._pollCount = 0;
|
||||||
this._pollTimeoutId = 0;
|
this._pollTimeoutId = 0;
|
||||||
this._noFrameWatchdogId = 0;
|
this._noFrameWatchdogId = 0;
|
||||||
|
this._imageStrategyIndex = 0;
|
||||||
|
this._frameErrorCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAvailable() {
|
get isAvailable() {
|
||||||
@@ -219,6 +246,8 @@ export class LiveWallpaperManager {
|
|||||||
|
|
||||||
this._frameCount = 0;
|
this._frameCount = 0;
|
||||||
this._pollCount = 0;
|
this._pollCount = 0;
|
||||||
|
this._imageStrategyIndex = 0;
|
||||||
|
this._frameErrorCount = 0;
|
||||||
// ~30fps polling of the appsink from the main thread. See the
|
// ~30fps polling of the appsink from the main thread. See the
|
||||||
// comment on appsink's properties above for why this replaces
|
// comment on appsink's properties above for why this replaces
|
||||||
// a 'new-sample' signal handler.
|
// a 'new-sample' signal handler.
|
||||||
@@ -282,7 +311,14 @@ export class LiveWallpaperManager {
|
|||||||
buffer.unmap(mapInfo);
|
buffer.unmap(mapInfo);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._logger.error(e, 'Failed to poll live wallpaper frame');
|
// Rate-limited: this runs at ~30fps, so logging every failure
|
||||||
|
// would flood the journal (and this loop keeps retrying every
|
||||||
|
// frame, e.g. while cycling through FRAME_IMAGE_STRATEGIES).
|
||||||
|
this._frameErrorCount++;
|
||||||
|
if (this._frameErrorCount === 1 || this._frameErrorCount % 300 === 0) {
|
||||||
|
this._logger.error(
|
||||||
|
e, `Failed to poll live wallpaper frame (${this._frameErrorCount} failures so far)`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GLib.SOURCE_CONTINUE;
|
return GLib.SOURCE_CONTINUE;
|
||||||
@@ -292,20 +328,31 @@ export class LiveWallpaperManager {
|
|||||||
if (!this._actor)
|
if (!this._actor)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!this._image || this._videoWidth !== width || this._videoHeight !== height) {
|
const needsNewImage = !this._image || this._videoWidth !== width || this._videoHeight !== height;
|
||||||
this._image = new Clutter.Image();
|
|
||||||
|
while (this._imageStrategyIndex < FRAME_IMAGE_STRATEGIES.length) {
|
||||||
|
const strategy = FRAME_IMAGE_STRATEGIES[this._imageStrategyIndex];
|
||||||
|
try {
|
||||||
|
if (needsNewImage) {
|
||||||
|
this._image = strategy.create(width, height);
|
||||||
this._videoWidth = width;
|
this._videoWidth = width;
|
||||||
this._videoHeight = height;
|
this._videoHeight = height;
|
||||||
}
|
}
|
||||||
|
strategy.upload(this._image, this._Cogl, data, width, height);
|
||||||
try {
|
|
||||||
this._image.set_data(data, this._Cogl.PixelFormat.RGBA_8888, width, height, width * 4);
|
|
||||||
this._actor.set_content(this._image);
|
this._actor.set_content(this._image);
|
||||||
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._logger.error(e, 'Failed to upload live wallpaper frame');
|
this._logger.warn(
|
||||||
|
`Live wallpaper: frame image strategy "${strategy.name}" failed ` +
|
||||||
|
`(${e.message ?? e}), trying the next one`);
|
||||||
|
this._imageStrategyIndex++;
|
||||||
|
this._image = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error('All live wallpaper frame image strategies failed');
|
||||||
|
}
|
||||||
|
|
||||||
_onBusMessage(message) {
|
_onBusMessage(message) {
|
||||||
const Gst = this._Gst;
|
const Gst = this._Gst;
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user