[gnome-shell] layout: rework background handling



commit 3c8325f1f30b1b4027533cef7bf4a4bc21f15e0c
Author: Ray Strode <rstrode redhat com>
Date:   Mon Dec 24 15:20:39 2012 +0100

    layout: rework background handling
    
    This commit updates the code to use mutter's new background
    api, and changes the shell's startup animation to be closer
    to the mockups.
    
    Based on initial work by Giovanni Campagna
    
    https://bugzilla.gnome.org/show_bug.cgi?id=682429

 js/Makefile.am              |    1 +
 js/ui/background.js         |  506 +++++++++++++++++++++++++++++++++++++++++++
 js/ui/layout.js             |  175 ++++++++++++---
 js/ui/main.js               |    4 -
 js/ui/overview.js           |  109 ++++++----
 js/ui/screenShield.js       |   83 +++----
 js/ui/workspaceThumbnail.js |   16 +-
 src/shell-global.c          |   11 -
 8 files changed, 764 insertions(+), 141 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index 8b19bcb..908d515 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -40,6 +40,7 @@ nobase_dist_js_DATA =         \
        ui/appDisplay.js        \
        ui/appFavorites.js      \
        ui/backgroundMenu.js    \
+       ui/background.js        \
        ui/boxpointer.js        \
        ui/calendar.js          \
        ui/checkBox.js          \
