[gnome-shell/eos3.8: 28/255] sideComponent: Add SideComponent back



commit c1d3031e0bae366ce06c044f27221a5560d26129
Author: Sam Spilsbury <sam endlessm com>
Date:   Sun May 28 04:59:48 2017 +0800

    sideComponent: Add SideComponent back
    
    This is used by the website panel, the discovery feed and Hack.
    
     * 2019-09-23: replace Tweener by Clutter implicit animations
     * 2020-03-13: Code style cleanup
    
    https://phabricator.endlessm.com/T2519
    https://phabricator.endlessm.com/T16876
    https://phabricator.endlessm.com/T18137
    https://phabricator.endlessm.com/T18601

 js/js-resources.gresource.xml |   1 +
 js/ui/layout.js               |  23 ++-
 js/ui/overview.js             |  12 ++
 js/ui/sideComponent.js        | 155 +++++++++++++++
 js/ui/windowManager.js        | 434 +++++++++++++++++++++++++++++++++++++++++-
 src/shell-window-tracker.c    |   8 +
 6 files changed, 631 insertions(+), 2 deletions(-)
---
diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml
index e460b0a174..e2ef9f48de 100644
--- a/js/js-resources.gresource.xml
+++ b/js/js-resources.gresource.xml
@@ -146,6 +146,7 @@
     <file>ui/endlessButton.js</file>
     <file>ui/forceAppExitDialog.js</file>
     <file>ui/hotCorner.js</file>
+    <file>ui/sideComponent.js</file>
     <file>ui/workspaceMonitor.js</file>
   </gresource>
 </gresources>
