summaryrefslogtreecommitdiffstats
path: root/js/ui/background.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/ui/background.js798
1 files changed, 798 insertions, 0 deletions
diff --git a/js/ui/background.js b/js/ui/background.js
new file mode 100644
index 0000000..ca34451
--- /dev/null
+++ b/js/ui/background.js
@@ -0,0 +1,798 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+/* exported SystemBackground */
+
+// READ THIS FIRST
+// Background handling is a maze of objects, both objects in this file, and
+// also objects inside Mutter. They all have a role.
+//
+// BackgroundManager
+// The only object that other parts of GNOME Shell deal with; a
+// BackgroundManager creates background actors and adds them to
+// the specified container. When the background is changed by the
+// user it will fade out the old actor and fade in the new actor.
+// (This is separate from the fading for an animated background,
+// since using two actors is quite inefficient.)
+//
+// MetaBackgroundImage
+// An object represented an image file that will be used for drawing
+// the background. MetaBackgroundImage objects asynchronously load,
+// so they are first created in an unloaded state, then later emit
+// a ::loaded signal when the Cogl object becomes available.
+//
+// MetaBackgroundImageCache
+// A cache from filename to MetaBackgroundImage.
+//
+// BackgroundSource
+// An object that is created for each GSettings schema (separate
+// settings schemas are used for the lock screen and main background),
+// and holds a reference to shared Background objects.
+//
+// MetaBackground
+// Holds the specification of a background - a background color
+// or gradient and one or two images blended together.
+//
+// Background
+// JS delegate object that Connects a MetaBackground to the GSettings
+// schema for the background.
+//
+// Animation
+// A helper object that handles loading a XML-based animation; it is a
+// wrapper for GnomeDesktop.BGSlideShow
+//
+// MetaBackgroundActor
+// An actor that draws the background for a single monitor
+//
+// BackgroundCache
+// A cache of Settings schema => BackgroundSource and of a single Animation.
+// Also used to share file monitors.
+//
+// A static image, background color or gradient is relatively straightforward. The
+// calling code creates a separate BackgroundManager for each monitor. Since they
+// are created for the same GSettings schema, they will use the same BackgroundSource
+// object, which provides a single Background and correspondingly a single
+// MetaBackground object.
+//
+// BackgroundManager BackgroundManager
+// | \ / |
+// | BackgroundSource | looked up in BackgroundCache
+// | | |
+// | Background |
+// | | |
+// MetaBackgroundActor | MetaBackgroundActor
+// \ | /
+// `------- MetaBackground ------'
+// |
+// MetaBackgroundImage looked up in MetaBackgroundImageCache
+//
+// The animated case is tricker because the animation XML file can specify different
+// files for different monitor resolutions and aspect ratios. For this reason,
+// the BackgroundSource provides different Background share a single Animation object,
+// which tracks the animation, but use different MetaBackground objects. In the
+// common case, the different MetaBackground objects will be created for the
+// same filename and look up the *same* MetaBackgroundImage object, so there is
+// little wasted memory:
+//
+// BackgroundManager BackgroundManager
+// | \ / |
+// | BackgroundSource | looked up in BackgroundCache
+// | / \ |
+// | Background Background |
+// | | \ / | |
+// | | Animation | | looked up in BackgroundCache
+// MetaBackgroundA|tor Me|aBackgroundActor
+// \ | | /
+// MetaBackground MetaBackground
+// \ /
+// MetaBackgroundImage looked up in MetaBackgroundImageCache
+// MetaBackgroundImage
+//
+// But the case of different filenames and different background images
+// is possible as well:
+// ....
+// MetaBackground MetaBackground
+// | |
+// MetaBackgroundImage MetaBackgroundImage
+// MetaBackgroundImage MetaBackgroundImage
+
+const { Clutter, GDesktopEnums, Gio, GLib, GObject, GnomeDesktop, Meta } = imports.gi;
+const Signals = imports.signals;
+
+const LoginManager = imports.misc.loginManager;
+const Main = imports.ui.main;
+const Params = imports.misc.params;
+
+var DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
+
+const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
+const PRIMARY_COLOR_KEY = 'primary-color';
+const SECONDARY_COLOR_KEY = 'secondary-color';
+const COLOR_SHADING_TYPE_KEY = 'color-shading-type';
+const BACKGROUND_STYLE_KEY = 'picture-options';
+const PICTURE_URI_KEY = 'picture-uri';
+
+var FADE_ANIMATION_TIME = 1000;
+
+// These parameters affect how often we redraw.
+// The first is how different (percent crossfaded) the slide show
+// has to look before redrawing and the second is the minimum
+// frequency (in seconds) we're willing to wake up
+var ANIMATION_OPACITY_STEP_INCREMENT = 4.0;
+var ANIMATION_MIN_WAKEUP_INTERVAL = 1.0;
+
+let _backgroundCache = null;
+
+function _fileEqual0(file1, file2) {
+ if (file1 == file2)
+ return true;
+
+ if (!file1 || !file2)
+ return false;
+
+ return file1.equal(file2);
+}
+
+var BackgroundCache = class BackgroundCache {
+ constructor() {
+ this._fileMonitors = {};
+ this._backgroundSources = {};
+ this._animations = {};
+ }
+
+ monitorFile(file) {
+ let key = file.hash();
+ if (this._fileMonitors[key])
+ return;
+
+ let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
+ monitor.connect('changed',
+ (obj, theFile, otherFile, eventType) => {
+ // Ignore CHANGED and CREATED events, since in both cases
+ // we'll get a CHANGES_DONE_HINT event when done.
+ if (eventType != Gio.FileMonitorEvent.CHANGED &&
+ eventType != Gio.FileMonitorEvent.CREATED)
+ this.emit('file-changed', file);
+ });
+
+ this._fileMonitors[key] = monitor;
+ }
+
+ getAnimation(params) {
+ params = Params.parse(params, { file: null,
+ settingsSchema: null,
+ onLoaded: null });
+
+ let animation = this._animations[params.settingsSchema];
+ if (animation && _fileEqual0(animation.file, params.file)) {
+ if (params.onLoaded) {
+ let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ params.onLoaded(this._animations[params.settingsSchema]);
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded');
+ }
+ return;
+ }
+
+ animation = new Animation({ file: params.file });
+
+ animation.load(() => {
+ this._animations[params.settingsSchema] = animation;
+
+ if (params.onLoaded) {
+ let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ params.onLoaded(this._animations[params.settingsSchema]);
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded');
+ }
+ });
+ }
+
+ getBackgroundSource(layoutManager, settingsSchema) {
+ // The layoutManager is always the same one; we pass in it since
+ // Main.layoutManager may not be set yet
+
+ if (!(settingsSchema in this._backgroundSources)) {
+ this._backgroundSources[settingsSchema] = new BackgroundSource(layoutManager, settingsSchema);
+ this._backgroundSources[settingsSchema]._useCount = 1;
+ } else {
+ this._backgroundSources[settingsSchema]._useCount++;
+ }
+
+ return this._backgroundSources[settingsSchema];
+ }
+
+ releaseBackgroundSource(settingsSchema) {
+ if (settingsSchema in this._backgroundSources) {
+ let source = this._backgroundSources[settingsSchema];
+ source._useCount--;
+ if (source._useCount == 0) {
+ delete this._backgroundSources[settingsSchema];
+ source.destroy();
+ }
+ }
+ }
+};
+Signals.addSignalMethods(BackgroundCache.prototype);
+
+function getBackgroundCache() {
+ if (!_backgroundCache)
+ _backgroundCache = new BackgroundCache();
+ return _backgroundCache;
+}
+
+var Background = GObject.registerClass({
+ Signals: { 'loaded': {}, 'bg-changed': {} },
+}, class Background extends Meta.Background {
+ _init(params) {
+ params = Params.parse(params, { monitorIndex: 0,
+ layoutManager: Main.layoutManager,
+ settings: null,
+ file: null,
+ style: null });
+
+ super._init({ meta_display: global.display });
+
+ this._settings = params.settings;
+ this._file = params.file;
+ this._style = params.style;
+ this._monitorIndex = params.monitorIndex;
+ this._layoutManager = params.layoutManager;
+ this._fileWatches = {};
+ this._cancellable = new Gio.Cancellable();
+ this.isLoaded = false;
+
+ this._clock = new GnomeDesktop.WallClock();
+ this._timezoneChangedId = this._clock.connect('notify::timezone',
+ () => {
+ if (this._animation)
+ this._loadAnimation(this._animation.file);
+ });
+
+ let loginManager = LoginManager.getLoginManager();
+ this._prepareForSleepId = loginManager.connect('prepare-for-sleep',
+ (lm, aboutToSuspend) => {
+ if (aboutToSuspend)
+ return;
+ this._refreshAnimation();
+ });
+
+ this._settingsChangedSignalId =
+ this._settings.connect('changed', this._emitChangedSignal.bind(this));
+
+ this._load();
+ }
+
+ destroy() {
+ this._cancellable.cancel();
+ this._removeAnimationTimeout();
+
+ let i;
+ let keys = Object.keys(this._fileWatches);
+ for (i = 0; i < keys.length; i++)
+ this._cache.disconnect(this._fileWatches[keys[i]]);
+
+ this._fileWatches = null;
+
+ if (this._timezoneChangedId != 0)
+ this._clock.disconnect(this._timezoneChangedId);
+ this._timezoneChangedId = 0;
+
+ this._clock = null;
+
+ if (this._prepareForSleepId != 0)
+ LoginManager.getLoginManager().disconnect(this._prepareForSleepId);
+ this._prepareForSleepId = 0;
+
+ if (this._settingsChangedSignalId != 0)
+ this._settings.disconnect(this._settingsChangedSignalId);
+ this._settingsChangedSignalId = 0;
+
+ if (this._changedIdleId) {
+ GLib.source_remove(this._changedIdleId);
+ this._changedIdleId = 0;
+ }
+ }
+
+ _emitChangedSignal() {
+ if (this._changedIdleId)
+ return;
+
+ this._changedIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ this._changedIdleId = 0;
+ this.emit('bg-changed');
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(this._changedIdleId,
+ '[gnome-shell] Background._emitChangedSignal');
+ }
+
+ updateResolution() {
+ if (this._animation)
+ this._refreshAnimation();
+ }
+
+ _refreshAnimation() {
+ if (!this._animation)
+ return;
+
+ this._removeAnimationTimeout();
+ this._updateAnimation();
+ }
+
+ _setLoaded() {
+ if (this.isLoaded)
+ return;
+
+ this.isLoaded = true;
+
+ let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ this.emit('loaded');
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(id, '[gnome-shell] Background._setLoaded Idle');
+ }
+
+ _loadPattern() {
+ let colorString, res_, color, secondColor;
+
+ colorString = this._settings.get_string(PRIMARY_COLOR_KEY);
+ [res_, color] = Clutter.Color.from_string(colorString);
+ colorString = this._settings.get_string(SECONDARY_COLOR_KEY);
+ [res_, secondColor] = Clutter.Color.from_string(colorString);
+
+ let shadingType = this._settings.get_enum(COLOR_SHADING_TYPE_KEY);
+
+ if (shadingType == GDesktopEnums.BackgroundShading.SOLID)
+ this.set_color(color);
+ else
+ this.set_gradient(shadingType, color, secondColor);
+
+ this._setLoaded();
+ }
+
+ _watchFile(file) {
+ let key = file.hash();
+ if (this._fileWatches[key])
+ return;
+
+ this._cache.monitorFile(file);
+ let signalId = this._cache.connect('file-changed',
+ (cache, changedFile) => {
+ if (changedFile.equal(file)) {
+ let imageCache = Meta.BackgroundImageCache.get_default();
+ imageCache.purge(changedFile);
+ this._emitChangedSignal();
+ }
+ });
+ this._fileWatches[key] = signalId;
+ }
+
+ _removeAnimationTimeout() {
+ if (this._updateAnimationTimeoutId) {
+ GLib.source_remove(this._updateAnimationTimeoutId);
+ this._updateAnimationTimeoutId = 0;
+ }
+ }
+
+ _updateAnimation() {
+ this._updateAnimationTimeoutId = 0;
+
+ this._animation.update(this._layoutManager.monitors[this._monitorIndex]);
+ let files = this._animation.keyFrameFiles;
+
+ let finish = () => {
+ this._setLoaded();
+ if (files.length > 1) {
+ this.set_blend(files[0], files[1],
+ this._animation.transitionProgress,
+ this._style);
+ } else if (files.length > 0) {
+ this.set_file(files[0], this._style);
+ } else {
+ this.set_file(null, this._style);
+ }
+ this._queueUpdateAnimation();
+ };
+
+ let cache = Meta.BackgroundImageCache.get_default();
+ let numPendingImages = files.length;
+ for (let i = 0; i < files.length; i++) {
+ this._watchFile(files[i]);
+ let image = cache.load(files[i]);
+ if (image.is_loaded()) {
+ numPendingImages--;
+ if (numPendingImages == 0)
+ finish();
+ } else {
+ // eslint-disable-next-line no-loop-func
+ let id = image.connect('loaded', () => {
+ image.disconnect(id);
+ numPendingImages--;
+ if (numPendingImages == 0)
+ finish();
+ });
+ }
+ }
+ }
+
+ _queueUpdateAnimation() {
+ if (this._updateAnimationTimeoutId != 0)
+ return;
+
+ if (!this._cancellable || this._cancellable.is_cancelled())
+ return;
+
+ if (!this._animation.transitionDuration)
+ return;
+
+ let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT;
+ let timePerStep = (this._animation.transitionDuration * 1000) / nSteps;
+
+ let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000,
+ timePerStep);
+
+ if (interval > GLib.MAXUINT32)
+ return;
+
+ this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
+ interval,
+ () => {
+ this._updateAnimationTimeoutId = 0;
+ this._updateAnimation();
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(this._updateAnimationTimeoutId, '[gnome-shell] this._updateAnimation');
+ }
+
+ _loadAnimation(file) {
+ this._cache.getAnimation({
+ file,
+ settingsSchema: this._settings.schema_id,
+ onLoaded: animation => {
+ this._animation = animation;
+
+ if (!this._animation || this._cancellable.is_cancelled()) {
+ this._setLoaded();
+ return;
+ }
+
+ this._updateAnimation();
+ this._watchFile(file);
+ },
+ });
+ }
+
+ _loadImage(file) {
+ this.set_file(file, this._style);
+ this._watchFile(file);
+
+ let cache = Meta.BackgroundImageCache.get_default();
+ let image = cache.load(file);
+ if (image.is_loaded()) {
+ this._setLoaded();
+ } else {
+ let id = image.connect('loaded', () => {
+ this._setLoaded();
+ image.disconnect(id);
+ });
+ }
+ }
+
+ _loadFile(file) {
+ if (file.get_basename().endsWith('.xml'))
+ this._loadAnimation(file);
+ else
+ this._loadImage(file);
+ }
+
+ _load() {
+ this._cache = getBackgroundCache();
+
+ this._loadPattern();
+
+ if (!this._file) {
+ this._setLoaded();
+ return;
+ }
+
+ this._loadFile(this._file);
+ }
+});
+
+let _systemBackground;
+
+var SystemBackground = GObject.registerClass({
+ Signals: { 'loaded': {} },
+}, class SystemBackground extends Meta.BackgroundActor {
+ _init() {
+ if (_systemBackground == null) {
+ _systemBackground = new Meta.Background({ meta_display: global.display });
+ _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR);
+ }
+
+ super._init({
+ meta_display: global.display,
+ monitor: 0,
+ });
+ this.content.background = _systemBackground;
+
+ let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ this.emit('loaded');
+ return GLib.SOURCE_REMOVE;
+ });
+ GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded');
+ }
+});
+
+var BackgroundSource = class BackgroundSource {
+ constructor(layoutManager, settingsSchema) {
+ // Allow override the background image setting for performance testing
+ this._layoutManager = layoutManager;
+ this._overrideImage = GLib.getenv('SHELL_BACKGROUND_IMAGE');
+ this._settings = new Gio.Settings({ schema_id: settingsSchema });
+ this._backgrounds = [];
+
+ let monitorManager = Meta.MonitorManager.get();
+ this._monitorsChangedId =
+ monitorManager.connect('monitors-changed',
+ this._onMonitorsChanged.bind(this));
+ }
+
+ _onMonitorsChanged() {
+ for (let monitorIndex in this._backgrounds) {
+ let background = this._backgrounds[monitorIndex];
+
+ if (monitorIndex < this._layoutManager.monitors.length) {
+ background.updateResolution();
+ } else {
+ background.disconnect(background._changedId);
+ background.destroy();
+ delete this._backgrounds[monitorIndex];
+ }
+ }
+ }
+
+ getBackground(monitorIndex) {
+ let file = null;
+ let style;
+
+ // We don't watch changes to settings here,
+ // instead we rely on Background to watch those
+ // and emit 'bg-changed' at the right time
+
+ if (this._overrideImage != null) {
+ file = Gio.File.new_for_path(this._overrideImage);
+ style = GDesktopEnums.BackgroundStyle.ZOOM; // Hardcode
+ } else {
+ style = this._settings.get_enum(BACKGROUND_STYLE_KEY);
+ if (style != GDesktopEnums.BackgroundStyle.NONE) {
+ let uri = this._settings.get_string(PICTURE_URI_KEY);
+ file = Gio.File.new_for_commandline_arg(uri);
+ }
+ }
+
+ // Animated backgrounds are (potentially) per-monitor, since
+ // they can have variants that depend on the aspect ratio and
+ // size of the monitor; for other backgrounds we can use the
+ // same background object for all monitors.
+ if (file == null || !file.get_basename().endsWith('.xml'))
+ monitorIndex = 0;
+
+ if (!(monitorIndex in this._backgrounds)) {
+ let background = new Background({
+ monitorIndex,
+ layoutManager: this._layoutManager,
+ settings: this._settings,
+ file,
+ style,
+ });
+
+ background._changedId = background.connect('bg-changed', () => {
+ background.disconnect(background._changedId);
+ background.destroy();
+ delete this._backgrounds[monitorIndex];
+ });
+
+ this._backgrounds[monitorIndex] = background;
+ }
+
+ return this._backgrounds[monitorIndex];
+ }
+
+ destroy() {
+ let monitorManager = Meta.MonitorManager.get();
+ monitorManager.disconnect(this._monitorsChangedId);
+
+ for (let monitorIndex in this._backgrounds) {
+ let background = this._backgrounds[monitorIndex];
+ background.disconnect(background._changedId);
+ background.destroy();
+ }
+
+ this._backgrounds = null;
+ }
+};
+
+var Animation = GObject.registerClass(
+class Animation extends GnomeDesktop.BGSlideShow {
+ _init(params) {
+ super._init(params);
+
+ this.keyFrameFiles = [];
+ this.transitionProgress = 0.0;
+ this.transitionDuration = 0.0;
+ this.loaded = false;
+ }
+
+ load(callback) {
+ this.load_async(null, () => {
+ this.loaded = true;
+ if (callback)
+ callback();
+ });
+ }
+
+ update(monitor) {
+ this.keyFrameFiles = [];
+
+ if (this.get_num_slides() < 1)
+ return;
+
+ let [progress, duration, isFixed_, filename1, filename2] =
+ this.get_current_slide(monitor.width, monitor.height);
+
+ this.transitionDuration = duration;
+ this.transitionProgress = progress;
+
+ if (filename1)
+ this.keyFrameFiles.push(Gio.File.new_for_path(filename1));
+
+ if (filename2)
+ this.keyFrameFiles.push(Gio.File.new_for_path(filename2));
+ }
+});
+
+var BackgroundManager = class BackgroundManager {
+ constructor(params) {
+ params = Params.parse(params, { container: null,
+ layoutManager: Main.layoutManager,
+ monitorIndex: null,
+ vignette: false,
+ controlPosition: true,
+ settingsSchema: BACKGROUND_SCHEMA });
+
+ let cache = getBackgroundCache();
+ this._settingsSchema = params.settingsSchema;
+ this._backgroundSource = cache.getBackgroundSource(params.layoutManager, params.settingsSchema);
+
+ this._container = params.container;
+ this._layoutManager = params.layoutManager;
+ this._vignette = params.vignette;
+ this._monitorIndex = params.monitorIndex;
+ this._controlPosition = params.controlPosition;
+
+ this.backgroundActor = this._createBackgroundActor();
+ this._newBackgroundActor = null;
+ }
+
+ destroy() {
+ let cache = getBackgroundCache();
+ cache.releaseBackgroundSource(this._settingsSchema);
+ this._backgroundSource = null;
+
+ if (this._newBackgroundActor) {
+ this._newBackgroundActor.destroy();
+ this._newBackgroundActor = null;
+ }
+
+ if (this.backgroundActor) {
+ this.backgroundActor.destroy();
+ this.backgroundActor = null;
+ }
+ }
+
+ _swapBackgroundActor() {
+ let oldBackgroundActor = this.backgroundActor;
+ this.backgroundActor = this._newBackgroundActor;
+ this._newBackgroundActor = null;
+ this.emit('changed');
+
+ oldBackgroundActor.ease({
+ opacity: 0,
+ duration: FADE_ANIMATION_TIME,
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+ onComplete: () => oldBackgroundActor.destroy(),
+ });
+ }
+
+ _updateBackgroundActor() {
+ if (this._newBackgroundActor) {
+ /* Skip displaying existing background queued for load */
+ this._newBackgroundActor.destroy();
+ this._newBackgroundActor = null;
+ }
+
+ let newBackgroundActor = this._createBackgroundActor();
+
+ const oldContent = this.backgroundActor.content;
+ const newContent = newBackgroundActor.content;
+
+ newContent.vignette_sharpness = oldContent.vignette_sharpness;
+ newContent.brightness = oldContent.brightness;
+
+ newBackgroundActor.visible = this.backgroundActor.visible;
+
+ this._newBackgroundActor = newBackgroundActor;
+
+ const { background } = newBackgroundActor.content;
+
+ if (background.isLoaded) {
+ this._swapBackgroundActor();
+ } else {
+ newBackgroundActor.loadedSignalId = background.connect('loaded',
+ () => {
+ background.disconnect(newBackgroundActor.loadedSignalId);
+ newBackgroundActor.loadedSignalId = 0;
+
+ this._swapBackgroundActor();
+ });
+ }
+ }
+
+ _createBackgroundActor() {
+ let background = this._backgroundSource.getBackground(this._monitorIndex);
+ let backgroundActor = new Meta.BackgroundActor({
+ meta_display: global.display,
+ monitor: this._monitorIndex,
+ });
+ backgroundActor.content.set({
+ background,
+ vignette: this._vignette,
+ vignette_sharpness: 0.5,
+ brightness: 0.5,
+ });
+
+ this._container.add_child(backgroundActor);
+
+ if (this._controlPosition) {
+ let monitor = this._layoutManager.monitors[this._monitorIndex];
+ backgroundActor.set_position(monitor.x, monitor.y);
+ this._container.set_child_below_sibling(backgroundActor, null);
+ }
+
+ let changeSignalId = background.connect('bg-changed', () => {
+ background.disconnect(changeSignalId);
+ changeSignalId = null;
+ this._updateBackgroundActor();
+ });
+
+ let loadedSignalId;
+ if (background.isLoaded) {
+ GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
+ this.emit('loaded');
+ return GLib.SOURCE_REMOVE;
+ });
+ } else {
+ loadedSignalId = background.connect('loaded', () => {
+ background.disconnect(loadedSignalId);
+ loadedSignalId = null;
+ this.emit('loaded');
+ });
+ }
+
+ backgroundActor.connect('destroy', () => {
+ if (changeSignalId)
+ background.disconnect(changeSignalId);
+
+ if (loadedSignalId)
+ background.disconnect(loadedSignalId);
+
+ if (backgroundActor.loadedSignalId)
+ background.disconnect(backgroundActor.loadedSignalId);
+ });
+
+ return backgroundActor;
+ }
+};
+Signals.addSignalMethods(BackgroundManager.prototype);