diff --git a/js/ui/background.js b/js/ui/background.js
new file mode 100644
index 0000000..e6a8d23
--- /dev/null
+++ b/js/ui/background.js
@@ -0,0 +1,506 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Clutter = imports.gi.Clutter;
+const GDesktopEnums = imports.gi.GDesktopEnums;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Lang = imports.lang;
+const Meta = imports.gi.Meta;
+const Signals = imports.signals;
+
+const Main = imports.ui.main;
+const Params = imports.misc.params;
+const Tweener = imports.ui.tweener;
+
+const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
+const DRAW_BACKGROUND_KEY = 'draw-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_OPACITY_KEY = 'picture-opacity';
+const PICTURE_URI_KEY = 'picture-uri';
+
+const FADE_ANIMATION_TIME = 1.0;
+
+let _backgroundCache = null;
+
+const BackgroundCache = new Lang.Class({
+    Name: 'BackgroundCache',
+
+    _init: function() {
+       this._patterns = [];
+       this._images = [];
+       this._fileMonitors = {};
+    },
+
+    getPatternContent: function(params) {
+        params = Params.parse(params, { monitorIndex: 0,
+                                        color: null,
+                                        secondColor: null,
+                                        shadingType: null,
+                                        effects: Meta.BackgroundEffects.NONE });
+
+        let content = null;
+        let candidateContent = null;
+        for (let i = 0; i < this._patterns.length; i++) {
+            if (!this._patterns[i])
+                continue;
+
+            if (this._patterns[i].get_shading() != params.shadingType)
+                continue;
+
+            if (!params.color.equal(this._patterns[i].get_color()))
+                continue;
+
+            if (params.shadingType != GDesktopEnums.BackgroundShading.SOLID &&
+                !params.secondColor.equal(this._patterns[i].get_second_color()))
+                continue;
+
+            candidateContent = this._patterns[i];
+
+            if (params.effects != this._patterns[i].effects)
+                continue;
+
+            break;
+        }
+
+        if (candidateContent) {
+            content = candidateContent.copy(params.monitorIndex, params.effects);
+        } else {
+            content = new Meta.Background({ meta_screen: global.screen,
+                                            monitor: params.monitorIndex,
+                                            effects: params.effects });
+
+            if (params.shadingType == GDesktopEnums.BackgroundShading.SOLID) {
+                content.load_color(params.color);
+            } else {
+                content.load_gradient(params.shadingType, params.color, params.secondColor);
+            }
+
+            this._patterns.push(content);
+        }
+
+        return content;
+    },
+
+    _monitorFile: function(filename) {
+        if (this._fileMonitors[filename])
+            return;
+
+        let file = Gio.File.new_for_path(filename);
+        let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
+
+        let signalId = monitor.connect('changed',
+                                       Lang.bind(this, function() {
+                                           for (let i = 0; i < this._images.length; i++) {
+                                               if (this._images[i].get_filename() == filename)
+                                                   this._images.splice(i, 1);
+                                           }
+
+                                           monitor.disconnect(signalId);
+
+                                           this.emit('file-changed', filename);
+                                       }));
+
+        this._fileMonitors[filename] = monitor;
+    },
+
+    _removeContent: function(contentList, content) {
+        let index = contentList.indexOf(content);
+
+        if (index >= 0)
+            contentList.splice(index, 1);
+    },
+
+    removePatternContent: function(content) {
+        this._removeContent(this._patterns, content);
+    },
+
+    removeImageContent: function(content) {
+        this._removeContent(this._images, content);
+    },
+
+    getImageContent: function(params) {
+        params = Params.parse(params, { monitorIndex: 0,
+                                        style: null,
+                                        filename: null,
+                                        effects: Meta.BackgroundEffects.NONE,
+                                        cancellable: null,
+                                        onFinished: null });
+
+        let content = null;
+        let candidateContent = null;
+        for (let i = 0; i < this._images.length; i++) {
+            if (!this._images[i])
+                continue;
+
+            if (this._images[i].get_style() != params.style)
+                continue;
+
+            if (this._images[i].get_filename() != params.filename)
+                continue;
+
+            if (params.style == GDesktopEnums.BackgroundStyle.SPANNED &&
+                this._images[i].monitor_index != this._monitorIndex)
+                continue;
+
+            candidateContent = this._images[i];
+
+            if (params.effects != this._images[i].effects)
+                continue;
+
+            break;
+        }
+
+        if (candidateContent) {
+            content = candidateContent.copy(params.monitorIndex, params.effects);
+
+            if (params.onFinished)
+                params.onFinished(content);
+        } else {
+            content = new Meta.Background({ meta_screen: global.screen,
+                                            monitor: params.monitorIndex,
+                                            effects: params.effects });
+
+            content.load_file_async(params.filename,
+                                    params.style,
+                                    params.cancellable,
+                                    Lang.bind(this,
+                                              function(object, result) {
+                                                  try {
+                                                      content.load_file_finish(result);
+
+                                                      this._monitorFile(params.filename);
+                                                      this._images.push(content);
+                                                  } catch(e) {
+                                                       content = null;
+                                                  }
+
+                                                  if (params.onFinished)
+                                                      params.onFinished(content);
+                                              }));
+        }
+    }
+});
+Signals.addSignalMethods(BackgroundCache.prototype);
+
+function getBackgroundCache() {
+    if (!_backgroundCache)
+        _backgroundCache = new BackgroundCache();
+    return _backgroundCache;
+}
+
+const Background = new Lang.Class({
+    Name: 'Background',
+
+    _init: function(params) {
+        params = Params.parse(params, { monitorIndex: 0,
+                                        effects: Meta.BackgroundEffects.NONE });
+        this.actor = new Meta.BackgroundGroup();
+        this.actor._delegate = this;
+
+        this._destroySignalId = this.actor.connect('destroy',
+                                                   Lang.bind(this, this._destroy));
+
+        this._settings = new Gio.Settings({ schema: BACKGROUND_SCHEMA });
+        this._monitorIndex = params.monitorIndex;
+        this._effects = params.effects;
+        this._fileWatches = {};
+        this._pattern = null;
+        this._image = null;
+
+        this._brightness = 1.0;
+        this._vignetteSharpness = 0.2;
+        this._saturation = 1.0;
+        this._cancellable = new Gio.Cancellable();
+        this.isLoaded = false;
+
+        this._settings.connect('changed', Lang.bind(this, function() {
+                                   this.emit('changed');
+                               }));
+
+        this._load();
+    },
+
+    _destroy: function() {
+        this._cancellable.cancel();
+        this._cancellable = null;
+
+        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._pattern) {
+            if (this._pattern.content)
+                this._cache.removePatternContent(this._pattern.content);
+
+            this._pattern.destroy();
+            this._pattern = null;
+        }
+
+        if (this._image) {
+            if (this._image.content)
+                this._cache.removeImageContent(this._image.content);
+
+            this._image.destroy();
+            this._image = null;
+        }
+
+        this.actor.disconnect(this._destroySignalId);
+        this._destroySignalId = 0;
+        this.actor.destroy();
+    },
+
+    _setLoaded: function() {
+        if (this.isLoaded)
+            return;
+
+        this.isLoaded = true;
+
+        GLib.idle_add(GLib.PRIORITY_DEFAULT, Lang.bind(this, function() {
+            this.emit('loaded');
+            return false;
+        }));
+    },
+
+    _loadPattern: function() {
+        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);
+
+        let content = this._cache.getPatternContent({ monitorIndex: this._monitorIndex,
+                                                      effects: this._effects,
+                                                      color: color,
+                                                      secondColor: secondColor,
+                                                      shadingType: shadingType });
+
+        this._pattern = new Meta.BackgroundActor();
+        this.actor.add_child(this._pattern);
+
+        this._pattern.content = content;
+    },
+
+    _watchCacheFile: function(filename) {
+        if (this._fileWatches[filename])
+            return;
+
+        let signalId = this._cache.connect('file-changed',
+                                           Lang.bind(this, function(cache, changedFile) {
+                                               if (changedFile == filename) {
+                                                   this.emit('changed');
+                                               }
+                                           }));
+        this._fileWatches[filename] = signalId;
+    },
+
+    _setImage: function(content, filename) {
+        content.saturation = this._saturation;
+        content.brightness = this._brightness;
+        content.vignette_sharpness = this._vignetteSharpness;
+
+        this._image = new Meta.BackgroundActor();
+        this._image.content = content;
+        this.actor.add_child(this._image);
+        this._watchCacheFile(filename);
+    },
+
+    _loadFile: function(filename) {
+        this._cache.getImageContent({ monitorIndex: this._monitorIndex,
+                                      effects: this._effects,
+                                      style: this._style,
+                                      filename: filename,
+                                      cancellable: this._cancellable,
+                                      onFinished: Lang.bind(this, function(content) {
+                                          if (!content) {
+                                              return;
+                                          }
+
+                                          this._setImage(content, filename);
+                                          this._setLoaded();
+                                      })
+                                    });
+
+    },
+
+    _load: function () {
+        if (!this._settings.get_boolean(DRAW_BACKGROUND_KEY)) {
+            this._setLoaded();
+            return;
+        }
+
+        this._cache = getBackgroundCache();
+
+        this._loadPattern(this._cache);
+
+        this._style = this._settings.get_enum(BACKGROUND_STYLE_KEY);
+        if (this._style == GDesktopEnums.BackgroundStyle.NONE) {
+            this._setLoaded();
+            return;
+        }
+
+        let uri = this._settings.get_string(PICTURE_URI_KEY);
+        let filename = Gio.File.new_for_uri(uri).get_path();
+
+        this._loadFile(filename);
+    },
+
+    get saturation() {
+        return this._saturation;
+    },
+
+    set saturation(saturation) {
+        this._saturation = saturation;
+
+        if (this._pattern && this._pattern.content)
+            this._pattern.content.saturation = saturation;
+
+        if (this._image && this._image.content)
+            this._image.content.saturation = saturation;
+    },
+
+    get brightness() {
+        return this._brightness;
+    },
+
+    set brightness(factor) {
+        this._brightness = factor;
+        if (this._pattern && this._pattern.content)
+            this._pattern.content.brightness = factor;
+
+        if (this._image && this._image.content)
+            this._image.content.brightness = factor;
+    },
+
+    get vignetteSharpness() {
+        return this._vignetteSharpness;
+    },
+
+    set vignetteSharpness(sharpness) {
+        this._vignetteSharpness = sharpness;
+        if (this._pattern && this._pattern.content)
+            this._pattern.content.vignette_sharpness = sharpness;
+
+        if (this._image && this._image.content)
+            this._image.content.vignette_sharpness = sharpness;
+    }
+});
+Signals.addSignalMethods(Background.prototype);
+
+const StillFrame = new Lang.Class({
+    Name: 'StillFrame',
+
+    _init: function(monitorIndex) {
+        this.actor = new Meta.BackgroundActor();
+        this.actor._delegate = this;
+
+        let content = new Meta.Background({ meta_screen: global.screen,
+                                            monitor: monitorIndex,
+                                            effects: Meta.BackgroundEffects.NONE });
+        content.load_still_frame();
+
+        this.actor.content = content;
+    }
+});
+Signals.addSignalMethods(StillFrame.prototype);
+
+const BackgroundManager = new Lang.Class({
+    Name: 'BackgroundManager',
+
+    _init: function(params) {
+        params = Params.parse(params, { container: null,
+                                        layoutManager: Main.layoutManager,
+                                        monitorIndex: null,
+                                        effects: Meta.BackgroundEffects.NONE });
+
+        this._container = params.container;
+        this._layoutManager = params.layoutManager;
+        this._effects = params.effects;
+        this._monitorIndex = params.monitorIndex;
+
+        this.background = this._createBackground();
+        this._newBackground = null;
+        this._loadedSignalId = 0;
+        this._changedSignalId = 0;
+    },
+
+    destroy: function() {
+        if (this._loadedSignalId)
+            this._newBackground.disconnect(this._loadedSignalId);
+
+        if (this._changedSignalId)
+            this.background.disconnect(this._changedSignalId);
+
+        if (this._newBackground) {
+            let container = this._newBackground.actor.get_parent();
+            if (container)
+                container.remove_actor(this._newBackground.actor);
+            this._newBackground = null;
+        }
+
+        if (this.background) {
+            let container = this.background.actor.get_parent();
+            if (container)
+                container.remove_actor(this.background.actor);
+            this.background = null;
+        }
+    },
+
+    _updateBackground: function(background, monitorIndex) {
+        let newBackground = this._createBackground(monitorIndex);
+        newBackground.vignetteSharpness = background.vignetteSharpness;
+        newBackground.brightness = background.brightness;
+        newBackground.saturation = background.saturation;
+        newBackground.visible = background.visible;
+
+        let signalId = newBackground.connect('loaded',
+            Lang.bind(this, function() {
+                newBackground.disconnect(signalId);
+                Tweener.addTween(background.actor,
+                                 { opacity: 0,
+                                   time: FADE_ANIMATION_TIME,
+                                   transition: 'easeOutQuad',
+                                   onComplete: Lang.bind(this, function() {
+                                       this.background = newBackground;
+                                       this._newBackground = null;
+                                       this._container.remove_actor(background.actor);
+                                       this.emit('changed');
+                                   })
+                                 });
+        }));
+        this._loadedSignalId = signalId;
+
+        this._newBackground = newBackground;
+    },
+
+    _createBackground: function() {
+        let background = new Background({ monitorIndex: this._monitorIndex,
+                                          layoutManager: this._layoutManager,
+                                          effects: this._effects });
+        this._container.add_child(background.actor);
+
+        let monitor = this._layoutManager.monitors[this._monitorIndex];
+        background.actor.set_position(monitor.x, monitor.y);
+        background.actor.set_size(monitor.width, monitor.height);
+        background.actor.lower_bottom();
+
+        let signalId = background.connect('changed', Lang.bind(this, function() {
+            background.disconnect(signalId);
+            this._updateBackground(background, this._monitorIndex);
+        }));
+
+        this._changedSignalId = signalId;
+
+        return background;
+    },
+});
+Signals.addSignalMethods(BackgroundManager.prototype);
diff --git a/js/ui/layout.js b/js/ui/layout.js
index 357a986..12a9e6e 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -9,15 +9,17 @@ const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const St = imports.gi.St;
 