diff --git a/js/ui/layout.js b/js/ui/layout.js
index e88e82aa3e..9ff28c50b9 100644
--- a/js/ui/layout.js
+++ b/js/ui/layout.js
@@ -196,7 +196,8 @@ const defaultParams = {
 };
 
 var LayoutManager = GObject.registerClass({
-    Signals: { 'hot-corners-changed': {},
+    Signals: { 'background-clicked': {},
+               'hot-corners-changed': {},
                'startup-complete': {},
                'startup-prepared': {},
                'monitors-changed': {},
@@ -218,6 +219,7 @@ var LayoutManager = GObject.registerClass({
         this._inOverview = false;
         this._updateRegionIdle = 0;
 
+        this._overlayRegion = null;
         this._trackedActors = [];
         this._topActors = [];
         this._isPopupWindowVisible = false;
@@ -859,6 +861,11 @@ var LayoutManager = GObject.registerClass({
         this._trackActor(actor, params);
     }
 
+    setOverlayRegion(region) {
+        this._overlayRegion = region;
+        this._queueUpdateRegions();
+    }
+
     // addTopChrome:
     // @actor: an actor to add to the chrome
     // @params: (optional) additional params
@@ -1121,6 +1128,20 @@ var LayoutManager = GObject.registerClass({
             }
         }
 
+        if (this._overlayRegion != null) {
+            let numOverlayRects = this._overlayRegion.numRectangles();
+            for (let idx = 0; idx < numOverlayRects; idx++) {
+                let rect = this._overlayRegion.getRectangle(idx);
+                let metaRect = new Meta.Rectangle({
+                    x: rect.x,
+                    y: rect.y,
+                    width: rect.width,
+                    height: rect.height,
+                });
+                rects.push(metaRect);
+            }
+        }
+
         global.set_stage_input_region(rects);
         this._isPopupWindowVisible = isPopupMenuVisible;
 
diff --git a/js/ui/overview.js b/js/ui/overview.js
index 8101327e85..9863d14c10 100644
--- a/js/ui/overview.js
+++ b/js/ui/overview.js
@@ -294,6 +294,7 @@ var Overview = class {
 
         this._shellInfo = new ShellInfo();
 
+        this.viewSelector.connect('page-changed', this._onPageChanged.bind(this));
         Main.layoutManager.connect('monitors-changed', this._relayout.bind(this));
         this._relayout();
     }
@@ -318,6 +319,13 @@ var Overview = class {
         this._shellInfo.setMessage(text, options);
     }
 
+    _onPageChanged() {
+        // SideComponent hooks on this signal but can't connect directly to
+        // viewSelector since it won't be created at the time the component
+        // is enabled, so rely on the overview and re-issue it from here.
+        this.emit('page-changed');
+    }
+
     _onDragBegin() {
         this._inXdndDrag = true;
 
@@ -688,6 +696,10 @@ var Overview = class {
             this.show();
     }
 
+    getActivePage() {
+        return this.viewSelector.getActivePage();
+    }
+
     getShowAppsButton() {
         logError(new Error('Usage of Overview.\'getShowAppsButton\' is deprecated, ' +
             'use \'dash.showAppsButton\' property instead'));
diff --git a/js/ui/sideComponent.js b/js/ui/sideComponent.js
new file mode 100644
index 0000000000..7ae16efd8a
--- /dev/null
+++ b/js/ui/sideComponent.js
@@ -0,0 +1,155 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+/* exported isSideComponentWindow, launchedFromDesktop,
+   shouldHideOtherWindows, SideComponent */
+
+const { Gio, GLib, GObject } = imports.gi;
+
+const Main = imports.ui.main;
+const ViewSelector = imports.ui.viewSelector;
+
+const SIDE_COMPONENT_ROLE = 'eos-side-component';
+
+/**
+ * isSideComponentWindow:
+ * @param {Meta.Window} metaWindow An instance of #Meta.Window
+ * @returns {bool} Whether the #Meta.Window belongs to a #SideComponent
+ */
+function isSideComponentWindow(metaWindow) {
+    return metaWindow && (metaWindow.get_role() === SIDE_COMPONENT_ROLE);
+}
+
+/**
+ * shouldHideOtherWindows:
+ * @param {Meta.Window} metaWindow An instance of #Meta.Window
+ * @returns {bool} Whether other windows should be hidden while this one is open
+ */
+function shouldHideOtherWindows(metaWindow) {
+    return isSideComponentWindow(metaWindow);
+}
+
+/**
+ * launchedFromDesktop:
+ * @param {Meta.Window} metaWindow An instance of #Meta.Window
+ * @returns {bool} Whether the side component was launched from the desktop
+ */
+function launchedFromDesktop(metaWindow) {
+    return false;
+}
+
+var SideComponent = GObject.registerClass(
+class SideComponent extends GObject.Object {
+    _init(proxyIface, proxyName, proxyPath) {
+        super._init();
+        this._propertiesChangedId = 0;
+        this._desktopShownId = 0;
+        this._overviewPageChangedId = 0;
+
+        this._proxyIface = proxyIface;
+        this._proxyInfo = Gio.DBusInterfaceInfo.new_for_xml(this._proxyIface);
+        this._proxyName = proxyName;
+        this._proxyPath = proxyPath;
+
+        this._visible = false;
+        this._launchedFromDesktop = false;
+    }
+
+    enable() {
+        if (!this.proxy) {
+            this.proxy = new Gio.DBusProxy({
+                g_connection: Gio.DBus.session,
+                g_interface_name: this._proxyInfo.name,
+                g_interface_info: this._proxyInfo,
+                g_name: this._proxyName,
+                g_object_path: this._proxyPath,
+                g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION,
+            });
+            this.proxy.init_async(GLib.PRIORITY_DEFAULT, null, this._onProxyConstructed.bind(this));
+        }
+
+        this._propertiesChangedId =
+            this.proxy.connect('g-properties-changed', this._onPropertiesChanged.bind(this));
+
+        // Clicking the background (which calls overview.showApps) hides the component,
+        // so trying to open it again will call WindowManager._mapWindow(),
+        // which will hide the overview and animate the window.
+        // Note that this is not the case when opening the window picker.
+        this._desktopShownId = Main.layoutManager.connect('background-clicked', () => {
+            if (this._visible)
+                this.hide(global.get_current_time());
+        });
+
+        // Same when clicking the background from the window picker.
+        this._overviewPageChangedId = Main.overview.connect('page-changed', () => {
+            if (this._visible && Main.overview.visible &&
+                Main.overview.getActivePage() === ViewSelector.ViewPage.APPS)
+                this.hide(global.get_current_time());
+        });
+    }
+
+    disable() {
+        if (this._propertiesChangedId > 0) {
+            this.proxy.disconnect(this._propertiesChangedId);
+            this._propertiesChangedId = 0;
+        }
+
+        if (this._desktopShownId > 0) {
+            Main.layoutManager.disconnect(this._desktopShownId);
+            this._desktopShownId = 0;
+        }
+
+        if (this._overviewPageChangedId > 0) {
+            Main.overview.disconnect(this._overviewPageChangedId);
+            this._overviewPageChangedId = 0;
+        }
+    }
+
+    _onProxyConstructed(object, res) {
+        try {
+            object.init_finish(res);
+        } catch (e) {
+            logError(e, `Error while constructing the DBus proxy for ${this._proxyName}`);
+        }
+    }
+
+    _onPropertiesChanged(proxy, changedProps) {
+        let propsDict = changedProps.deep_unpack();
+        if (propsDict.hasOwnProperty('Visible'))
+            this._onVisibilityChanged();
+    }
+
+    _onVisibilityChanged() {
+        if (this._visible === this.proxy.Visible)
+            return;
+
+        // resync visibility
+        this._visible = this.proxy.Visible;
+    }
+
+    toggle(timestamp, params) {
+        if (this._visible)
+            this.hide(timestamp, params);
+        else
+            this.show(timestamp, params);
+    }
+
+    show(timestamp, params) {
+        this._launchedFromDesktop =
+            Main.overview.visible &&
+            Main.overview.getActivePage() === ViewSelector.ViewPage.APPS;
+
+        if (this._visible && Main.overview.visible)
+            // the component is already open, but obscured by the overview
+            Main.overview.hide();
+        else
+            this.callShow(timestamp, params);
+    }
+
+    hide(timestamp, params) {
+        this.callHide(timestamp, params);
+    }
+
+    get launchedFromDesktop() {
+        return this._launchedFromDesktop;
+    }
+});
diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js
index c4cea0892e..91e10e157e 100644
--- a/js/ui/windowManager.js
+++ b/js/ui/windowManager.js
@@ -1,7 +1,9 @@
 // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 /* exported WindowManager */
 
-const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
+const { Clutter, Gdk, Gio, GLib,
+        GObject, Meta, Shell, St } = imports.gi;
+const Cairo = imports.cairo;
 
 const AltTab = imports.ui.altTab;
 const AppFavorites = imports.ui.appFavorites;
@@ -18,6 +20,8 @@ const CloseDialog = imports.ui.closeDialog;
 const SwipeTracker = imports.ui.swipeTracker;
 const SwitchMonitor = imports.ui.switchMonitor;
 const IBusManager = imports.misc.ibusManager;
+const SideComponent = imports.ui.sideComponent;
+const BackgroundMenu = imports.ui.backgroundMenu;
 
 const { loadInterfaceXML } = imports.misc.fileUtils;
 
@@ -550,6 +554,266 @@ class ResizePopup extends St.Widget {
     }
 });
 
+var DesktopOverlay = GObject.registerClass({
+    Signals: { 'clicked': {} },
+}, class DesktopOverlay extends St.Widget {
+    _init() {
+        super._init({ reactive: true });
+
+        this._shellwm = global.window_manager;
+
+        this._actorDestroyId = 0;
+        this._allocationId = 0;
+        this._destroyId = 0;
+        this._mapId = 0;
+        this._visibleId = 0;
+        this._showing = false;
+
+        this._overlayActor = null;
+        this._transientActors = [];
+
+        let action = new Clutter.ClickAction();
+        action.connect('clicked', a => {
+            if (a.get_button() !== Gdk.BUTTON_PRIMARY)
+                return;
+
+            if (this._showing && this._overlayActor)
+                this.emit('clicked');
+        });
+        this.add_action(action);
+        BackgroundMenu.addBackgroundMenu(this, Main.layoutManager);
+
+        Main.overview.connect('showing', () => {
+            // hide the overlay so it doesn't conflict with the desktop
+            if (this._showing)
+                this.hide();
+        });
+        Main.overview.connect('hiding', () => {
+            // show the overlay if needed
+            if (this._showing)
+                this.show();
+        });
+
+        Main.uiGroup.add_actor(this);
+        if (Main.uiGroup.contains(global.top_window_group))
+            Main.uiGroup.set_child_below_sibling(this, global.top_window_group);
+    }
+
+    _rebuildRegion() {
+        if (!this._overlayActor.get_paint_visibility()) {
+            Main.layoutManager.setOverlayRegion(null);
+            return;
+        }
+
+        let overlayWindow = this._overlayActor.meta_window;
+        let monitorIdx = overlayWindow.get_monitor();
+        let monitor = Main.layoutManager.monitors[monitorIdx];
+        if (!monitor)
+            return;
+
+        let workArea = Main.layoutManager.getWorkAreaForMonitor(overlayWindow.get_monitor());
+        let region = new Cairo.Region();
+        region.unionRectangle(workArea);
+
+        let [x, y] = this._overlayActor.get_transformed_position();
+        let [width, height] = this._overlayActor.get_transformed_size();
+        let rect = {
+            x: Math.round(x),
+            y: Math.round(y),
+            width: Math.round(width),
+            height: Math.round(height),
+        };
+
+        region.subtractRectangle(rect);
+
+        this._transientActors.forEach(actorData => {
+            let transientActor = actorData.actor;
+
+            let [transientX, transientY] =
+                transientActor.get_transformed_position();
+            let [transientWidth, transientHeight] =
+                transientActor.get_transformed_size();
+            let transientRect = {
+                x: Math.round(transientX),
+                y: Math.round(transientY),
+                width: Math.round(transientWidth),
+                height: Math.round(transientHeight),
+            };
+
+            region.subtractRectangle(transientRect);
+        });
+
+        Main.layoutManager.setOverlayRegion(region);
+    }
+
+    _repositionOverlay() {
+        let overlayWindow = this._overlayActor.meta_window;
+        let monitorIdx = overlayWindow.get_monitor();
+        let monitor = Main.layoutManager.monitors[monitorIdx];
+        if (!monitor)
+            return;
+
+        // The width and height of the overlay need to be the width and height
+        // of the workArea. We already capture inputs correctly
+        // outside the overlay window by setting the region of the
+        // layoutManager overlay, so it is safe to just take up the
+        // entire workArea.
+        let workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIdx);
+        this.width = workArea.width;
+        this.height = workArea.height;
+        this.y = this._overlayActor.y;
+
+        if (this._overlayActor.x <= monitor.x)
+            this.x = monitor.x + monitor.width - this.width;
+        else
+            this.x = monitor.x;
+    }
+
+    _recalculateOverlay() {
+        this._repositionOverlay();
+        this._rebuildRegion();
+    }
+
+    _findTransientActor(actor) {
+        for (let i = 0; i < this._transientActors.length; i++) {
+            let actorData = this._transientActors[i];
+            if (actorData.actor === actor)
+                return i;
+        }
+        return -1;
+    }
+
+    _untrackTransientActor(actor) {
+        let idx = this._findTransientActor(actor);
+        if (idx === -1) {
+            log('Trying to untrack a non-tracked transient actor!');
+            return;
+        }
+
+        let actorData = this._transientActors[idx];
+        this._transientActors.splice(idx, 1);
+
+        actor.disconnect(actorData.visibleId);
+        actor.disconnect(actorData.allocationId);
+        actor.disconnect(actorData.destroyId);
+
+        this._rebuildRegion();
+    }
+
+    _trackTransientActor(actor) {
+        if (this._findTransientActor(actor) !== -1) {
+            log('Trying to track twice the same transient actor!');
+            return;
+        }
+
+        let actorData = {};
+        actorData.actor = actor;
+        actorData.visibleId = actor.connect(
+            'notify::visible', this._recalculateOverlay.bind(this));
+        actorData.allocationId = actor.connect(
+            'notify::allocation', this._recalculateOverlay.bind(this));
+        actorData.destroyId = actor.connect(
+            'destroy', this._untrackTransientActor.bind(this));
+
+        this._transientActors.push(actorData);
+        this._recalculateOverlay();
+    }
+
+    _untrackActor() {
+        this._transientActors.forEach(actorData => {
+            this._untrackTransientActor(actorData.actor);
+        });
+        this._transientActors = [];
+
+        if (this._visibleId > 0) {
+            this._overlayActor.disconnect(this._visibleId);
+            this._visibleId = 0;
+        }
+
+        if (this._allocationId > 0) {
+            this._overlayActor.disconnect(this._allocationId);
+            this._allocationId = 0;
+        }
+
+        if (this._actorDestroyId > 0) {
+            this._overlayActor.disconnect(this._actorDestroyId);
+            this._actorDestroyId = 0;
+        }
+
+        if (this._destroyId > 0) {
+            this._shellwm.disconnect(this._destroyId);
+            this._destroyId = 0;
+        }
+
+        if (this._mapId > 0) {
+            this._shellwm.disconnect(this._mapId);
+            this._mapId = 0;
+        }
+
+        Main.layoutManager.setOverlayRegion(null);
+    }
+
+    _trackActor() {
+        let overlayWindow = this._overlayActor.meta_window;
+
+        this._visibleId = this._overlayActor.connect(
+            'notify::visible', this._recalculateOverlay.bind(this));
+        this._allocationId = this._overlayActor.connect(
+            'notify::allocation', this._recalculateOverlay.bind(this));
+        this._actorDestroyId = this._overlayActor.connect(
+            'destroy', this._untrackActor.bind(this));
+
+        this._mapId = this._shellwm.connect('map', (shellwm, actor) => {
+            let newWindow = actor.meta_window;
+            if (overlayWindow.is_ancestor_of_transient(newWindow))
+                this._trackTransientActor(actor);
+        });
+        this._destroyId = this._shellwm.connect('destroy', (shellwm, actor) => {
+            let destroyedWindow = actor.meta_window;
+            if (overlayWindow.is_ancestor_of_transient(destroyedWindow))
+                this._untrackTransientActor(actor);
+        });
+
+        // seed the transient actors
+        overlayWindow.foreach_transient(() => {
+            let transientActor = overlayWindow.get_compositor_private();
+            if (transientActor)
+                this._trackTransientActor(transientActor);
+        });
+
+        this._recalculateOverlay();
+    }
+
+    _setOverlayActor(actor) {
+        if (actor === this._overlayActor)
+            return;
+
+        this._untrackActor();
+        this._overlayActor = actor;
+
+        if (this._overlayActor)
+            this._trackActor();
+    }
+
+    get overlayActor() {
+        return this._overlayActor;
+    }
+
+    showOverlay(actor) {
+        this._setOverlayActor(actor);
+
+        this._showing = true;
+        this.show();
+    }
+
+    hideOverlay() {
+        this._setOverlayActor(null);
+
+        this._showing = false;
+        this.hide();
+    }
+});
+
 var WindowManager = class {
     constructor() {
         this._shellwm =  global.window_manager;
@@ -568,6 +832,18 @@ var WindowManager = class {
 
         this._allowedKeybindings = {};
 
+        this._desktopOverlay = new DesktopOverlay();
+        this._showDesktopOnDestroyDone = false;
+
+        // The desktop overlay needs to replicate the background's functionality;
+        // when clicked, we animate the side component out before emitting "background-clicked".
+        this._desktopOverlay.connect('clicked', () => {
+            this._slideSideComponentOut(
+                this._shellwm,
+                this._desktopOverlay.overlayActor,
+                () => Main.layoutManager.emit('background-clicked'));
+        });
+
         this._isWorkspacePrepended = false;
 
         this._switchData = null;
@@ -1130,6 +1406,11 @@ var WindowManager = class {
         if (this._skippedActors.delete(actor))
             return false;
 
+        // We should always animate side component windows in, even
+        // if the overview is visible
+        if (SideComponent.isSideComponentWindow(actor.meta_window))
+            return true;
+
         if (!this._shouldAnimate())
             return false;
 
@@ -1140,6 +1421,30 @@ var WindowManager = class {
         return types.includes(type);
     }
 
+    _slideSideComponentOut(shellwm, actor, onComplete) {
+        let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
+        if (!monitor) {
+            onComplete.apply(this, [shellwm, actor]);
+            return;
+        }
+
+        actor.opacity = 255;
+        actor.show();
+
+        let endX;
+        if (actor.x <= monitor.x)
+            endX = monitor.x - actor.width;
+        else
+            endX = monitor.x + monitor.width;
+
+        actor.ease({
+            x: endX,
+            duration: WINDOW_ANIMATION_TIME,
+            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            onComplete: () => onComplete(shellwm, actor),
+        });
+    }
+
     _minimizeWindow(shellwm, actor) {
         let types = [Meta.WindowType.NORMAL,
                      Meta.WindowType.MODAL_DIALOG,
@@ -1432,6 +1737,86 @@ var WindowManager = class {
         dimmer.setDimmed(false, this._shouldAnimate());
     }
 
+    _hideOtherWindows(actor, animate) {
+        let winActors = global.get_window_actors();
+        for (let winActor of winActors) {
+            if (!winActor.get_meta_window().showing_on_its_workspace())
+                continue;
+
+            if (SideComponent.isSideComponentWindow(winActor.meta_window))
+                continue;
+
+            if (animate) {
+                winActor.ease({
+                    opacity: 0,
+                    duration: WINDOW_ANIMATION_TIME,
+                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                    onComplete: windowActor => windowActor.hide(),
+                });
+            } else {
+                winActor.opacity = 0;
+                winActor.hide();
+            }
+        }
+
+        this._desktopOverlay.showOverlay(actor);
+    }
+
+    _showOtherWindows(actor, animate) {
+        let winActors = global.get_window_actors();
+        for (let winActor of winActors) {
+            if (!winActor.get_meta_window().showing_on_its_workspace())
+                continue;
+
+            if (SideComponent.isSideComponentWindow(winActor.meta_window))
+                continue;
+
+            if (animate && winActor.opacity !== 255) {
+                winActor.show();
+                winActor.ease({
+                    opacity: 255,
+                    duration: WINDOW_ANIMATION_TIME,
+                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+                });
+            } else {
+                winActor.opacity = 255;
+                winActor.show();
+            }
+        }
+
+        this._desktopOverlay.hideOverlay();
+    }
+
+    _mapSideComponent(shellwm, actor, animateFade) {
+        let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
+        if (!monitor) {
+            this._mapWindowDone(shellwm, actor);
+            return;
+        }
+
+        let origX = actor.x;
+        if (origX === monitor.x) {
+            // the side bar will appear from the left side
+            actor.set_position(monitor.x - actor.width, actor.y);
+        } else {
+            // ... from the right side
+            actor.set_position(monitor.x + monitor.width, actor.y);
+        }
+
+        actor.ease({
+            x: origX,
+            duration: WINDOW_ANIMATION_TIME,
+            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
+            onComplete: this._mapWindowDone.bind(this),
+        });
+
+        actor.opacity = 255;
+        actor.show();
+
+        if (SideComponent.shouldHideOtherWindows(actor.meta_window))
+            this._hideOtherWindows(actor, animateFade);
+    }
+
     _mapWindow(shellwm, actor) {
         actor._windowType = actor.meta_window.get_window_type();
         actor._notifyWindowTypeSignalId =
@@ -1477,10 +1862,28 @@ var WindowManager = class {
                      Meta.WindowType.DIALOG,
                      Meta.WindowType.MODAL_DIALOG];
         if (!this._shouldAnimateActor(actor, types)) {
+            if (SideComponent.shouldHideOtherWindows(actor.meta_window))
+                this._showOtherWindows(actor, false);
+
             shellwm.completed_map(actor);
             return;
         }
 
+        if (SideComponent.isSideComponentWindow(actor.meta_window)) {
+            this._mapping.add(actor);
+
+            if (Main.overview.visible) {
+                let overviewHiddenId = Main.overview.connect('hidden', () => {
+                    Main.overview.disconnect(overviewHiddenId);
+                    this._mapSideComponent(shellwm, actor, false);
+                });
+                Main.overview.hide();
+            } else {
+                this._mapSideComponent(shellwm, actor, true);
+            }
+            return;
+        }
+
         switch (actor._windowType) {
         case Meta.WindowType.NORMAL:
             actor.set_pivot_point(0.5, 1.0);
@@ -1556,6 +1959,26 @@ var WindowManager = class {
             return;
         }
 
+        if (SideComponent.isSideComponentWindow(actor.meta_window)) {
+            this._destroying.add(actor);
+            this._slideSideComponentOut(
+                shellwm, actor, this._destroyWindowDone.bind(this));
+
+            // if the side component does not have the focus at this point,
+            // that means that it is closing because another window has gotten it
+            // and therefore we should not try to show the desktop
+            this._showDesktopOnDestroyDone =
+                actor.meta_window.has_focus() &&
+                SideComponent.launchedFromDesktop(actor.meta_window);
+
+            if (!this._showDesktopOnDestroyDone && SideComponent.shouldHideOtherWindows(actor.meta_window)) {
+                // reveal other windows while we slide out the side component
+                this._showOtherWindows(actor, true);
+            }
+
+            return;
+        }
+
         switch (actor.meta_window.window_type) {
         case Meta.WindowType.NORMAL:
             actor.set_pivot_point(0.5, 0.5);
@@ -1602,6 +2025,15 @@ var WindowManager = class {
                 parent.disconnect(actor._parentDestroyId);
                 actor._parentDestroyId = 0;
             }
+
+            if (SideComponent.isSideComponentWindow(actor.meta_window) &&
+                this._showDesktopOnDestroyDone) {
+                Main.overview.showApps();
+
+                if (SideComponent.shouldHideOtherWindows(actor.meta_window))
+                    this._showOtherWindows(actor, false);
+            }
+
             shellwm.completed_destroy(actor);
         }
     }
diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c
index 16abd42673..a75e728ebb 100644
--- a/src/shell-window-tracker.c
+++ b/src/shell-window-tracker.c
@@ -24,6 +24,7 @@
  * Copyright Red Hat, Inc. 2006-2008
  */
 
+#define SIDE_COMPONENT_ROLE "eos-side-component"
 #define SPEEDWAGON_ROLE "eos-speedwagon"
 
 /**
@@ -372,6 +373,10 @@ get_app_for_window (ShellWindowTracker    *tracker,
   MetaWindow *transient_for;
   const char *startup_id;
 
+  /* Side components don't have an associated app */
+  if (g_strcmp0 (meta_window_get_role (window), SIDE_COMPONENT_ROLE) == 0)
+    return NULL;
+
   transient_for = meta_window_get_transient_for (window);
   if (transient_for != NULL)
     return get_app_for_window (tracker, transient_for);
@@ -821,6 +826,9 @@ shell_window_tracker_get_startup_sequences (ShellWindowTracker *self)
 gboolean
 shell_window_tracker_is_window_interesting (MetaWindow *window)
 {
+  if (g_strcmp0 (meta_window_get_role (window), SIDE_COMPONENT_ROLE) == 0)
+    return TRUE;
+
   if (meta_window_is_skip_taskbar (window))
     return FALSE;
 


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