diff --git a/js/ui/layout.js b/js/ui/layout.js index 4ca2680a9..1fff9cc53 100644 --- a/js/ui/layout.js +++ b/js/ui/layout.js @@ -142,6 +142,7 @@ const LayoutManager = new Lang.Class({ this._updateRegionIdle = 0; this._trackedActors = []; + this._topActors = []; this._isPopupWindowVisible = false; this._startingUp = true; @@ -888,36 +889,78 @@ const LayoutManager = new Lang.Class({ }); }, + _disconnectTopActor: function(window) { + /* O-R status is immutable in Mutter */ + if (window.metaWindow.is_override_redirect()) { + window.disconnect(window._topActorPositionChangedId); + delete window._topActorPositionChangedId; + window.disconnect(window._topActorSizeChangedId); + delete window._topActorSizeChangedId; + } + }, + + _disconnectTopWindow: function(metaWindow) { + metaWindow.disconnect(metaWindow._topActorUnmanagedId); + delete window._topActorUnmanagedId; + metaWindow.disconnect(metaWindow._topActorNotifyFullscreenId); + delete window._topActorNotifyFullscreenId; + }, + + _topActorUnmanaged: function(metaWindow, window) { + /* Actor is already destroyed, so don't disconnect it */ + this._disconnectTopWindow(metaWindow); + + let i = this._topActors.indexOf(window); + this._topActors.splice(i, 1); + }, + + _topActorChanged: function() { + this._windowsRestacked(); + }, + _updateFullscreen: function() { + // Ordinary chrome should be visible unless the top window in + // the stack is monitor sized. We allow override-redirect + // windows to be above this "top window" because there can be + // O-R windows that are offscreen, or otherwise have no + // semantic meaning to the user. + // + // 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. let windows = this._getWindowActorsForWorkspace(global.screen.get_active_workspace()); // Reset all monitors to not fullscreen for (let i = 0; i < this.monitors.length; i++) this.monitors[i].inFullscreen = false; - // Ordinary chrome 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. + let oldTopActors = this._topActors; + let newTopActors = []; for (let i = windows.length - 1; i > -1; i--) { let window = windows[i]; let metaWindow = window.meta_window; - let layer = metaWindow.get_layer(); - // Skip minimized windows - if (!metaWindow.showing_on_its_workspace()) + // Because we are working with the list of actors, not the list of + // windows, we have an uncomfortable corner case to deal with. + // If a window is being hidden, it's actor will be left on top + // of the actor stack until any animation is done. (See + // meta_compositor_sync_stack()). These windows are actually at + // the bottom of the window stack, so if they return and become + // relevant again, we'll get another ::restacked signal, so we + // can just ignore them. This check *DOES NOT* handle destroyed + // windows correctly, but we don't currently animate such windows. + // If bugs show up here, instead of making this more complex, + // add a function to get the window stack from MetaStackTracker. + if (!metaWindow.showing_on_its_workspace ()) continue; - if (layer == Meta.StackLayer.FULLSCREEN || - (layer == Meta.StackLayer.OVERRIDE_REDIRECT && metaWindow.is_monitor_sized())) { + newTopActors.push(window); + + if (metaWindow.is_monitor_sized()) { if (metaWindow.is_screen_sized()) { for (let i = 0; i < this.monitors.length; i++) this.monitors[i].inFullscreen = true; @@ -928,8 +971,53 @@ const LayoutManager = new Lang.Class({ this.monitors[index].inFullscreen = true; } } + break; + } + + if (!window.is_override_redirect()) + break; + } + + // Deal with windows being added or removed from the "top actors" set. + // These are the actors that a change to could cause a change in + // our computed fullscreen status without a change in the stack. + for (let i = 0; i < oldTopActors.length; i++) { + let window = oldTopActors[i]; + if (newTopActors.indexOf(window) < 0) { + this._disconnectTopActor(window); + this._disconnectTopWindow(window.metaWindow); } } + + for (let i = 0; i < newTopActors.length; i++) { + let window = newTopActors[i]; + + if (oldTopActors.indexOf(window) < 0) { + window.metaWindow._topActorUnmanagedId = + window.metaWindow.connect('unmanaged', + Lang.bind(this, this._topActorUnmanaged, window)); + window.metaWindow._topActorNotifyFullscreenId = + window.metaWindow.connect('notify::fullscreen', + Lang.bind(this, this._topActorChanged)); + + /* In almost all cases, meta_window_is_monitor_size() depends + * on position only for O-R windows. The remaining case is a non-OR, + * non-fullscreen window which is screen sized. That is highly + * unlikely and probably should be excluded in + * meta_window_is_monitor_size(). + */ + if (window.metaWindow.is_override_redirect()) { + window._topActorPositionChangedId = + window.connect('position-changed', + Lang.bind(this, this._topActorChanged)); + window._topActorSizeChangedId = + window.connect('size-changed', + Lang.bind(this, this._topActorChanged)); + } + } + } + + this._topActors = newTopActors; }, _windowsRestacked: function() {