2011-06-13 09:54:05 -04:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
2011-07-14 08:35:55 -04:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2011-06-13 09:54:05 -04:00
|
|
|
const Lang = imports.lang;
|
2011-07-25 09:56:51 -04:00
|
|
|
const Mainloop = imports.mainloop;
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const Shell = imports.gi.Shell;
|
2011-06-13 09:54:05 -04:00
|
|
|
const Signals = imports.signals;
|
|
|
|
const St = imports.gi.St;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
2011-07-25 09:56:51 -04:00
|
|
|
const Params = imports.misc.params;
|
|
|
|
const ScreenSaver = imports.misc.screenSaver;
|
2011-07-14 08:35:55 -04:00
|
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
|
|
|
|
const HOT_CORNER_ACTIVATION_TIMEOUT = 0.5;
|
2011-06-13 09:54:05 -04:00
|
|
|
|
|
|
|
function LayoutManager() {
|
|
|
|
this._init.apply(this, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
LayoutManager.prototype = {
|
|
|
|
_init: function () {
|
|
|
|
this._rtl = (St.Widget.get_default_direction() == St.TextDirection.RTL);
|
|
|
|
this.monitors = [];
|
|
|
|
this.primaryMonitor = null;
|
|
|
|
this.primaryIndex = -1;
|
|
|
|
this._hotCorners = [];
|
|
|
|
|
2011-07-25 09:53:19 -04:00
|
|
|
global.screen.connect('monitors-changed', Lang.bind(this, this._monitorsChanged));
|
2011-06-13 09:54:05 -04:00
|
|
|
this._updateMonitors();
|
2011-07-25 09:53:19 -04:00
|
|
|
|
|
|
|
this._chrome = new Chrome(this);
|
|
|
|
this._updateHotCorners();
|
2011-06-13 09:54:05 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// This is called by Main after everything else is constructed;
|
2011-07-25 09:53:19 -04:00
|
|
|
// Chrome.init() needs access to Main.overview, which didn't exist
|
2011-06-13 09:54:05 -04:00
|
|
|
// yet when the LayoutManager was constructed.
|
|
|
|
init: function() {
|
2011-07-25 09:53:19 -04:00
|
|
|
this._chrome.init();
|
2011-06-13 09:54:05 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateMonitors: function() {
|
|
|
|
let screen = global.screen;
|
|
|
|
|
|
|
|
this.monitors = [];
|
|
|
|
let nMonitors = screen.get_n_monitors();
|
|
|
|
for (let i = 0; i < nMonitors; i++)
|
|
|
|
this.monitors.push(screen.get_monitor_geometry(i));
|
|
|
|
|
2011-06-13 10:37:10 -04:00
|
|
|
if (nMonitors == 1) {
|
|
|
|
this.primaryIndex = this.bottomIndex = 0;
|
|
|
|
} else {
|
|
|
|
// If there are monitors below the primary, then we need
|
|
|
|
// to split primary from bottom.
|
|
|
|
this.primaryIndex = this.bottomIndex = screen.get_primary_monitor();
|
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
|
|
|
let monitor = this.monitors[i];
|
|
|
|
if (this._isAboveOrBelowPrimary(monitor)) {
|
|
|
|
if (monitor.y > this.monitors[this.bottomIndex].y)
|
|
|
|
this.bottomIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-06-13 09:54:05 -04:00
|
|
|
this.primaryMonitor = this.monitors[this.primaryIndex];
|
2011-06-13 10:37:10 -04:00
|
|
|
this.bottomMonitor = this.monitors[this.bottomIndex];
|
2011-06-13 09:54:05 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateHotCorners: function() {
|
|
|
|
// destroy old hot corners
|
|
|
|
for (let i = 0; i < this._hotCorners.length; i++)
|
|
|
|
this._hotCorners[i].destroy();
|
|
|
|
this._hotCorners = [];
|
|
|
|
|
|
|
|
// build new hot corners
|
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
2011-07-14 08:35:55 -04:00
|
|
|
if (i == this.primaryIndex)
|
|
|
|
continue;
|
|
|
|
|
2011-06-13 09:54:05 -04:00
|
|
|
let monitor = this.monitors[i];
|
|
|
|
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
|
|
|
|
let cornerY = monitor.y;
|
|
|
|
|
|
|
|
let haveTopLeftCorner = true;
|
|
|
|
|
2011-07-14 08:35:55 -04:00
|
|
|
// Check if we have a top left (right for RTL) corner.
|
|
|
|
// I.e. if there is no monitor directly above or to the left(right)
|
|
|
|
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
|
|
|
|
let besideY = cornerY;
|
|
|
|
let aboveX = cornerX;
|
|
|
|
let aboveY = cornerY - 1;
|
|
|
|
|
|
|
|
for (let j = 0; j < this.monitors.length; j++) {
|
|
|
|
if (i == j)
|
|
|
|
continue;
|
|
|
|
let otherMonitor = this.monitors[j];
|
|
|
|
if (besideX >= otherMonitor.x &&
|
|
|
|
besideX < otherMonitor.x + otherMonitor.width &&
|
|
|
|
besideY >= otherMonitor.y &&
|
|
|
|
besideY < otherMonitor.y + otherMonitor.height) {
|
|
|
|
haveTopLeftCorner = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (aboveX >= otherMonitor.x &&
|
|
|
|
aboveX < otherMonitor.x + otherMonitor.width &&
|
|
|
|
aboveY >= otherMonitor.y &&
|
|
|
|
aboveY < otherMonitor.y + otherMonitor.height) {
|
|
|
|
haveTopLeftCorner = false;
|
|
|
|
break;
|
2011-06-13 09:54:05 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!haveTopLeftCorner)
|
|
|
|
continue;
|
|
|
|
|
2011-07-14 08:35:55 -04:00
|
|
|
let corner = new HotCorner();
|
2011-06-13 09:54:05 -04:00
|
|
|
this._hotCorners.push(corner);
|
|
|
|
corner.actor.set_position(cornerX, cornerY);
|
2011-07-25 09:53:19 -04:00
|
|
|
this._chrome.addActor(corner.actor);
|
2011-06-13 09:54:05 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_monitorsChanged: function() {
|
|
|
|
this._updateMonitors();
|
|
|
|
this._updateHotCorners();
|
|
|
|
|
|
|
|
this.emit('monitors-changed');
|
|
|
|
},
|
|
|
|
|
2011-06-13 10:37:10 -04:00
|
|
|
_isAboveOrBelowPrimary: function(monitor) {
|
|
|
|
let primary = this.monitors[this.primaryIndex];
|
|
|
|
let monitorLeft = monitor.x, monitorRight = monitor.x + monitor.width;
|
|
|
|
let primaryLeft = primary.x, primaryRight = primary.x + primary.width;
|
|
|
|
|
|
|
|
if ((monitorLeft >= primaryLeft && monitorLeft <= primaryRight) ||
|
|
|
|
(monitorRight >= primaryLeft && monitorRight <= primaryRight) ||
|
|
|
|
(primaryLeft >= monitorLeft && primaryLeft <= monitorRight) ||
|
|
|
|
(primaryRight >= monitorLeft && primaryRight <= monitorRight))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2011-06-13 09:54:05 -04:00
|
|
|
get focusIndex() {
|
2011-07-13 12:34:31 -04:00
|
|
|
let focusWindow = global.display.focus_window;
|
2011-06-13 09:54:05 -04:00
|
|
|
|
|
|
|
if (focusWindow) {
|
|
|
|
let wrect = focusWindow.get_outer_rect();
|
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
|
|
|
let monitor = this.monitors[i];
|
|
|
|
|
|
|
|
if (monitor.x <= wrect.x && monitor.y <= wrect.y &&
|
|
|
|
monitor.x + monitor.width > wrect.x &&
|
|
|
|
monitor.y + monitor.height > wrect.y)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.primaryIndex;
|
|
|
|
},
|
|
|
|
|
|
|
|
get focusMonitor() {
|
|
|
|
return this.monitors[this.focusIndex];
|
2011-07-25 09:53:19 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// addChrome:
|
|
|
|
// @actor: an actor to add to the chrome layer
|
|
|
|
// @params: (optional) additional params
|
|
|
|
//
|
|
|
|
// Adds @actor to the chrome layer and (unless %affectsInputRegion
|
|
|
|
// in @params 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 in @params 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 in @params is %true, the actor will be
|
|
|
|
// visible even when a fullscreen window should be covering it.
|
|
|
|
addChrome: function(actor, params) {
|
|
|
|
this._chrome.addActor(actor, params);
|
|
|
|
},
|
|
|
|
|
|
|
|
// trackChrome:
|
|
|
|
// @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 addChrome(). 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 addChrome(),
|
|
|
|
// 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.
|
|
|
|
trackChrome: function(actor, params) {
|
|
|
|
this._chrome.trackActor(actor, params);
|
|
|
|
},
|
|
|
|
|
|
|
|
// untrackChrome:
|
|
|
|
// @actor: an actor previously tracked via trackChrome()
|
|
|
|
//
|
|
|
|
// Undoes the effect of trackChrome()
|
|
|
|
untrackChrome: function(actor) {
|
|
|
|
this._chrome.untrackActor(actor);
|
|
|
|
},
|
|
|
|
|
|
|
|
// removeChrome:
|
|
|
|
// @actor: a child of the chrome layer
|
|
|
|
//
|
|
|
|
// Removes @actor from the chrome layer
|
|
|
|
removeChrome: function(actor) {
|
|
|
|
this._chrome.removeActor(actor);
|
2011-06-13 09:54:05 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(LayoutManager.prototype);
|
2011-07-14 08:35:55 -04:00
|
|
|
|
|
|
|
|
|
|
|
// HotCorner:
|
|
|
|
//
|
|
|
|
// This class manages a "hot corner" that can toggle switching to
|
|
|
|
// overview.
|
|
|
|
function HotCorner() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
HotCorner.prototype = {
|
|
|
|
_init : function() {
|
|
|
|
// We use this flag to mark the case where the user has entered the
|
|
|
|
// hot corner and has not left both the hot corner and a surrounding
|
|
|
|
// guard area (the "environs"). This avoids triggering the hot corner
|
|
|
|
// multiple times due to an accidental jitter.
|
|
|
|
this._entered = false;
|
|
|
|
|
|
|
|
this.actor = new Clutter.Group({ name: 'hot-corner-environs',
|
|
|
|
width: 3,
|
|
|
|
height: 3,
|
|
|
|
reactive: true });
|
|
|
|
|
|
|
|
this._corner = new Clutter.Rectangle({ name: 'hot-corner',
|
|
|
|
width: 1,
|
|
|
|
height: 1,
|
|
|
|
opacity: 0,
|
|
|
|
reactive: true });
|
|
|
|
this._corner._delegate = this;
|
|
|
|
|
|
|
|
this.actor.add_actor(this._corner);
|
|
|
|
|
|
|
|
if (St.Widget.get_default_direction() == St.TextDirection.RTL) {
|
|
|
|
this._corner.set_position(this.actor.width - this._corner.width, 0);
|
|
|
|
this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
|
|
|
} else {
|
|
|
|
this._corner.set_position(0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
this._activationTime = 0;
|
|
|
|
|
|
|
|
this.actor.connect('leave-event',
|
|
|
|
Lang.bind(this, this._onEnvironsLeft));
|
|
|
|
|
|
|
|
// Clicking on the hot corner environs should result in the
|
|
|
|
// same behavior as clicking on the hot corner.
|
|
|
|
this.actor.connect('button-release-event',
|
|
|
|
Lang.bind(this, this._onCornerClicked));
|
|
|
|
|
|
|
|
// In addition to being triggered by the mouse enter event,
|
|
|
|
// the hot corner can be triggered by clicking on it. This is
|
|
|
|
// useful if the user wants to undo the effect of triggering
|
|
|
|
// the hot corner once in the hot corner.
|
|
|
|
this._corner.connect('enter-event',
|
|
|
|
Lang.bind(this, this._onCornerEntered));
|
|
|
|
this._corner.connect('button-release-event',
|
|
|
|
Lang.bind(this, this._onCornerClicked));
|
|
|
|
this._corner.connect('leave-event',
|
|
|
|
Lang.bind(this, this._onCornerLeft));
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.actor.destroy();
|
|
|
|
},
|
|
|
|
|
|
|
|
_addRipple : function(delay, time, startScale, startOpacity, finalScale, finalOpacity) {
|
|
|
|
// We draw a ripple by using a source image and animating it scaling
|
|
|
|
// outwards and fading away. We want the ripples to move linearly
|
|
|
|
// or it looks unrealistic, but if the opacity of the ripple goes
|
|
|
|
// linearly to zero it fades away too quickly, so we use Tweener's
|
|
|
|
// 'onUpdate' to give a non-linear curve to the fade-away and make
|
|
|
|
// it more visible in the middle section.
|
|
|
|
|
|
|
|
let [x, y] = this._corner.get_transformed_position();
|
|
|
|
let ripple = new St.BoxLayout({ style_class: 'ripple-box',
|
|
|
|
opacity: 255 * Math.sqrt(startOpacity),
|
|
|
|
scale_x: startScale,
|
|
|
|
scale_y: startScale,
|
|
|
|
x: x,
|
|
|
|
y: y });
|
|
|
|
ripple._opacity = startOpacity;
|
|
|
|
if (ripple.get_direction() == St.TextDirection.RTL)
|
|
|
|
ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
|
|
|
Tweener.addTween(ripple, { _opacity: finalOpacity,
|
|
|
|
scale_x: finalScale,
|
|
|
|
scale_y: finalScale,
|
|
|
|
delay: delay,
|
|
|
|
time: time,
|
|
|
|
transition: 'linear',
|
|
|
|
onUpdate: function() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
|
|
|
|
onComplete: function() { ripple.destroy(); } });
|
|
|
|
Main.uiGroup.add_actor(ripple);
|
|
|
|
},
|
|
|
|
|
|
|
|
rippleAnimation: function() {
|
|
|
|
// Show three concentric ripples expanding outwards; the exact
|
|
|
|
// parameters were found by trial and error, so don't look
|
|
|
|
// for them to make perfect sense mathematically
|
|
|
|
|
|
|
|
// delay time scale opacity => scale opacity
|
|
|
|
this._addRipple(0.0, 0.83, 0.25, 1.0, 1.5, 0.0);
|
|
|
|
this._addRipple(0.05, 1.0, 0.0, 0.7, 1.25, 0.0);
|
|
|
|
this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0);
|
|
|
|
},
|
|
|
|
|
|
|
|
handleDragOver: function(source, actor, x, y, time) {
|
|
|
|
if (source != Main.xdndHandler)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!Main.overview.visible && !Main.overview.animationInProgress) {
|
|
|
|
this.rippleAnimation();
|
|
|
|
Main.overview.showTemporarily();
|
|
|
|
Main.overview.beginItemDrag(actor);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onCornerEntered : function() {
|
|
|
|
if (!this._entered) {
|
|
|
|
this._entered = true;
|
|
|
|
if (!Main.overview.animationInProgress) {
|
|
|
|
this._activationTime = Date.now() / 1000;
|
|
|
|
|
|
|
|
this.rippleAnimation();
|
|
|
|
Main.overview.toggle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onCornerClicked : function() {
|
|
|
|
if (this.shouldToggleOverviewOnClick())
|
|
|
|
Main.overview.toggle();
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onCornerLeft : function(actor, event) {
|
|
|
|
if (event.get_related() != this.actor)
|
|
|
|
this._entered = false;
|
|
|
|
// Consume event, otherwise this will confuse onEnvironsLeft
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onEnvironsLeft : function(actor, event) {
|
|
|
|
if (event.get_related() != this._corner)
|
|
|
|
this._entered = false;
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Checks if the Activities button is currently sensitive to
|
|
|
|
// clicks. The first call to this function within the
|
|
|
|
// HOT_CORNER_ACTIVATION_TIMEOUT time of the hot corner being
|
|
|
|
// triggered will return false. This avoids opening and closing
|
|
|
|
// the overview if the user both triggered the hot corner and
|
|
|
|
// clicked the Activities button.
|
|
|
|
shouldToggleOverviewOnClick: function() {
|
|
|
|
if (Main.overview.animationInProgress)
|
|
|
|
return false;
|
|
|
|
if (this._activationTime == 0 || Date.now() / 1000 - this._activationTime > HOT_CORNER_ACTIVATION_TIMEOUT)
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
2011-07-25 09:56:51 -04:00
|
|
|
|
|
|
|
|
|
|
|
// 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() {
|
2011-07-25 09:53:19 -04:00
|
|
|
this._init.apply(this, arguments);
|
2011-07-25 09:56:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Chrome.prototype = {
|
2011-07-25 09:53:19 -04:00
|
|
|
_init: function(layoutManager) {
|
|
|
|
this._layoutManager = layoutManager;
|
|
|
|
|
2011-07-25 09:56:51 -04:00
|
|
|
// 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 = [];
|
|
|
|
|
2011-07-25 09:53:19 -04:00
|
|
|
this._layoutManager.connect('monitors-changed',
|
|
|
|
Lang.bind(this, this._relayout));
|
2011-07-25 09:56:51 -04:00
|
|
|
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));
|
|
|
|
|
|
|
|
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();
|
|
|
|
},
|
|
|
|
|
2011-07-25 09:53:19 -04:00
|
|
|
init: function() {
|
|
|
|
Main.overview.connect('showing',
|
|
|
|
Lang.bind(this, this._overviewShowing));
|
|
|
|
Main.overview.connect('hidden',
|
|
|
|
Lang.bind(this, this._overviewHidden));
|
|
|
|
},
|
|
|
|
|
2011-07-25 09:56:51 -04:00
|
|
|
_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: function(actor, params) {
|
|
|
|
this.actor.add_actor(actor);
|
|
|
|
this._trackActor(actor, params);
|
|
|
|
},
|
|
|
|
|
|
|
|
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: function(actor) {
|
|
|
|
this._untrackActor(actor);
|
|
|
|
},
|
|
|
|
|
|
|
|
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() {
|
2011-07-25 09:53:19 -04:00
|
|
|
this._monitors = this._layoutManager.monitors;
|
|
|
|
this._primaryMonitor = this._layoutManager.primaryMonitor;
|
2011-07-25 09:56:51 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|