[gnome-shell] layout: merge chrome.js into layout.js



commit 99149f9c41bf138765b17370ebb39cfed5ee3d5f
Author: Dan Winship <danw gnome org>
Date:   Mon Jul 25 09:56:51 2011 -0400

    layout: merge chrome.js into layout.js
    
    LayoutManager and Chrome are already somewhat intertwined and will be
    becoming more so. As a first step in merging them, move the Chrome
    object into layout.js (with no other code changes).
    
    https://bugzilla.gnome.org/show_bug.cgi?id=655813

 js/Makefile.am  |    1 -
 js/ui/chrome.js |  445 -------------------------------------------------------
 js/ui/layout.js |  439 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 js/ui/main.js   |    3 +-
 4 files changed, 440 insertions(+), 448 deletions(-)
---
diff --git a/js/Makefile.am b/js/Makefile.am
index fadab2d..79a81c7 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -20,7 +20,6 @@ nobase_dist_js_DATA = 	\
 	ui/autorunManager.js    \
 	ui/boxpointer.js	\
 	ui/calendar.js		\
-	ui/chrome.js		\
 	ui/ctrlAltTab.js	\
 	ui/dash.js		\
 	ui/dateMenu.js		\
diff --git a/js/ui/layout.js b/js/ui/layout.js
index e13151a..b563440 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -2,10 +2,15 @@
 
 const Clutter = imports.gi.Clutter;
 const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Meta = imports.gi.Meta;
+const Shell = imports.gi.Shell;
 const Signals = imports.signals;
 const St = imports.gi.St;
 
 const Main = imports.ui.main;
+const Params = imports.misc.params;
+const ScreenSaver = imports.misc.screenSaver;
 const Tweener = imports.ui.tweener;
 
 const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
@@ -319,3 +324,437 @@ HotCorner.prototype = {
         return false;
     }
 };