+const Background = imports.ui.background;
+const BackgroundMenu = imports.ui.backgroundMenu;
+
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const Params = imports.misc.params;
 const Tweener = imports.ui.tweener;
 
 const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
-const STARTUP_ANIMATION_TIME = 0.2;
+const STARTUP_ANIMATION_TIME = 0.5;
 const KEYBOARD_ANIMATION_TIME = 0.15;
-const PLYMOUTH_TRANSITION_TIME = 1;
 const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
 
 // The message tray takes this much pressure
@@ -128,7 +130,6 @@ const LayoutManager = new Lang.Class({
         this.primaryIndex = -1;
         this._keyboardIndex = -1;
         this._hotCorners = [];
-        this._background = null;
         this._leftPanelBarrier = null;
         this._rightPanelBarrier = null;
         this._trayBarrier = null;
@@ -207,6 +208,14 @@ const LayoutManager = new Lang.Class({
         global.stage.remove_actor(global.top_window_group);
         this.uiGroup.add_actor(global.top_window_group);
 
+        this._consoleBackgroundGroup = new Meta.BackgroundGroup();
+        global.stage.insert_child_below(this._consoleBackgroundGroup, null);
+
+        this._backgroundGroup = new Meta.BackgroundGroup();
+        global.window_group.add_child(this._backgroundGroup);
+        this._backgroundGroup.lower_bottom();
+        this._bgManagers = [];
+
         // This blocks the XDND picks from finding the activities button
         // and we never attempt to pick anything from it anyway so make
         // it invisible from picks
@@ -220,6 +229,7 @@ const LayoutManager = new Lang.Class({
         global.screen.connect('monitors-changed',
                               Lang.bind(this, this._monitorsChanged));
         this._monitorsChanged();
+        this._prepareStartupAnimation();
     },
 
     // This is called by Main after everything else is constructed;
@@ -328,6 +338,56 @@ const LayoutManager = new Lang.Class({
         }
     },
 
+    _createBackground: function(monitorIndex) {
+        let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
+                                                           layoutManager: this,
+                                                           monitorIndex: monitorIndex });
+
+        bgManager.connect('changed', Lang.bind(this, function() {
+                              BackgroundMenu.addBackgroundMenu(bgManager.background.actor);
+                          }));
+
+        this._bgManagers.push(bgManager);
+
+        return bgManager.background;
+    },
+
+    _createSecondaryBackgrounds: function() {
+        for (let i = 0; i < this.monitors.length; i++) {
+            if (i != this.primaryIndex) {
+                let background = this._createBackground(i);
+
+                background.actor.opacity = 0;
+                Tweener.addTween(background.actor,
+                                 { opacity: 255,
+                                   time: BACKGROUND_FADE_ANIMATION_TIME,
+                                   transition: 'easeOutQuad' });
+            }
+        }
+    },
+
+    _createPrimaryBackground: function() {
+        this._createBackground(this.primaryIndex);
+    },
+
+    _updateBackgrounds: function() {
+        let i;
+        for (i = 0; i < this._bgManagers.length; i++)
+            this._bgManagers[i].destroy();
+
+        this._bgManagers = [];
+
+        if (Main.sessionMode.isGreeter)
+            return;
+
+        if (this._startingUp)
+            return;
+
+        for (let i = 0; i < this.monitors.length; i++) {
+            this._createBackground(i);
+        }
+    },
+
     _updateBoxes: function() {
         this.screenShieldGroup.set_position(0, 0);
         this.screenShieldGroup.set_size(global.screen_width, global.screen_height);
@@ -402,6 +462,7 @@ const LayoutManager = new Lang.Class({
         this._updateBoxes();
         this._updateTrayBarrier();
         this._updateHotCorners();
+        this._updateBackgrounds();
         this._updateFullscreen();
         this._updateVisibility();
         this._queueUpdateRegions();
@@ -464,53 +525,95 @@ const LayoutManager = new Lang.Class({
         return this._keyboardIndex;
     },
 
-    prepareStartupAnimation: function() {
-        this.panelBox.translation_y = -this.panelBox.height;
-    },
+    // Startup Animations
+    //
+    // We have two different animations, depending on whether we're a greeter
+    // or a normal session.
+    //
+    // In the greeter, we want to animate the panel from the top, and smoothly
+    // fade the login dialog on top of whatever plymouth left on screen which
+    // we get as a still frame background before drawing anything else.
+    //
+    // Here we just have the code to animate the panel, and fade up the background.
+    // The login dialog animation is handled by modalDialog.js
+    //
+    // When starting a normal user session, we want to grow it out of the middle
+    // of the screen.
+    //
+    // Usually, we don't want to paint the stage background color because the
+    // MetaBackgroundActor inside global.window_group covers the entirety of the
+    // screen. So, we set no_clear_hint at the end of the animation.
 
-    startupAnimation: function() {
-        let plymouthTransitionRunning = false;
+    _prepareStartupAnimation: function() {
+        // Set ourselves to FULLSCREEN input mode while the animation is running
+        // so events don't get delivered to X11 windows (which are distorted by the animation)
+        global.stage_input_mode = Shell.StageInputMode.FULLSCREEN;
 
-        // If we're the greeter, put up the xrootpmap actor
-        // and fade it out to have a nice transition from plymouth
-        // to the greeter. Otherwise, we'll just animate the panel,
-        // as usual.
-        if (Main.sessionMode.isGreeter) {
-            this._background = Meta.BackgroundActor.new_for_screen(global.screen);
-            if (this._background != null) {
-                this.uiGroup.add_actor(this._background);
-                Tweener.addTween(this._background,
-                                 { opacity: 0,
-                                   time: PLYMOUTH_TRANSITION_TIME,
-                                   transition: 'linear',
-                                   onComplete: this._fadeBackgroundComplete,
-                                   onCompleteScope: this });
-                plymouthTransitionRunning = true;
-            }
+        // build new backgrounds
+        for (let i = 0; i < this.monitors.length; i++) {
+            let monitor = this.monitors[i];
+
+            let stillFrame = new Background.StillFrame(i);
+            this._consoleBackgroundGroup.add_child(stillFrame.actor);
+
+            stillFrame.actor.set_size(this.monitors[i].width, this.monitors[i].height);
+            stillFrame.actor.set_position(this.monitors[i].x, this.monitors[i].y);
         }
 
-        if (!plymouthTransitionRunning)
-            this._fadeBackgroundComplete();
+        if (Main.sessionMode.isGreeter) {
+            this.panelBox.translation_y = -this.panelBox.height;
+        } else {
+            let monitor = this.primaryMonitor;
+            let x = monitor.x + monitor.width / 2.0;
+            let y = monitor.y + monitor.height / 2.0;
+
+            this.uiGroup.set_pivot_point(x / global.screen_width,
+                                         y / global.screen_height);
+            this.uiGroup.scale_x = this.uiGroup.scale_y = 0;
+        }
     },
 
-    _fadeBackgroundComplete: function() {
-        this._freezeUpdateRegions();
+    startupAnimation: function() {
+        if (Main.sessionMode.isGreeter)
+            this._startupAnimationGreeter();
+        else
+            this._startupAnimationSession();
+    },
 
-        if (this._background != null) {
-            this._background.destroy();
-            this._background = null;
-        }
+    _startupAnimationGreeter: function() {
+         this._freezeUpdateRegions();
+         Tweener.addTween(this.panelBox,
+                          { translation_y: 0,
+                            time: STARTUP_ANIMATION_TIME,
+                            transition: 'easeOutQuad',
+                            onComplete: this._startupAnimationComplete,
+                            onCompleteScope: this });
+    },
 
-        Tweener.addTween(this.panelBox,
-                         { translation_y: 0,
+    _startupAnimationSession: function() {
+        this._freezeUpdateRegions();
+        this._createPrimaryBackground();
+        Tweener.addTween(this.uiGroup,
+                         { scale_x: 1,
+                           scale_y: 1,
                            time: STARTUP_ANIMATION_TIME,
                            transition: 'easeOutQuad',
                            onComplete: this._startupAnimationComplete,
-                           onCompleteScope: this
-                         });
+                           onCompleteScope: this });
     },
 
     _startupAnimationComplete: function() {
+        // At this point, the UI group is covering everything, so
+        // we no longer need to clear the stage
+        global.stage.no_clear_hint = true;
+
+        global.stage_input_mode = Shell.StageInputMode.NORMAL;
+
+        this._consoleBackgroundGroup.destroy();
+
+        if (Main.sessionMode.isGreeter)
+            this._createSecondaryBackgrounds();
+
         this.emit('panel-box-changed');
         this._thawUpdateRegions();
     },
diff --git a/js/ui/main.js b/js/ui/main.js
index 22edfb9..fa42f25 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -10,7 +10,6 @@ const Meta = imports.gi.Meta;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
 
-const BackgroundMenu = imports.ui.backgroundMenu;
 const Components = imports.ui.components;
 const CtrlAltTab = imports.ui.ctrlAltTab;
 const EndSessionDialog = imports.ui.endSessionDialog;
@@ -152,10 +151,7 @@ function startSession() {
     windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
     componentManager = new Components.ComponentManager();
 
-    BackgroundMenu.addBackgroundMenu(global.background_actor);
-
     layoutManager.init();
-    layoutManager.prepareStartupAnimation();
     overview.init();
 
     global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT,
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 263e2b7..755dfb4 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -10,6 +10,7 @@ const St = imports.gi.St;
 const Shell = imports.gi.Shell;
 const Gdk = imports.gi.Gdk;
 
+const Background = imports.ui.background;
 const Dash = imports.ui.dash;
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
@@ -24,25 +25,12 @@ const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
 // Time for initial animation going into Overview mode
 const ANIMATION_TIME = 0.25;
 
-const DND_WINDOW_SWITCH_TIMEOUT = 1250;
+// Must be less than ANIMATION_TIME, since we switch to
+// or from the overview completely after ANIMATION_TIME,
+// and don't want the shading animation to get cut off
+const SHADE_ANIMATION_TIME = .20;
 
-const GLSL_DIM_EFFECT_DECLARATIONS = '';
-const GLSL_DIM_EFFECT_CODE = '\
-   vec2 dist = cogl_tex_coord_in[0].xy - vec2(0.5, 0.5); \
-   float elipse_radius = 0.5; \
-   /* from https://bugzilla.gnome.org/show_bug.cgi?id=669798: \
-      the alpha on the gradient goes from 250 at its darkest to 180 at its most transparent. */ \
-   float y = 250.0 / 255.0; \
-   float x = 180.0 / 255.0; \
-   /* interpolate darkening value, based on distance from screen center */ \
-   float val = min(length(dist), elipse_radius); \
-   float a = mix(x, y, val / elipse_radius); \
-   /* dim_factor varies from [1.0 -> 0.5] when overview is showing \
-      We use it to smooth value, then we clamp it to valid color interval */ \
-   a = clamp(a - cogl_color_in.r + 0.5, 0.0, 1.0); \
-   /* We\'re blending between: color and black color (obviously omitted in the equation) */ \
-   cogl_color_out.xyz = cogl_color_out.xyz * (1.0 - a); \
-   cogl_color_out.a = 1.0;';
+const DND_WINDOW_SWITCH_TIMEOUT = 1250;
 
 const ShellInfo = new Lang.Class({
     Name: 'ShellInfo',
@@ -118,18 +106,12 @@ const Overview = new Lang.Class({
 
         this._overviewCreated = true;
 
-        // The main BackgroundActor is inside global.window_group which is
+        // The main Background actors are inside global.window_group which are
         // hidden when displaying the overview, so we create a new
         // one. Instances of this class share a single CoglTexture behind the
         // scenes which allows us to show the background with different
         // rendering options without duplicating the texture data.
-        this._background = Meta.BackgroundActor.new_for_screen(global.screen);
-        this._background.add_glsl_snippet(Meta.SnippetHook.FRAGMENT,
-                                          GLSL_DIM_EFFECT_DECLARATIONS,
-                                          GLSL_DIM_EFFECT_CODE,
-                                          false);
-        this._background.hide();
-        global.overlay_group.add_actor(this._background);
+        let monitor = Main.layoutManager.primaryMonitor;
 
         this._desktopFade = new St.Bin();
         global.overlay_group.add_actor(this._desktopFade);
@@ -145,6 +127,11 @@ const Overview = new Lang.Class({
         this._group = new St.BoxLayout({ name: 'overview-group',
                                          clip_to_allocation: true });
 
+        this._backgroundGroup = new Meta.BackgroundGroup();
+        global.overlay_group.add_child(this._backgroundGroup);
+        this._backgroundGroup.hide();
+        this._bgManagers = [];
+
         this._capturedEventId = 0;
         this._buttonPressId = 0;
 
@@ -188,6 +175,56 @@ const Overview = new Lang.Class({
             this.init();
     },
 
+    _updateBackgrounds: function() {
+        for (let i = 0; i < this._bgManagers.length; i++)
+            this._bgManagers[i].destroy();
+
+        this._bgManagers = [];
+
+        for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+            let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
+                                                               monitorIndex: i,
+                                                               effects: Meta.BackgroundEffects.VIGNETTE });
+            this._bgManagers.push(bgManager);
+        }
+    },
+
+    _unshadeBackgrounds: function() {
+        let backgrounds = this._backgroundGroup.get_children();
+        for (let i = 0; i < backgrounds.length; i++) {
+            let background = backgrounds[i]._delegate;
+
+            Tweener.addTween(background,
+                             { brightness: 1.0,
+                               time: SHADE_ANIMATION_TIME,
+                               transition: 'easeOutQuad'
+                             });
+            Tweener.addTween(background,
+                             { vignetteSharpness: 0.0,
+                               time: SHADE_ANIMATION_TIME,
+                               transition: 'easeOutQuad'
+                             });
+        }
+    },
+
+    _shadeBackgrounds: function() {
+        let backgrounds = this._backgroundGroup.get_children();
+        for (let i = 0; i < backgrounds.length; i++) {
+            let background = backgrounds[i]._delegate;
+
+            Tweener.addTween(background,
+                             { brightness: 0.8,
+                               time: SHADE_ANIMATION_TIME,
+                               transition: 'easeOutQuad'
+                             });
+            Tweener.addTween(background,
+                             { vignetteSharpness: 0.7,
+                               time: SHADE_ANIMATION_TIME,
+                               transition: 'easeOutQuad'
+                             });
+        }
+    },
+
     _sessionUpdated: function() {
         this.isDummy = !Main.sessionMode.hasOverview;
         this._createOverview();
@@ -379,6 +416,8 @@ const Overview = new Lang.Class({
 
         this._coverPane.set_position(0, workArea.y);
         this._coverPane.set_size(workArea.width, workArea.height);
+
+        this._updateBackgrounds();
     },
 
     _onRestacked: function() {
@@ -477,7 +516,7 @@ const Overview = new Lang.Class({
         global.window_group.hide();
         global.top_window_group.hide();
         this._overview.show();
-        this._background.show();
+        this._backgroundGroup.show();
         this._viewSelector.show();
 
         this._overview.opacity = 0;
@@ -488,12 +527,7 @@ const Overview = new Lang.Class({
                            onComplete: this._showDone,
                            onCompleteScope: this
                          });
-
-        Tweener.addTween(this._background,
-                         { dim_factor: 0.8,
-                           time: ANIMATION_TIME,
-                           transition: 'easeOutQuad'
-                         });
+        this._shadeBackgrounds();
 
         this._coverPane.raise_top();
         this._coverPane.show();
@@ -612,12 +646,7 @@ const Overview = new Lang.Class({
                            onComplete: this._hideDone,
                            onCompleteScope: this
                          });
-
-        Tweener.addTween(this._background,
-                         { dim_factor: 1.0,
-                           time: ANIMATION_TIME,
-                           transition: 'easeOutQuad'
-                         });
+        this._unshadeBackgrounds();
 
         this._coverPane.raise_top();
         this._coverPane.show();
@@ -647,7 +676,7 @@ const Overview = new Lang.Class({
 
         this._viewSelector.hide();
         this._desktopFade.hide();
-        this._background.hide();
+        this._backgroundGroup.hide();
         this._overview.hide();
 
         this.visible = false;
diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js
index a2f482a..0edb7c3 100644
--- a/js/ui/screenShield.js
+++ b/js/ui/screenShield.js
@@ -13,6 +13,7 @@ const Signals = imports.signals;
 const St = imports.gi.St;
 const TweenerEquations = imports.tweener.equations;
 
+const Background = imports.ui.background;
 const GnomeSession = imports.misc.gnomeSession;
 const Hash = imports.misc.hash;
 const Layout = imports.ui.layout;
@@ -49,45 +50,15 @@ const SUMMARY_ICON_SIZE = 48;
 // - STANDARD_FADE_TIME is used when the session goes idle
 // - MANUAL_FADE_TIME is used for lowering the shield when asked by the user,
 //   or when cancelling the dialog
+// - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background
 // - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
 // - INITIAL_FADE_IN_TIME is used for the initial fade in at startup
 const STANDARD_FADE_TIME = 10;
 const MANUAL_FADE_TIME = 0.8;
+const BACKGROUND_FADE_TIME = 1.0;
 const CURTAIN_SLIDE_TIME = 0.3;
 const INITIAL_FADE_IN_TIME = 0.25;
 
-function sample(offx, offy) {
-    return 'texel += texture2D (sampler, tex_coord.st + pixel_step * ' +
-        'vec2 (' + offx + ',' + offy + '));\n'
-}
-const GLSL_BLUR_EFFECT_DECLARATIONS = ' \
-uniform vec2 pixel_step;\n \
-uniform float desaturation;\n \
-vec4 apply_blur(in sampler2D sampler, in vec2 tex_coord) {\n \
-  vec4 texel;\n \
-  texel = texture2D (sampler, tex_coord.st);\n'
-  + sample(-1.0, -1.0)
-  + sample( 0.0, -1.0)
-  + sample(+1.0, -1.0)
-  + sample(-1.0,  0.0)
-  + sample(+1.0,  0.0)
-  + sample(-1.0, +1.0)
-  + sample( 0.0, +1.0)
-  + sample(+1.0, +1.0) + ' \
-   texel /= 9.0;\n \
-   return texel;\n \
-}\n \
-vec3 desaturate (const vec3 color)\n \
-{\n \
-   const vec3 gray_conv = vec3 (0.299, 0.587, 0.114);\n \
-   vec3 gray = vec3 (dot (gray_conv, color));\n \
-   return vec3 (mix (color.rgb, gray, desaturation));\n \
-}';
-const GLSL_BLUR_EFFECT_CODE = ' \
-cogl_texel = apply_blur(cogl_sampler, cogl_tex_coord.st);\n \
-cogl_texel.rgb = desaturate(cogl_texel.rgb);\n';
-
-
 const Clock = new Lang.Class({
     Name: 'ScreenShieldClock',
 
@@ -474,22 +445,17 @@ const ScreenShield = new Lang.Class({
                                                    name: 'lockScreenContents' });
         this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));
 
-        let backgroundActor = Meta.BackgroundActor.new_for_screen(global.screen);
-        backgroundActor.add_glsl_snippet(Meta.SnippetHook.TEXTURE_LOOKUP,
-                                         GLSL_BLUR_EFFECT_DECLARATIONS,
-                                         GLSL_BLUR_EFFECT_CODE,
-                                         true);
-        backgroundActor.set_uniform_float('desaturation',
-                                          1, 1, [0.6]);
-        backgroundActor.connect('notify::size', function(actor) {
-            actor.set_uniform_float('pixel_step', 2, 1, [1/actor.width, 1/actor.height]);
-        });
-
-        this._background = new St.Bin({ style_class: 'screen-shield-background',
-                                        child: backgroundActor });
-        this._lockScreenGroup.add_actor(this._background);
         this._lockScreenGroup.add_actor(this._lockScreenContents);
 
+        this._backgroundGroup = new Meta.BackgroundGroup();
+
+        this._lockScreenGroup.add_actor(this._backgroundGroup);
+        this._backgroundGroup.lower_bottom();
+        this._bgManagers = [];
+
+        this._updateBackgrounds();
+        Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._updateBackgrounds));
+
         this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
                                                   vertical: true,
                                                   x_align: Clutter.ActorAlign.CENTER,
@@ -571,6 +537,31 @@ const ScreenShield = new Lang.Class({
         this.idleMonitor = new GnomeDesktop.IdleMonitor();
     },
 
+    _createBackground: function(monitorIndex) {
+        let bin = new St.Bin({ style_class: 'screen-shield-background' });
+
+        let bgManager = new Background.BackgroundManager({ container: bin,
+                                                           monitorIndex: monitorIndex,
+                                                           effects: Meta.BackgroundEffects.BLUR | 
Meta.BackgroundEffects.DESATURATE });
+
+        bgManager.background.saturation = 0.6;
+
+        this._bgManagers.push(bgManager);
+
+        this._backgroundGroup.add_child(bin);
+        bin.lower_bottom();
+    },
+
+    _updateBackgrounds: function() {
+        for (let i = 0; i < this._bgManagers.length; i++)
+            this._bgManagers[i].destroy();
+
+        this._bgManagers = [];
+
+        for (let i = 0; i < Main.layoutManager.monitors.length; i++)
+            this._createBackground(i);
+    },
+
     _liftShield: function(onPrimary, velocity) {
         if (this._isLocked) {
             this._ensureUnlockDialog(onPrimary, true /* allowCancel */);
diff --git a/js/ui/workspaceThumbnail.js b/js/ui/workspaceThumbnail.js
index a087f3a..f908777 100644
--- a/js/ui/workspaceThumbnail.js
+++ b/js/ui/workspaceThumbnail.js
@@ -9,6 +9,7 @@ const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const St = imports.gi.St;
 
+const Background = imports.ui.background;
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
@@ -170,8 +171,7 @@ const WorkspaceThumbnail = new Lang.Class({
 
         this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
 
-        this._background = Meta.BackgroundActor.new_for_screen(global.screen);
-        this._contents.add_actor(this._background);
+        this._createBackground();
 
         let monitor = Main.layoutManager.primaryMonitor;
         this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
@@ -213,6 +213,12 @@ const WorkspaceThumbnail = new Lang.Class({
         this._collapseFraction = 0; // Not collapsed
     },
 
+    _createBackground: function() {
+        this._bgManager = new Background.BackgroundManager({ monitorIndex: Main.layoutManager.primaryIndex,
+                                                             container: this._contents,
+                                                             effects: Meta.BackgroundEffects.NONE });
+    },
+
     setPorthole: function(x, y, width, height) {
         this._portholeX = x;
         this._portholeY = y;
@@ -236,7 +242,7 @@ const WorkspaceThumbnail = new Lang.Class({
             let clone = this._windows[i];
             let metaWindow = clone.metaWindow;
             if (i == 0) {
-                clone.setStackAbove(this._background);
+                clone.setStackAbove(this._bgManager.background.actor);
             } else {
                 let previousClone = this._windows[i - 1];
                 clone.setStackAbove(previousClone.actor);
@@ -356,6 +362,8 @@ const WorkspaceThumbnail = new Lang.Class({
 
     destroy : function() {
         this.actor.destroy();
+        this._bgManager.destroy();
+        this._bgManager = null;
     },
 
     workspaceRemoved : function() {
@@ -417,7 +425,7 @@ const WorkspaceThumbnail = new Lang.Class({
         this._contents.add_actor(clone.actor);
 
         if (this._windows.length == 0)
-            clone.setStackAbove(this._background);
+            clone.setStackAbove(this._bgManager.actor);
         else
             clone.setStackAbove(this._windows[this._windows.length - 1].actor);
 
diff --git a/src/shell-global.c b/src/shell-global.c
index 8177067..6f20e8c 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -108,7 +108,6 @@ enum {
   PROP_STAGE_INPUT_MODE,
   PROP_WINDOW_GROUP,
   PROP_TOP_WINDOW_GROUP,
-  PROP_BACKGROUND_ACTOR,
   PROP_WINDOW_MANAGER,
   PROP_SETTINGS,
   PROP_DATADIR,
@@ -207,9 +206,6 @@ shell_global_get_property(GObject         *object,
     case PROP_TOP_WINDOW_GROUP:
       g_value_set_object (value, meta_get_top_window_group_for_screen (global->meta_screen));
       break;
-    case PROP_BACKGROUND_ACTOR:
-      g_value_set_object (value, meta_get_background_actor_for_screen (global->meta_screen));
-      break;
     case PROP_WINDOW_MANAGER:
       g_value_set_object (value, global->wm);
       break;
@@ -437,13 +433,6 @@ shell_global_class_init (ShellGlobalClass *klass)
                                                           G_PARAM_READABLE));
 
   g_object_class_install_property (gobject_class,
-                                   PROP_BACKGROUND_ACTOR,
-                                   g_param_spec_object ("background-actor",
-                                                        "Background Actor",
-                                                        "Actor drawing root window background",
-                                                        CLUTTER_TYPE_ACTOR,
-                                                        G_PARAM_READABLE));
-  g_object_class_install_property (gobject_class,
                                    PROP_WINDOW_MANAGER,
                                    g_param_spec_object ("window-manager",
                                                         "Window Manager",


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]