+
+
+// This manages the shell "chrome"; the UI that's visible in the
+// normal mode (ie, outside the Overview), that surrounds the main
+// workspace content.
+
+const defaultParams = {
+    visibleInFullscreen: false,
+    affectsStruts: false,
+    affectsInputRegion: true
+};
+
+function Chrome() {
+    this._init();
+}
+
+Chrome.prototype = {
+    _init: function() {
+        // The group itself has zero size so it doesn't interfere with DND
+        this.actor = new Shell.GenericContainer({ width: 0, height: 0 });
+        Main.uiGroup.add_actor(this.actor);
+        this.actor.connect('allocate', Lang.bind(this, this._allocated));
+
+        this._monitors = [];
+        this._inOverview = false;
+
+        this._trackedActors = [];
+
+        Main.layoutManager.connect('monitors-changed',
+                                   Lang.bind(this, this._relayout));
+        global.screen.connect('restacked',
+                              Lang.bind(this, this._windowsRestacked));
+
+        // Need to update struts on new workspaces when they are added
+        global.screen.connect('notify::n-workspaces',
+                              Lang.bind(this, this._queueUpdateRegions));
+
+        Main.overview.connect('showing',
+                             Lang.bind(this, this._overviewShowing));
+        Main.overview.connect('hidden',
+                             Lang.bind(this, this._overviewHidden));
+
+        this._screenSaverProxy = new ScreenSaver.ScreenSaverProxy();
+        this._screenSaverProxy.connect('ActiveChanged', Lang.bind(this, this._onScreenSaverActiveChanged));
+        this._screenSaverProxy.GetActiveRemote(Lang.bind(this,
+            function(result, err) {
+                if (!err)
+                    this._onScreenSaverActiveChanged(this._screenSaverProxy, result);
+            }));
+
+        this._relayout();
+    },
+
+    _allocated: function(actor, box, flags) {
+        let children = this.actor.get_children();
+        for (let i = 0; i < children.length; i++)
+            children[i].allocate_preferred_size(flags);
+    },
+
+    // addActor:
+    // @actor: an actor to add to the chrome layer
+    // @params: (optional) additional params
+    //
+    // Adds @actor to the chrome layer and (unless %affectsInputRegion
+    // is %false) extends the input region to include it. Changes in
+    // @actor's size, position, and visibility will automatically
+    // result in appropriate changes to the input region.
+    //
+    // If %affectsStruts is %true (and @actor is along a screen edge),
+    // then @actor's size and position will also affect the window
+    // manager struts. Changes to @actor's visibility will NOT affect
+    // whether or not the strut is present, however.
+    //
+    // If %visibleInFullscreen is %true, the actor will be visible
+    // even when a fullscreen window should be covering it.
+    addActor: function(actor, params) {
+        this.actor.add_actor(actor);
+        this._trackActor(actor, params);
+    },
+
+    // trackActor:
+    // @actor: a descendant of the chrome to begin tracking
+    // @params: parameters describing how to track @actor
+    //
+    // Tells the chrome to track @actor, which must be a descendant
+    // of an actor added via addActor(). This can be used to extend the
+    // struts or input region to cover specific children.
+    //
+    // @params can have any of the same values as in addActor(), though
+    // some possibilities don't make sense (eg, trying to have a
+    // %visibleInFullscreen child of a non-%visibleInFullscreen parent).
+    // By default, @actor has the same params as its chrome ancestor.
+    trackActor: function(actor, params) {
+        let ancestor = actor.get_parent();
+        let index = this._findActor(ancestor);
+        while (ancestor && index == -1) {
+            ancestor = ancestor.get_parent();
+            index = this._findActor(ancestor);
+        }
+        if (!ancestor)
+            throw new Error('actor is not a descendent of the chrome layer');
+
+        let ancestorData = this._trackedActors[index];
+        if (!params)
+            params = {};
+        // We can't use Params.parse here because we want to drop
+        // the extra values like ancestorData.actor
+        for (let prop in defaultParams) {
+            if (!params[prop])
+                params[prop] = ancestorData[prop];
+        }
+
+        this._trackActor(actor, params);
+    },
+
+    // untrackActor:
+    // @actor: an actor previously tracked via trackActor()
+    //
+    // Undoes the effect of trackActor()
+    untrackActor: function(actor) {
+        this._untrackActor(actor);
+    },
+
+    // removeActor:
+    // @actor: a child of the chrome layer
+    //
+    // Removes @actor from the chrome layer
+    removeActor: function(actor) {
+        this.actor.remove_actor(actor);
+        this._untrackActor(actor);
+    },
+
+    _findActor: function(actor) {
+        for (let i = 0; i < this._trackedActors.length; i++) {
+            let actorData = this._trackedActors[i];
+            if (actorData.actor == actor)
+                return i;
+        }
+        return -1;
+    },
+
+    _trackActor: function(actor, params) {
+        if (this._findActor(actor) != -1)
+            throw new Error('trying to re-track existing chrome actor');
+
+        let actorData = Params.parse(params, defaultParams);
+        actorData.actor = actor;
+        actorData.visibleId = actor.connect('notify::visible',
+                                            Lang.bind(this, this._queueUpdateRegions));
+        actorData.allocationId = actor.connect('notify::allocation',
+                                               Lang.bind(this, this._queueUpdateRegions));
+        actorData.parentSetId = actor.connect('parent-set',
+                                              Lang.bind(this, this._actorReparented));
+        // Note that destroying actor will unset its parent, so we don't
+        // need to connect to 'destroy' too.
+
+        this._trackedActors.push(actorData);
+        this._queueUpdateRegions();
+    },
+
+    _untrackActor: function(actor) {
+        let i = this._findActor(actor);
+
+        if (i == -1)
+            return;
+        let actorData = this._trackedActors[i];
+
+        this._trackedActors.splice(i, 1);
+        actor.disconnect(actorData.visibleId);
+        actor.disconnect(actorData.allocationId);
+        actor.disconnect(actorData.parentSetId);
+
+        this._queueUpdateRegions();
+    },
+
+    _actorReparented: function(actor, oldParent) {
+        if (!this.actor.contains(actor))
+            this._untrackActor(actor);
+    },
+
+    _updateVisibility: function() {
+        for (let i = 0; i < this._trackedActors.length; i++) {
+            let actorData = this._trackedActors[i];
+            if (!this._inOverview && !actorData.visibleInFullscreen &&
+                this._findMonitorForActor(actorData.actor).inFullscreen)
+                this.actor.set_skip_paint(actorData.actor, true);
+            else
+                this.actor.set_skip_paint(actorData.actor, false);
+        }
+    },
+
+    _overviewShowing: function() {
+        this._inOverview = true;
+        this._updateVisibility();
+        this._queueUpdateRegions();
+    },
+
+    _overviewHidden: function() {
+        this._inOverview = false;
+        this._updateVisibility();
+        this._queueUpdateRegions();
+    },
+
+    _relayout: function() {
+        this._monitors = Main.layoutManager.monitors;
+        this._primaryMonitor = Main.layoutManager.primaryMonitor;
+
+        this._updateFullscreen();
+        this._updateVisibility();
+        this._queueUpdateRegions();
+    },
+
+    _onScreenSaverActiveChanged: function(proxy, screenSaverActive) {
+        this.actor.visible = !screenSaverActive;
+        this._queueUpdateRegions();
+    },
+
+    _findMonitorForRect: function(x, y, w, h) {
+        // First look at what monitor the center of the rectangle is at
+        let cx = x + w/2;
+        let cy = y + h/2;
+        for (let i = 0; i < this._monitors.length; i++) {
+            let monitor = this._monitors[i];
+            if (cx >= monitor.x && cx < monitor.x + monitor.width &&
+                cy >= monitor.y && cy < monitor.y + monitor.height)
+                return monitor;
+        }
+        // If the center is not on a monitor, return the first overlapping monitor
+        for (let i = 0; i < this._monitors.length; i++) {
+            let monitor = this._monitors[i];
+            if (x + w > monitor.x && x < monitor.x + monitor.width &&
+                y + h > monitor.y && y < monitor.y + monitor.height)
+                return monitor;
+        }
+        // otherwise on no monitor
+        return null;
+    },
+
+    _findMonitorForWindow: function(window) {
+        return this._findMonitorForRect(window.x, window.y, window.width, window.height);
+    },
+
+    // This call guarantees that we return some monitor to simplify usage of it
+    // In practice all tracked actors should be visible on some monitor anyway
+    _findMonitorForActor: function(actor) {
+        let [x, y] = actor.get_transformed_position();
+        let [w, h] = actor.get_transformed_size();
+        let monitor = this._findMonitorForRect(x, y, w, h);
+        if (monitor)
+            return monitor;
+        return this._primaryMonitor; // Not on any monitor, pretend its on the primary
+    },
+
+    _queueUpdateRegions: function() {
+        if (!this._updateRegionIdle)
+            this._updateRegionIdle = Mainloop.idle_add(Lang.bind(this, this._updateRegions),
+                                                       Meta.PRIORITY_BEFORE_REDRAW);
+    },
+
+    _updateFullscreen: function() {
+        let windows = Main.getWindowActorsForWorkspace(global.screen.get_active_workspace_index());
+
+        // Reset all monitors to not fullscreen
+        for (let i = 0; i < this._monitors.length; i++)
+            this._monitors[i].inFullscreen = false;
+
+        // The chrome layer should be visible unless there is a window
+        // with layer FULLSCREEN, or a window with layer
+        // OVERRIDE_REDIRECT that covers the whole screen.
+        // ('override_redirect' is not actually a layer above all
+        // other windows, but this seems to be how mutter treats it
+        // currently...) If we wanted to be extra clever, we could
+        // figure out when an OVERRIDE_REDIRECT window was trying to
+        // partially overlap us, and then adjust the input region and
+        // our clip region accordingly...
+
+        // @windows is sorted bottom to top.
+
+        for (let i = windows.length - 1; i > -1; i--) {
+            let window = windows[i];
+            let layer = window.get_meta_window().get_layer();
+
+            // Skip minimized windows
+            if (!window.showing_on_its_workspace())
+                continue;
+
+            if (layer == Meta.StackLayer.FULLSCREEN) {
+                let monitor = this._findMonitorForWindow(window);
+                if (monitor)
+                    monitor.inFullscreen = true;
+            }
+            if (layer == Meta.StackLayer.OVERRIDE_REDIRECT) {
+                let monitor = this._findMonitorForWindow(window);
+                if (monitor &&
+                    window.x <= monitor.x &&
+                    window.x + window.width >= monitor.x + monitor.width &&
+                    window.y <= monitor.y &&
+                    window.y + window.height >= monitor.y + monitor.height)
+                    monitor.inFullscreen = true;
+            } else
+                break;
+        }
+    },
+
+    _windowsRestacked: function() {
+        let wasInFullscreen = [];
+        for (let i = 0; i < this._monitors.length; i++)
+            wasInFullscreen[i] = this._monitors[i].inFullscreen;
+
+        this._updateFullscreen();
+
+        let changed = false;
+        for (let i = 0; i < wasInFullscreen.length; i++) {
+            if (wasInFullscreen[i] != this._monitors[i].inFullscreen) {
+                changed = true;
+                break;
+            }
+        }
+        if (changed) {
+            this._updateVisibility();
+            this._queueUpdateRegions();
+        }
+    },
+
+    _updateRegions: function() {
+        let rects = [], struts = [], i;
+
+        delete this._updateRegionIdle;
+
+        for (i = 0; i < this._trackedActors.length; i++) {
+            let actorData = this._trackedActors[i];
+            if (!actorData.affectsInputRegion && !actorData.affectsStruts)
+                continue;
+
+            let [x, y] = actorData.actor.get_transformed_position();
+            let [w, h] = actorData.actor.get_transformed_size();
+            x = Math.round(x);
+            y = Math.round(y);
+            w = Math.round(w);
+            h = Math.round(h);
+            let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h});
+
+            if (actorData.affectsInputRegion &&
+                actorData.actor.get_paint_visibility() &&
+                !this.actor.get_skip_paint(actorData.actor))
+                rects.push(rect);
+
+            if (!actorData.affectsStruts)
+                continue;
+
+            // Limit struts to the size of the screen
+            let x1 = Math.max(x, 0);
+            let x2 = Math.min(x + w, global.screen_width);
+            let y1 = Math.max(y, 0);
+            let y2 = Math.min(y + h, global.screen_height);
+
+            // NetWM struts are not really powerful enought to handle
+            // a multi-monitor scenario, they only describe what happens
+            // around the outer sides of the full display region. However
+            // it can describe a partial region along each side, so
+            // we can support having the struts only affect the
+            // primary monitor. This should be enough as we only have
+            // chrome affecting the struts on the primary monitor so
+            // far.
+            //
+            // Metacity wants to know what side of the screen the
+            // strut is considered to be attached to. If the actor is
+            // only touching one edge, or is touching the entire
+            // border of the primary monitor, then it's obvious which
+            // side to call it. If it's in a corner, we pick a side
+            // arbitrarily. If it doesn't touch any edges, or it spans
+            // the width/height across the middle of the screen, then
+            // we don't create a strut for it at all.
+            let side;
+            let primary = this._primaryMonitor;
+            if (x1 <= primary.x && x2 >= primary.x + primary.width) {
+                if (y1 <= primary.y)
+                    side = Meta.Side.TOP;
+                else if (y2 >= primary.y + primary.height)
+                    side = Meta.Side.BOTTOM;
+                else
+                    continue;
+            } else if (y1 <= primary.y && y2 >= primary.y + primary.height) {
+                if (x1 <= 0)
+                    side = Meta.Side.LEFT;
+                else if (x2 >= global.screen_width)
+                    side = Meta.Side.RIGHT;
+                else
+                    continue;
+            } else if (x1 <= 0)
+                side = Meta.Side.LEFT;
+            else if (y1 <= 0)
+                side = Meta.Side.TOP;
+            else if (x2 >= global.screen_width)
+                side = Meta.Side.RIGHT;
+            else if (y2 >= global.screen_height)
+                side = Meta.Side.BOTTOM;
+            else
+                continue;
+
+            // Ensure that the strut rects goes all the way to the screen edge,
+            // as this really what mutter expects.
+            switch (side) {
+            case Meta.Side.TOP:
+                y1 = 0;
+                break;
+            case Meta.Side.BOTTOM:
+                y2 = global.screen_height;
+                break;
+            case Meta.Side.LEFT:
+                x1 = 0;
+                break;
+            case Meta.Side.RIGHT:
+                x2 = global.screen_width;
+                break;
+            }
+
+            let strutRect = new Meta.Rectangle({ x: x1, y: y1, width: x2 - x1, height: y2 - y1});
+            let strut = new Meta.Strut({ rect: strutRect, side: side });
+            struts.push(strut);
+        }
+
+        global.set_stage_input_region(rects);
+
+        let screen = global.screen;
+        for (let w = 0; w < screen.n_workspaces; w++) {
+            let workspace = screen.get_workspace_by_index(w);
+            workspace.set_builtin_struts(struts);
+        }
+
+        return false;
+    }
+};
+Signals.addSignalMethods(Chrome.prototype);
diff --git a/js/ui/main.js b/js/ui/main.js
index efda3b2..05f2bb8 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -14,7 +14,6 @@ const St = imports.gi.St;
 
 const AutomountManager = imports.ui.automountManager;
 const AutorunManager = imports.ui.autorunManager;
-const Chrome = imports.ui.chrome;
 const CtrlAltTab = imports.ui.ctrlAltTab;
 const EndSessionDialog = imports.ui.endSessionDialog;
 const PolkitAuthenticationAgent = imports.ui.polkitAuthenticationAgent;
@@ -137,7 +136,7 @@ function start() {
     xdndHandler = new XdndHandler.XdndHandler();
     ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
     overview = new Overview.Overview();
-    chrome = new Chrome.Chrome();
+    chrome = new Layout.Chrome();
     magnifier = new Magnifier.Magnifier();
     statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
     panel = new Panel.Panel();



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