2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2011-06-13 13:54:05 +00:00
|
|
|
|
2011-07-14 12:35:55 +00:00
|
|
|
const Clutter = imports.gi.Clutter;
|
2013-02-21 19:32:23 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
2012-08-14 13:37:12 +00:00
|
|
|
const GObject = imports.gi.GObject;
|
2011-06-13 13:54:05 +00:00
|
|
|
const Lang = imports.lang;
|
2011-07-25 13:56:51 +00:00
|
|
|
const Mainloop = imports.mainloop;
|
|
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const Shell = imports.gi.Shell;
|
2011-06-13 13:54:05 +00:00
|
|
|
const Signals = imports.signals;
|
|
|
|
const St = imports.gi.St;
|
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
const Background = imports.ui.background;
|
|
|
|
const BackgroundMenu = imports.ui.backgroundMenu;
|
|
|
|
|
2012-02-11 10:14:43 +00:00
|
|
|
const DND = imports.ui.dnd;
|
2011-06-13 13:54:05 +00:00
|
|
|
const Main = imports.ui.main;
|
2011-07-25 13:56:51 +00:00
|
|
|
const Params = imports.misc.params;
|
2011-07-14 12:35:55 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
const STARTUP_ANIMATION_TIME = 0.5;
|
2012-11-15 15:35:20 +00:00
|
|
|
const KEYBOARD_ANIMATION_TIME = 0.15;
|
2013-02-20 01:17:55 +00:00
|
|
|
const BACKGROUND_FADE_ANIMATION_TIME = 1.0;
|
2013-01-10 21:02:20 +00:00
|
|
|
const DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);
|
2011-07-25 18:25:51 +00:00
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
// The message tray takes this much pressure
|
|
|
|
// in the pressure barrier at once to release it.
|
2013-02-21 21:43:28 +00:00
|
|
|
const MESSAGE_TRAY_PRESSURE_THRESHOLD = 250; // pixels
|
|
|
|
const MESSAGE_TRAY_PRESSURE_TIMEOUT = 1000; // ms
|
2012-07-30 19:25:07 +00:00
|
|
|
|
2013-03-01 21:19:07 +00:00
|
|
|
const HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
|
|
|
|
const HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms
|
|
|
|
|
2014-02-12 16:40:01 +00:00
|
|
|
// We scale up when the dpi is higher then this (same value used by gsd)
|
|
|
|
const HIGH_DPI_LIMIT = 192;
|
|
|
|
|
2013-02-12 21:00:41 +00:00
|
|
|
function isPopupMetaWindow(actor) {
|
|
|
|
switch(actor.meta_window.get_window_type()) {
|
|
|
|
case Meta.WindowType.DROPDOWN_MENU:
|
|
|
|
case Meta.WindowType.POPUP_MENU:
|
|
|
|
case Meta.WindowType.COMBO:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-14 13:37:12 +00:00
|
|
|
const MonitorConstraint = new Lang.Class({
|
|
|
|
Name: 'MonitorConstraint',
|
|
|
|
Extends: Clutter.Constraint,
|
|
|
|
Properties: {'primary': GObject.ParamSpec.boolean('primary',
|
|
|
|
'Primary', 'Track primary monitor',
|
|
|
|
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
|
|
|
|
false),
|
|
|
|
'index': GObject.ParamSpec.int('index',
|
|
|
|
'Monitor index', 'Track specific monitor',
|
|
|
|
GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
|
|
|
|
-1, 64, -1)},
|
|
|
|
|
|
|
|
_init: function(props) {
|
|
|
|
this._primary = false;
|
|
|
|
this._index = -1;
|
|
|
|
|
|
|
|
this.parent(props);
|
|
|
|
},
|
|
|
|
|
|
|
|
get primary() {
|
|
|
|
return this._primary;
|
|
|
|
},
|
|
|
|
|
|
|
|
set primary(v) {
|
2012-10-14 16:34:22 +00:00
|
|
|
if (v)
|
|
|
|
this._index = -1;
|
2012-08-14 13:37:12 +00:00
|
|
|
this._primary = v;
|
|
|
|
if (this.actor)
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
this.notify('primary');
|
|
|
|
},
|
|
|
|
|
|
|
|
get index() {
|
|
|
|
return this._index;
|
|
|
|
},
|
|
|
|
|
|
|
|
set index(v) {
|
2012-10-14 16:34:22 +00:00
|
|
|
this._primary = false;
|
2012-08-14 13:37:12 +00:00
|
|
|
this._index = v;
|
|
|
|
if (this.actor)
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
this.notify('index');
|
|
|
|
},
|
|
|
|
|
|
|
|
vfunc_set_actor: function(actor) {
|
|
|
|
if (actor) {
|
|
|
|
if (!this._monitorsChangedId) {
|
|
|
|
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, function() {
|
|
|
|
this.actor.queue_relayout();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (this._monitorsChangedId)
|
|
|
|
Main.layoutManager.disconnect(this._monitorsChangedId);
|
|
|
|
this._monitorsChangedId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.parent(actor);
|
|
|
|
},
|
|
|
|
|
|
|
|
vfunc_update_allocation: function(actor, actorBox) {
|
|
|
|
if (!this._primary && this._index < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let monitor;
|
|
|
|
if (this._primary) {
|
|
|
|
monitor = Main.layoutManager.primaryMonitor;
|
|
|
|
} else {
|
2012-08-16 20:12:23 +00:00
|
|
|
let index = Math.min(this._index, Main.layoutManager.monitors.length - 1);
|
2012-08-14 13:37:12 +00:00
|
|
|
monitor = Main.layoutManager.monitors[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
actorBox.init_rect(monitor.x, monitor.y, monitor.width, monitor.height);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2013-03-18 19:02:05 +00:00
|
|
|
const Monitor = new Lang.Class({
|
|
|
|
Name: 'Monitor',
|
|
|
|
|
|
|
|
_init: function(index, geometry) {
|
|
|
|
this.index = index;
|
|
|
|
this.x = geometry.x;
|
|
|
|
this.y = geometry.y;
|
|
|
|
this.width = geometry.width;
|
|
|
|
this.height = geometry.height;
|
|
|
|
},
|
|
|
|
|
|
|
|
get inFullscreen() {
|
|
|
|
return global.screen.get_monitor_in_fullscreen(this.index);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
const defaultParams = {
|
|
|
|
trackFullscreen: false,
|
|
|
|
affectsStruts: false,
|
|
|
|
};
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const LayoutManager = new Lang.Class({
|
|
|
|
Name: 'LayoutManager',
|
2011-06-13 13:54:05 +00:00
|
|
|
|
|
|
|
_init: function () {
|
2012-02-14 01:37:28 +00:00
|
|
|
this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
|
2011-06-13 13:54:05 +00:00
|
|
|
this.monitors = [];
|
|
|
|
this.primaryMonitor = null;
|
|
|
|
this.primaryIndex = -1;
|
2013-03-01 21:00:37 +00:00
|
|
|
this.hotCorners = [];
|
|
|
|
|
2012-10-14 16:57:45 +00:00
|
|
|
this._keyboardIndex = -1;
|
2012-07-30 17:02:33 +00:00
|
|
|
this._rightPanelBarrier = null;
|
2012-07-30 19:25:07 +00:00
|
|
|
this._trayBarrier = null;
|
2011-07-25 13:53:19 +00:00
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
this._inOverview = false;
|
|
|
|
this._updateRegionIdle = 0;
|
|
|
|
|
|
|
|
this._trackedActors = [];
|
2013-03-12 19:27:51 +00:00
|
|
|
this._topActors = [];
|
2013-02-12 21:00:41 +00:00
|
|
|
this._isPopupWindowVisible = false;
|
2013-02-20 01:20:44 +00:00
|
|
|
this._startingUp = true;
|
2011-07-25 18:25:51 +00:00
|
|
|
|
2013-01-10 21:02:20 +00:00
|
|
|
// Normally, the stage is always covered so Clutter doesn't need to clear
|
|
|
|
// it; however it becomes visible during the startup animation
|
|
|
|
// See the comment below for a longer explanation
|
|
|
|
global.stage.color = DEFAULT_BACKGROUND_COLOR;
|
|
|
|
|
|
|
|
// Set up stage hierarchy to group all UI actors under one container.
|
|
|
|
this.uiGroup = new Shell.GenericContainer({ name: 'uiGroup' });
|
|
|
|
this.uiGroup.connect('allocate',
|
|
|
|
function (actor, box, flags) {
|
|
|
|
let children = actor.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++)
|
|
|
|
children[i].allocate_preferred_size(flags);
|
|
|
|
});
|
|
|
|
this.uiGroup.connect('get-preferred-width',
|
|
|
|
function(actor, forHeight, alloc) {
|
|
|
|
let width = global.stage.width;
|
|
|
|
[alloc.min_size, alloc.natural_size] = [width, width];
|
|
|
|
});
|
|
|
|
this.uiGroup.connect('get-preferred-height',
|
|
|
|
function(actor, forWidth, alloc) {
|
|
|
|
let height = global.stage.height;
|
|
|
|
[alloc.min_size, alloc.natural_size] = [height, height];
|
|
|
|
});
|
|
|
|
|
2013-02-13 17:30:26 +00:00
|
|
|
global.stage.remove_actor(global.window_group);
|
|
|
|
this.uiGroup.add_actor(global.window_group);
|
2013-01-10 21:02:20 +00:00
|
|
|
|
|
|
|
global.stage.add_child(this.uiGroup);
|
|
|
|
|
2013-05-22 16:05:24 +00:00
|
|
|
this.overviewGroup = new St.Widget({ name: 'overviewGroup',
|
|
|
|
visible: false });
|
|
|
|
this.addChrome(this.overviewGroup);
|
|
|
|
|
2012-05-22 22:27:06 +00:00
|
|
|
this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup',
|
|
|
|
visible: false,
|
|
|
|
clip_to_allocation: true,
|
2012-08-14 13:39:17 +00:00
|
|
|
layout_manager: new Clutter.BinLayout(),
|
2012-05-22 22:27:06 +00:00
|
|
|
});
|
|
|
|
this.addChrome(this.screenShieldGroup);
|
|
|
|
|
2011-07-25 18:25:51 +00:00
|
|
|
this.panelBox = new St.BoxLayout({ name: 'panelBox',
|
|
|
|
vertical: true });
|
2012-05-22 20:16:31 +00:00
|
|
|
this.addChrome(this.panelBox, { affectsStruts: true,
|
|
|
|
trackFullscreen: true });
|
2011-07-25 18:25:51 +00:00
|
|
|
this.panelBox.connect('allocation-changed',
|
2012-12-03 20:56:18 +00:00
|
|
|
Lang.bind(this, this._panelBoxChanged));
|
2011-07-25 18:25:51 +00:00
|
|
|
|
2012-09-05 00:26:09 +00:00
|
|
|
this.trayBox = new St.Widget({ name: 'trayBox',
|
|
|
|
layout_manager: new Clutter.BinLayout() });
|
2012-05-22 20:16:31 +00:00
|
|
|
this.addChrome(this.trayBox);
|
2013-03-01 19:57:38 +00:00
|
|
|
this._setupTrayPressure();
|
2011-08-29 15:11:22 +00:00
|
|
|
|
2011-09-09 14:56:51 +00:00
|
|
|
this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
|
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
2012-05-22 20:16:31 +00:00
|
|
|
this.addChrome(this.keyboardBox);
|
2011-09-01 15:37:54 +00:00
|
|
|
this._keyboardHeightNotifyId = 0;
|
2011-07-25 18:25:51 +00:00
|
|
|
|
2013-02-15 09:56:34 +00:00
|
|
|
// A dummy actor that tracks the mouse or text cursor, based on the
|
|
|
|
// position set in setDummyCursorPosition.
|
|
|
|
this.dummyCursor = new St.Widget({ width: 0, height: 0 });
|
|
|
|
this.uiGroup.add_actor(this.dummyCursor);
|
|
|
|
|
2013-02-12 21:00:41 +00:00
|
|
|
global.stage.remove_actor(global.top_window_group);
|
|
|
|
this.uiGroup.add_actor(global.top_window_group);
|
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
this._backgroundGroup = new Meta.BackgroundGroup();
|
|
|
|
global.window_group.add_child(this._backgroundGroup);
|
|
|
|
this._backgroundGroup.lower_bottom();
|
|
|
|
this._bgManagers = [];
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
// Need to update struts on new workspaces when they are added
|
|
|
|
global.screen.connect('notify::n-workspaces',
|
|
|
|
Lang.bind(this, this._queueUpdateRegions));
|
|
|
|
global.screen.connect('restacked',
|
|
|
|
Lang.bind(this, this._windowsRestacked));
|
2011-07-25 18:25:51 +00:00
|
|
|
global.screen.connect('monitors-changed',
|
|
|
|
Lang.bind(this, this._monitorsChanged));
|
2013-03-14 18:36:13 +00:00
|
|
|
global.screen.connect('in-fullscreen-changed',
|
|
|
|
Lang.bind(this, this._updateFullscreen));
|
2011-07-25 18:25:51 +00:00
|
|
|
this._monitorsChanged();
|
2011-06-13 13:54:05 +00:00
|
|
|
},
|
|
|
|
|
2013-05-22 16:05:24 +00:00
|
|
|
// This is called by Main after everything else is constructed
|
2011-06-13 13:54:05 +00:00
|
|
|
init: function() {
|
2013-01-28 04:34:33 +00:00
|
|
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
2013-03-06 22:55:33 +00:00
|
|
|
|
2013-05-18 23:34:35 +00:00
|
|
|
this._loadBackground();
|
2013-01-28 04:34:33 +00:00
|
|
|
},
|
|
|
|
|
2013-05-22 16:05:24 +00:00
|
|
|
showOverview: function() {
|
|
|
|
this.overviewGroup.show();
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
this._inOverview = true;
|
|
|
|
this._updateVisibility();
|
2013-10-11 18:10:24 +00:00
|
|
|
this._updateRegions();
|
2013-01-28 04:34:33 +00:00
|
|
|
},
|
2011-07-25 18:25:51 +00:00
|
|
|
|
2013-05-22 16:05:24 +00:00
|
|
|
hideOverview: function() {
|
|
|
|
this.overviewGroup.hide();
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
this._inOverview = false;
|
|
|
|
this._updateVisibility();
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_sessionUpdated: function() {
|
|
|
|
this._updateVisibility();
|
|
|
|
this._queueUpdateRegions();
|
2011-06-13 13:54:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateMonitors: function() {
|
|
|
|
let screen = global.screen;
|
|
|
|
|
|
|
|
this.monitors = [];
|
|
|
|
let nMonitors = screen.get_n_monitors();
|
|
|
|
for (let i = 0; i < nMonitors; i++)
|
2013-03-18 19:02:05 +00:00
|
|
|
this.monitors.push(new Monitor(i, screen.get_monitor_geometry(i)));
|
2011-06-13 13:54:05 +00:00
|
|
|
|
2011-06-13 14:37:10 +00: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 13:54:05 +00:00
|
|
|
this.primaryMonitor = this.monitors[this.primaryIndex];
|
2011-06-13 14:37:10 +00:00
|
|
|
this.bottomMonitor = this.monitors[this.bottomIndex];
|
2011-06-13 13:54:05 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateHotCorners: function() {
|
|
|
|
// destroy old hot corners
|
2013-03-19 19:50:16 +00:00
|
|
|
this.hotCorners.forEach(function(corner) {
|
|
|
|
if (corner)
|
|
|
|
corner.destroy();
|
|
|
|
});
|
2013-03-01 21:00:37 +00:00
|
|
|
this.hotCorners = [];
|
2011-06-13 13:54:05 +00:00
|
|
|
|
2013-03-01 20:07:11 +00:00
|
|
|
let size = this.panelBox.height;
|
|
|
|
|
2011-06-13 13:54:05 +00:00
|
|
|
// build new hot corners
|
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
|
|
|
let monitor = this.monitors[i];
|
|
|
|
let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
|
|
|
|
let cornerY = monitor.y;
|
|
|
|
|
2013-03-19 19:50:16 +00:00
|
|
|
let haveTopLeftCorner = true;
|
2011-06-13 13:54:05 +00:00
|
|
|
|
2013-03-19 19:50:16 +00:00
|
|
|
if (i != this.primaryIndex) {
|
2013-03-01 21:00:37 +00: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;
|
2011-07-14 12:35:55 +00:00
|
|
|
|
2013-03-01 21:00:37 +00:00
|
|
|
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 13:54:05 +00:00
|
|
|
}
|
2013-03-01 21:00:37 +00:00
|
|
|
}
|
2011-06-13 13:54:05 +00:00
|
|
|
|
2013-03-19 19:50:16 +00:00
|
|
|
if (haveTopLeftCorner) {
|
|
|
|
let corner = new HotCorner(this, monitor, cornerX, cornerY);
|
|
|
|
corner.setBarrierSize(size);
|
|
|
|
this.hotCorners.push(corner);
|
|
|
|
} else {
|
|
|
|
this.hotCorners.push(null);
|
|
|
|
}
|
2011-06-13 13:54:05 +00:00
|
|
|
}
|
2013-03-01 21:00:37 +00:00
|
|
|
|
|
|
|
this.emit('hot-corners-changed');
|
2011-06-13 13:54:05 +00:00
|
|
|
},
|
|
|
|
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
_addBackgroundMenu: function(bgManager) {
|
|
|
|
BackgroundMenu.addBackgroundMenu(bgManager.background.actor, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
_createBackgroundManager: function(monitorIndex) {
|
2012-12-24 14:20:39 +00:00
|
|
|
let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
|
|
|
|
layoutManager: this,
|
|
|
|
monitorIndex: monitorIndex });
|
|
|
|
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
bgManager.connect('changed', Lang.bind(this, this._addBackgroundMenu));
|
|
|
|
this._addBackgroundMenu(bgManager);
|
2012-12-24 14:20:39 +00:00
|
|
|
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
return bgManager;
|
2012-12-24 14:20:39 +00:00
|
|
|
},
|
|
|
|
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
_showSecondaryBackgrounds: function() {
|
2012-12-24 14:20:39 +00:00
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
|
|
|
if (i != this.primaryIndex) {
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
let background = this._bgManagers[i].background;
|
|
|
|
background.actor.show();
|
2012-12-24 14:20:39 +00:00
|
|
|
background.actor.opacity = 0;
|
|
|
|
Tweener.addTween(background.actor,
|
|
|
|
{ opacity: 255,
|
|
|
|
time: BACKGROUND_FADE_ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateBackgrounds: function() {
|
|
|
|
let i;
|
|
|
|
for (i = 0; i < this._bgManagers.length; i++)
|
|
|
|
this._bgManagers[i].destroy();
|
|
|
|
|
|
|
|
this._bgManagers = [];
|
|
|
|
|
|
|
|
if (Main.sessionMode.isGreeter)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (let i = 0; i < this.monitors.length; i++) {
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
let bgManager = this._createBackgroundManager(i);
|
|
|
|
this._bgManagers.push(bgManager);
|
|
|
|
|
|
|
|
if (i != this.primaryIndex && this._startingUp)
|
|
|
|
bgManager.background.actor.hide();
|
2012-12-24 14:20:39 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-07-25 18:25:51 +00:00
|
|
|
_updateBoxes: function() {
|
2012-05-24 20:47:48 +00:00
|
|
|
this.screenShieldGroup.set_position(0, 0);
|
|
|
|
this.screenShieldGroup.set_size(global.screen_width, global.screen_height);
|
|
|
|
|
2011-07-25 18:25:51 +00:00
|
|
|
this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y);
|
|
|
|
this.panelBox.set_size(this.primaryMonitor.width, -1);
|
|
|
|
|
2012-10-14 16:57:45 +00:00
|
|
|
if (this.keyboardIndex < 0)
|
|
|
|
this.keyboardIndex = this.primaryIndex;
|
2011-08-29 15:11:22 +00:00
|
|
|
|
2011-09-01 15:37:54 +00:00
|
|
|
this.trayBox.set_position(this.bottomMonitor.x,
|
|
|
|
this.bottomMonitor.y + this.bottomMonitor.height);
|
|
|
|
this.trayBox.set_size(this.bottomMonitor.width, -1);
|
2011-07-25 18:25:51 +00:00
|
|
|
},
|
|
|
|
|
2012-12-03 20:56:18 +00:00
|
|
|
_panelBoxChanged: function() {
|
2013-03-01 20:07:11 +00:00
|
|
|
this._updatePanelBarrier();
|
2012-12-03 20:56:18 +00:00
|
|
|
|
2013-03-01 20:07:11 +00:00
|
|
|
let size = this.panelBox.height;
|
|
|
|
this.hotCorners.forEach(function(corner) {
|
2013-03-19 19:50:16 +00:00
|
|
|
if (corner)
|
|
|
|
corner.setBarrierSize(size);
|
2013-03-01 20:07:11 +00:00
|
|
|
});
|
|
|
|
},
|
2012-07-30 17:02:33 +00:00
|
|
|
|
2013-03-01 20:07:11 +00:00
|
|
|
_updatePanelBarrier: function() {
|
2012-07-30 17:02:33 +00:00
|
|
|
if (this._rightPanelBarrier) {
|
|
|
|
this._rightPanelBarrier.destroy();
|
|
|
|
this._rightPanelBarrier = null;
|
|
|
|
}
|
2011-07-25 18:25:51 +00:00
|
|
|
|
|
|
|
if (this.panelBox.height) {
|
|
|
|
let primary = this.primaryMonitor;
|
2012-07-30 17:02:33 +00:00
|
|
|
|
|
|
|
this._rightPanelBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: primary.x + primary.width, y1: primary.y,
|
|
|
|
x2: primary.x + primary.width, y2: primary.y + this.panelBox.height,
|
|
|
|
directions: Meta.BarrierDirection.NEGATIVE_X });
|
2011-07-25 18:25:51 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
_setupTrayPressure: function() {
|
|
|
|
this._trayPressure = new PressureBarrier(MESSAGE_TRAY_PRESSURE_THRESHOLD,
|
|
|
|
MESSAGE_TRAY_PRESSURE_TIMEOUT,
|
|
|
|
Shell.KeyBindingMode.NORMAL |
|
|
|
|
Shell.KeyBindingMode.OVERVIEW);
|
|
|
|
this._trayPressure.setEventFilter(this._trayBarrierEventFilter);
|
|
|
|
this._trayPressure.connect('trigger', function(barrier) {
|
2013-03-10 02:11:46 +00:00
|
|
|
if (Main.layoutManager.bottomMonitor.inFullscreen)
|
|
|
|
return;
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
Main.messageTray.openTray();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
_updateTrayBarrier: function() {
|
|
|
|
let monitor = this.bottomMonitor;
|
|
|
|
|
|
|
|
if (this._trayBarrier) {
|
2013-03-01 19:57:38 +00:00
|
|
|
this._trayPressure.removeBarrier(this._trayBarrier);
|
2012-07-30 19:25:07 +00:00
|
|
|
this._trayBarrier.destroy();
|
2013-02-18 09:45:06 +00:00
|
|
|
this._trayBarrier = null;
|
2012-07-30 19:25:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this._trayBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: monitor.x, x2: monitor.x + monitor.width,
|
|
|
|
y1: monitor.y + monitor.height, y2: monitor.y + monitor.height,
|
|
|
|
directions: Meta.BarrierDirection.NEGATIVE_Y });
|
2013-03-01 19:57:38 +00:00
|
|
|
this._trayPressure.addBarrier(this._trayBarrier);
|
2012-07-30 19:25:07 +00:00
|
|
|
},
|
|
|
|
|
2013-03-01 20:33:39 +00:00
|
|
|
_trayBarrierEventFilter: function(event) {
|
|
|
|
// Throw out all events where the pointer was grabbed by another
|
|
|
|
// client, as the client that grabbed the pointer expects to have
|
|
|
|
// complete control over it
|
|
|
|
if (event.grabbed && Main.modalCount == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2014-02-12 16:40:01 +00:00
|
|
|
_updateScaling: function() {
|
|
|
|
let primary = this.monitors[this.primaryIndex];
|
|
|
|
let dpi_x = primary.width / (global.gdk_screen.get_monitor_width_mm(this.primaryIndex) / 25.4);
|
|
|
|
let dpi_y = primary.height / (global.gdk_screen.get_monitor_height_mm(this.primaryIndex) / 25.4);
|
|
|
|
if (dpi_x > HIGH_DPI_LIMIT && dpi_y > HIGH_DPI_LIMIT)
|
|
|
|
St.ThemeContext.get_for_stage(global.stage).scale_factor = 2;
|
|
|
|
else
|
|
|
|
St.ThemeContext.get_for_stage(global.stage).scale_factor = 1;
|
|
|
|
},
|
|
|
|
|
2011-06-13 13:54:05 +00:00
|
|
|
_monitorsChanged: function() {
|
|
|
|
this._updateMonitors();
|
2014-02-12 16:40:01 +00:00
|
|
|
this._updateScaling();
|
2011-07-25 18:25:51 +00:00
|
|
|
this._updateBoxes();
|
2012-07-30 19:25:07 +00:00
|
|
|
this._updateTrayBarrier();
|
2011-06-13 13:54:05 +00:00
|
|
|
this._updateHotCorners();
|
2012-12-24 14:20:39 +00:00
|
|
|
this._updateBackgrounds();
|
2013-01-28 04:34:33 +00:00
|
|
|
this._updateFullscreen();
|
|
|
|
this._updateVisibility();
|
|
|
|
this._queueUpdateRegions();
|
2011-06-13 13:54:05 +00:00
|
|
|
|
|
|
|
this.emit('monitors-changed');
|
|
|
|
},
|
|
|
|
|
2011-06-13 14:37:10 +00: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;
|
|
|
|
|
2011-10-10 17:21:18 +00:00
|
|
|
if ((monitorLeft >= primaryLeft && monitorLeft < primaryRight) ||
|
|
|
|
(monitorRight > primaryLeft && monitorRight <= primaryRight) ||
|
|
|
|
(primaryLeft >= monitorLeft && primaryLeft < monitorRight) ||
|
|
|
|
(primaryRight > monitorLeft && primaryRight <= monitorRight))
|
2011-06-13 14:37:10 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-06-08 08:26:58 +00:00
|
|
|
get currentMonitor() {
|
|
|
|
let index = global.screen.get_current_monitor();
|
2012-08-22 07:15:53 +00:00
|
|
|
return this.monitors[index];
|
2011-07-25 13:53:19 +00:00
|
|
|
},
|
|
|
|
|
2012-10-14 16:57:45 +00:00
|
|
|
get keyboardMonitor() {
|
|
|
|
return this.monitors[this.keyboardIndex];
|
|
|
|
},
|
|
|
|
|
|
|
|
get focusIndex() {
|
|
|
|
let i = Main.layoutManager.primaryIndex;
|
|
|
|
|
Rework window / actor focus handling
The duality of the Clutter's key focus and mutter's window focus has long been
a problem for us in lots of case, and caused us to create large and complicated
hacks to get around the issue, including GrabHelper's focus grab model.
Instead of doing this, tie basic focus management into the core of gnome-shell,
instead of requiring complex "application-level" management to get it done
right.
Do this by making sure that only one of an actor or window can be focused at
the same time, and apply the appropriate logic to drop one or the other,
reactively.
Modals are considered a special case, as we grab all keyboard events, but at
the X level, the client window still has focus. Make sure to not do any input
synchronization when we have a modal.
At the same time, remove the FOCUSED input mode, as it's no longer necessary.
https://bugzilla.gnome.org/show_bug.cgi?id=700735
2013-05-18 04:18:13 +00:00
|
|
|
if (global.stage.key_focus != null)
|
|
|
|
i = this.findIndexForActor(global.stage.key_focus);
|
|
|
|
else if (global.display.focus_window != null)
|
|
|
|
i = global.display.focus_window.get_monitor();
|
2012-10-14 16:57:45 +00:00
|
|
|
return i;
|
|
|
|
},
|
|
|
|
|
|
|
|
get focusMonitor() {
|
|
|
|
return this.monitors[this.focusIndex];
|
|
|
|
},
|
|
|
|
|
|
|
|
set keyboardIndex(v) {
|
|
|
|
this._keyboardIndex = v;
|
|
|
|
this.keyboardBox.set_position(this.keyboardMonitor.x,
|
|
|
|
this.keyboardMonitor.y + this.keyboardMonitor.height);
|
|
|
|
this.keyboardBox.set_size(this.keyboardMonitor.width, -1);
|
|
|
|
},
|
|
|
|
|
|
|
|
get keyboardIndex() {
|
|
|
|
return this._keyboardIndex;
|
|
|
|
},
|
|
|
|
|
2013-05-18 23:34:35 +00:00
|
|
|
_loadBackground: function() {
|
|
|
|
this._systemBackground = new Background.SystemBackground();
|
|
|
|
this._systemBackground.actor.hide();
|
|
|
|
|
|
|
|
global.stage.insert_child_below(this._systemBackground.actor, null);
|
|
|
|
|
|
|
|
let constraint = new Clutter.BindConstraint({ source: global.stage,
|
|
|
|
coordinate: Clutter.BindCoordinate.ALL });
|
|
|
|
this._systemBackground.actor.add_constraint(constraint);
|
|
|
|
|
|
|
|
let signalId = this._systemBackground.connect('loaded', Lang.bind(this, function() {
|
|
|
|
this._systemBackground.disconnect(signalId);
|
|
|
|
this._systemBackground.actor.show();
|
|
|
|
global.stage.show();
|
|
|
|
|
|
|
|
this._prepareStartupAnimation();
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
// 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.
|
|
|
|
|
2013-03-06 22:55:33 +00:00
|
|
|
_prepareStartupAnimation: function() {
|
2013-05-18 23:06:50 +00:00
|
|
|
// During the initial transition, add a simple actor to block all events,
|
|
|
|
// so they don't get delivered to X11 windows that have been transformed.
|
|
|
|
this._coverPane = new Clutter.Actor({ opacity: 0,
|
|
|
|
width: global.screen_width,
|
|
|
|
height: global.screen_height,
|
|
|
|
reactive: true });
|
|
|
|
this.addChrome(this._coverPane);
|
2012-12-24 14:20:39 +00:00
|
|
|
|
|
|
|
if (Main.sessionMode.isGreeter) {
|
|
|
|
this.panelBox.translation_y = -this.panelBox.height;
|
|
|
|
} else {
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
this._updateBackgrounds();
|
2013-08-20 22:29:02 +00:00
|
|
|
|
2013-02-20 04:21:35 +00:00
|
|
|
// We need to force an update of the regions now before we scale
|
|
|
|
// the UI group to get the coorect allocation for the struts.
|
|
|
|
this._updateRegions();
|
|
|
|
|
2013-02-20 00:56:56 +00:00
|
|
|
this.trayBox.hide();
|
|
|
|
this.keyboardBox.hide();
|
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
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);
|
2013-11-20 01:35:30 +00:00
|
|
|
this.uiGroup.scale_x = this.uiGroup.scale_y = 0.75;
|
2013-02-21 19:38:50 +00:00
|
|
|
this.uiGroup.opacity = 0;
|
2013-03-20 00:16:54 +00:00
|
|
|
global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height);
|
2012-12-24 14:20:39 +00:00
|
|
|
}
|
2013-02-21 19:32:23 +00:00
|
|
|
|
2013-05-18 23:34:35 +00:00
|
|
|
this.emit('startup-prepared');
|
|
|
|
|
|
|
|
// We're mostly prepared for the startup animation
|
|
|
|
// now, but since a lot is going on asynchronously
|
|
|
|
// during startup, let's defer the startup animation
|
|
|
|
// until the event loop is uncontended and idle.
|
|
|
|
// This helps to prevent us from running the animation
|
|
|
|
// when the system is bogged down
|
|
|
|
GLib.idle_add(GLib.PRIORITY_LOW, Lang.bind(this, function() {
|
|
|
|
this._startupAnimation();
|
2013-11-29 00:45:39 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2013-05-18 23:28:45 +00:00
|
|
|
}));
|
2012-08-22 07:22:30 +00:00
|
|
|
},
|
|
|
|
|
2013-03-06 22:55:33 +00:00
|
|
|
_startupAnimation: function() {
|
2012-12-24 14:20:39 +00:00
|
|
|
if (Main.sessionMode.isGreeter)
|
|
|
|
this._startupAnimationGreeter();
|
|
|
|
else
|
|
|
|
this._startupAnimationSession();
|
|
|
|
},
|
2011-09-02 20:36:02 +00:00
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
_startupAnimationGreeter: function() {
|
2013-02-20 01:41:29 +00:00
|
|
|
Tweener.addTween(this.panelBox,
|
|
|
|
{ translation_y: 0,
|
|
|
|
time: STARTUP_ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._startupAnimationComplete,
|
|
|
|
onCompleteScope: this });
|
2012-12-24 14:20:39 +00:00
|
|
|
},
|
2012-08-22 07:22:30 +00:00
|
|
|
|
2012-12-24 14:20:39 +00:00
|
|
|
_startupAnimationSession: function() {
|
|
|
|
Tweener.addTween(this.uiGroup,
|
|
|
|
{ scale_x: 1,
|
|
|
|
scale_y: 1,
|
2013-02-21 19:38:50 +00:00
|
|
|
opacity: 255,
|
2011-07-25 18:25:51 +00:00
|
|
|
time: STARTUP_ANIMATION_TIME,
|
2011-09-02 20:36:02 +00:00
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._startupAnimationComplete,
|
2012-12-24 14:20:39 +00:00
|
|
|
onCompleteScope: this });
|
2011-07-25 18:25:51 +00:00
|
|
|
},
|
|
|
|
|
2011-09-02 20:36:02 +00:00
|
|
|
_startupAnimationComplete: function() {
|
2012-12-24 14:20:39 +00:00
|
|
|
// At this point, the UI group is covering everything, so
|
|
|
|
// we no longer need to clear the stage
|
|
|
|
global.stage.no_clear_hint = true;
|
|
|
|
|
2013-05-18 23:06:50 +00:00
|
|
|
this._coverPane.destroy();
|
|
|
|
this._coverPane = null;
|
2012-12-24 14:20:39 +00:00
|
|
|
|
2013-02-21 19:32:23 +00:00
|
|
|
this._systemBackground.actor.destroy();
|
|
|
|
this._systemBackground = null;
|
2013-02-20 01:31:56 +00:00
|
|
|
|
2013-02-20 01:20:44 +00:00
|
|
|
this._startingUp = false;
|
2012-12-24 14:20:39 +00:00
|
|
|
|
2013-02-20 00:56:56 +00:00
|
|
|
this.trayBox.show();
|
|
|
|
this.keyboardBox.show();
|
|
|
|
|
2013-03-20 00:16:54 +00:00
|
|
|
if (!Main.sessionMode.isGreeter) {
|
layout: Fix several issues with the background management code
If monitor-changed fires at startup, it will destroy all of the
backgrounds, but since this._isStartup is true, won't recreate any
of them. Additionally, since _bgManagers is indexed by monitor index,
if the primary index is not 0, it could become a sparse array (e.g.
[undefined, undefined, primaryBackground]), and our for loop will
crash trying to access properties of undefined.
Fix both of these issues by always creating background managers for
every monitor, hiding them on startup but only showing them after
the startup animation is complete.
One thing we need to watch out for is that while LayoutManager is
constructing, Main.uiGroup / Main.layoutManager will be undefined,
so addBackgroundMenu will fail. Fix this by passing down the uiGroup
to the background menu code.
https://bugzilla.gnome.org/show_bug.cgi?id=709313
2013-11-07 22:14:47 +00:00
|
|
|
this._showSecondaryBackgrounds();
|
2013-03-20 00:16:54 +00:00
|
|
|
global.window_group.remove_clip();
|
|
|
|
}
|
2012-12-24 14:20:39 +00:00
|
|
|
|
2013-02-20 04:21:35 +00:00
|
|
|
this._queueUpdateRegions();
|
2013-03-04 13:07:14 +00:00
|
|
|
|
|
|
|
this.emit('startup-complete');
|
2011-09-02 20:36:02 +00:00
|
|
|
},
|
|
|
|
|
2011-08-29 15:11:22 +00:00
|
|
|
showKeyboard: function () {
|
2011-09-01 15:37:54 +00:00
|
|
|
Tweener.addTween(this.keyboardBox,
|
|
|
|
{ anchor_y: this.keyboardBox.height,
|
2011-08-29 15:11:22 +00:00
|
|
|
time: KEYBOARD_ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._showKeyboardComplete,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2012-09-08 18:01:30 +00:00
|
|
|
this.emit('keyboard-visible-changed', true);
|
2011-08-29 15:11:22 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_showKeyboardComplete: function() {
|
|
|
|
// Poke Chrome to update the input shape; it doesn't notice
|
|
|
|
// anchor point changes
|
2013-01-28 04:34:33 +00:00
|
|
|
this._updateRegions();
|
2011-08-29 15:11:22 +00:00
|
|
|
|
2011-09-01 15:37:54 +00:00
|
|
|
this._keyboardHeightNotifyId = this.keyboardBox.connect('notify::height', Lang.bind(this, function () {
|
|
|
|
this.keyboardBox.anchor_y = this.keyboardBox.height;
|
2011-08-29 15:11:22 +00:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
hideKeyboard: function (immediate) {
|
2011-09-01 15:35:25 +00:00
|
|
|
if (this._keyboardHeightNotifyId) {
|
2011-09-01 15:37:54 +00:00
|
|
|
this.keyboardBox.disconnect(this._keyboardHeightNotifyId);
|
2011-09-01 15:35:25 +00:00
|
|
|
this._keyboardHeightNotifyId = 0;
|
|
|
|
}
|
2011-09-01 15:37:54 +00:00
|
|
|
Tweener.addTween(this.keyboardBox,
|
2011-08-29 15:11:22 +00:00
|
|
|
{ anchor_y: 0,
|
|
|
|
time: immediate ? 0 : KEYBOARD_ANIMATION_TIME,
|
2012-11-15 15:35:20 +00:00
|
|
|
transition: 'easeInQuad',
|
2011-09-01 15:35:25 +00:00
|
|
|
onComplete: this._hideKeyboardComplete,
|
2011-08-29 15:11:22 +00:00
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2012-10-14 16:57:45 +00:00
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
this.emit('keyboard-visible-changed', false);
|
2011-08-29 15:11:22 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_hideKeyboardComplete: function() {
|
2013-01-28 04:34:33 +00:00
|
|
|
this._updateRegions();
|
2011-08-29 15:11:22 +00:00
|
|
|
},
|
|
|
|
|
2013-02-15 09:56:34 +00:00
|
|
|
// setDummyCursorPosition:
|
|
|
|
//
|
|
|
|
// The cursor dummy is a standard widget commonly used for popup
|
|
|
|
// menus and box pointers to track, as the box pointer API only
|
|
|
|
// tracks actors. If you want to pop up a menu based on where the
|
|
|
|
// user clicked, or where the text cursor is, the cursor dummy
|
|
|
|
// is what you should use. Given that the menu should not track
|
|
|
|
// the actual mouse pointer as it moves, you need to call this
|
|
|
|
// function before you show the menu to ensure it is at the right
|
|
|
|
// position.
|
|
|
|
setDummyCursorPosition: function(x, y) {
|
|
|
|
this.dummyCursor.set_position(Math.round(x), Math.round(y));
|
|
|
|
},
|
|
|
|
|
2011-07-25 13:53:19 +00:00
|
|
|
// addChrome:
|
2011-09-01 14:20:52 +00:00
|
|
|
// @actor: an actor to add to the chrome
|
2011-07-25 13:53:19 +00:00
|
|
|
// @params: (optional) additional params
|
|
|
|
//
|
2013-05-19 00:59:46 +00:00
|
|
|
// Adds @actor to the chrome, and 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.
|
2011-07-25 13:53:19 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
//
|
2012-05-22 20:16:31 +00:00
|
|
|
// If %trackFullscreen in @params is %true, the actor's visibility
|
|
|
|
// will be bound to the presence of fullscreen windows on the same
|
|
|
|
// monitor (it will be hidden whenever a fullscreen window is visible,
|
|
|
|
// and shown otherwise)
|
2011-07-25 13:53:19 +00:00
|
|
|
addChrome: function(actor, params) {
|
2013-01-10 21:02:20 +00:00
|
|
|
this.uiGroup.add_actor(actor);
|
2013-06-26 08:41:04 +00:00
|
|
|
if (this.uiGroup.contains(global.top_window_group))
|
|
|
|
this.uiGroup.set_child_below_sibling(actor, global.top_window_group);
|
2013-01-28 04:34:33 +00:00
|
|
|
this._trackActor(actor, params);
|
2011-07-25 13:53:19 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
// 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(),
|
2012-05-22 20:16:31 +00:00
|
|
|
// though some possibilities don't make sense. By default, @actor has
|
|
|
|
// the same params as its chrome ancestor.
|
2011-07-25 13:53:19 +00:00
|
|
|
trackChrome: function(actor, params) {
|
2011-07-25 13:56:51 +00:00
|
|
|
let ancestor = actor.get_parent();
|
|
|
|
let index = this._findActor(ancestor);
|
|
|
|
while (ancestor && index == -1) {
|
|
|
|
ancestor = ancestor.get_parent();
|
|
|
|
index = this._findActor(ancestor);
|
|
|
|
}
|
|
|
|
if (!ancestor)
|
2011-09-01 14:20:52 +00:00
|
|
|
throw new Error('actor is not a descendent of a chrome actor');
|
2011-07-25 13:56:51 +00:00
|
|
|
|
|
|
|
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) {
|
2011-10-01 02:23:04 +00:00
|
|
|
if (!params.hasOwnProperty(prop))
|
2011-07-25 13:56:51 +00:00
|
|
|
params[prop] = ancestorData[prop];
|
|
|
|
}
|
|
|
|
|
|
|
|
this._trackActor(actor, params);
|
|
|
|
},
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
// untrackChrome:
|
|
|
|
// @actor: an actor previously tracked via trackChrome()
|
|
|
|
//
|
|
|
|
// Undoes the effect of trackChrome()
|
|
|
|
untrackChrome: function(actor) {
|
2011-07-25 13:56:51 +00:00
|
|
|
this._untrackActor(actor);
|
|
|
|
},
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
// removeChrome:
|
|
|
|
// @actor: a chrome actor
|
|
|
|
//
|
|
|
|
// Removes @actor from the chrome
|
|
|
|
removeChrome: function(actor) {
|
2013-01-10 21:02:20 +00:00
|
|
|
this.uiGroup.remove_actor(actor);
|
2011-07-25 13:56:51 +00:00
|
|
|
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));
|
2014-02-08 20:44:58 +00:00
|
|
|
actorData.destroyId = actor.connect('destroy',
|
|
|
|
Lang.bind(this, this._untrackActor));
|
2011-07-25 13:56:51 +00:00
|
|
|
// 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);
|
2014-02-08 20:44:58 +00:00
|
|
|
actor.disconnect(actorData.destroyId);
|
2011-07-25 13:56:51 +00:00
|
|
|
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
},
|
|
|
|
|
|
|
|
_updateVisibility: function() {
|
2013-03-13 18:38:59 +00:00
|
|
|
let windowsVisible = Main.sessionMode.hasWindows && !this._inOverview;
|
|
|
|
|
|
|
|
global.window_group.visible = windowsVisible;
|
|
|
|
global.top_window_group.visible = windowsVisible;
|
|
|
|
|
2011-07-25 13:56:51 +00:00
|
|
|
for (let i = 0; i < this._trackedActors.length; i++) {
|
2012-05-22 21:24:32 +00:00
|
|
|
let actorData = this._trackedActors[i], visible;
|
2012-05-22 20:16:31 +00:00
|
|
|
if (!actorData.trackFullscreen)
|
|
|
|
continue;
|
2011-09-01 14:20:52 +00:00
|
|
|
|
2013-03-13 18:38:59 +00:00
|
|
|
if (!windowsVisible)
|
2011-09-01 14:20:52 +00:00
|
|
|
visible = true;
|
2012-05-22 20:16:31 +00:00
|
|
|
else if (this.findMonitorForActor(actorData.actor).inFullscreen)
|
2011-09-01 14:20:52 +00:00
|
|
|
visible = false;
|
2011-07-25 13:56:51 +00:00
|
|
|
else
|
2011-09-01 14:20:52 +00:00
|
|
|
visible = true;
|
2012-05-22 20:16:31 +00:00
|
|
|
actorData.actor.visible = visible;
|
2011-07-25 13:56:51 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-01-28 05:09:12 +00:00
|
|
|
getWorkAreaForMonitor: function(monitorIndex) {
|
|
|
|
// Assume that all workspaces will have the same
|
|
|
|
// struts and pick the first one.
|
|
|
|
let ws = global.screen.get_workspace_by_index(0);
|
|
|
|
return ws.get_work_area_for_monitor(monitorIndex);
|
|
|
|
},
|
|
|
|
|
2011-07-25 13:56:51 +00:00
|
|
|
// 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
|
2012-10-14 16:57:45 +00:00
|
|
|
findIndexForActor: function(actor) {
|
2011-07-25 13:56:51 +00:00
|
|
|
let [x, y] = actor.get_transformed_position();
|
|
|
|
let [w, h] = actor.get_transformed_size();
|
2013-01-28 04:41:30 +00:00
|
|
|
let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h });
|
|
|
|
return global.screen.get_monitor_index_for_rect(rect);
|
2012-10-14 16:57:45 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
findMonitorForActor: function(actor) {
|
2013-01-28 04:34:33 +00:00
|
|
|
return this.monitors[this.findIndexForActor(actor)];
|
2011-07-25 13:56:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_queueUpdateRegions: function() {
|
2013-02-20 02:06:33 +00:00
|
|
|
if (Main.sessionMode.isGreeter)
|
|
|
|
return;
|
|
|
|
|
2013-02-20 04:21:35 +00:00
|
|
|
if (this._startingUp)
|
|
|
|
return;
|
|
|
|
|
2013-02-20 04:17:16 +00:00
|
|
|
if (!this._updateRegionIdle)
|
2013-01-28 04:34:33 +00:00
|
|
|
this._updateRegionIdle = Mainloop.idle_add(Lang.bind(this, this._updateRegions),
|
2011-07-25 13:56:51 +00:00
|
|
|
Meta.PRIORITY_BEFORE_REDRAW);
|
|
|
|
},
|
|
|
|
|
2013-01-14 17:43:24 +00:00
|
|
|
_getWindowActorsForWorkspace: function(workspace) {
|
|
|
|
return global.get_window_actors().filter(function (actor) {
|
|
|
|
let win = actor.meta_window;
|
|
|
|
return win.located_on_workspace(workspace);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2011-07-25 13:56:51 +00:00
|
|
|
_updateFullscreen: function() {
|
2013-03-14 18:36:13 +00:00
|
|
|
this._updateVisibility();
|
|
|
|
this._queueUpdateRegions();
|
2011-07-25 13:56:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_windowsRestacked: function() {
|
|
|
|
let changed = false;
|
2012-06-06 22:20:23 +00:00
|
|
|
|
2013-03-14 18:36:13 +00:00
|
|
|
if (this._isPopupWindowVisible != global.top_window_group.get_children().some(isPopupMetaWindow))
|
2013-02-12 21:00:41 +00:00
|
|
|
changed = true;
|
|
|
|
|
2011-07-25 13:56:51 +00:00
|
|
|
if (changed) {
|
|
|
|
this._updateVisibility();
|
|
|
|
this._queueUpdateRegions();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-01-28 04:34:33 +00:00
|
|
|
_updateRegions: function() {
|
2011-07-25 13:56:51 +00:00
|
|
|
let rects = [], struts = [], i;
|
|
|
|
|
2011-08-29 15:11:22 +00:00
|
|
|
if (this._updateRegionIdle) {
|
|
|
|
Mainloop.source_remove(this._updateRegionIdle);
|
|
|
|
delete this._updateRegionIdle;
|
|
|
|
}
|
2011-07-25 13:56:51 +00:00
|
|
|
|
2013-02-12 21:00:41 +00:00
|
|
|
let isPopupMenuVisible = global.top_window_group.get_children().some(isPopupMetaWindow);
|
|
|
|
let wantsInputRegion = !isPopupMenuVisible;
|
|
|
|
|
2011-07-25 13:56:51 +00:00
|
|
|
for (i = 0; i < this._trackedActors.length; i++) {
|
|
|
|
let actorData = this._trackedActors[i];
|
2013-05-19 00:59:46 +00:00
|
|
|
if (!wantsInputRegion && !actorData.affectsStruts)
|
2011-07-25 13:56:51 +00:00
|
|
|
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);
|
|
|
|
|
2013-05-19 00:59:46 +00:00
|
|
|
if (wantsInputRegion && actorData.actor.get_paint_visibility())
|
2013-05-18 23:23:46 +00:00
|
|
|
rects.push(new Meta.Rectangle({ x: x, y: y, width: w, height: h }));
|
2011-07-25 13:56:51 +00:00
|
|
|
|
2013-02-12 20:57:08 +00:00
|
|
|
if (actorData.affectsStruts) {
|
|
|
|
// 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 >= primary.x + primary.width)
|
|
|
|
side = Meta.Side.RIGHT;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
} else if (x1 <= 0)
|
2011-07-25 13:56:51 +00:00
|
|
|
side = Meta.Side.LEFT;
|
2013-02-12 20:57:08 +00:00
|
|
|
else if (y1 <= 0)
|
|
|
|
side = Meta.Side.TOP;
|
|
|
|
else if (x2 >= global.screen_width)
|
2011-07-25 13:56:51 +00:00
|
|
|
side = Meta.Side.RIGHT;
|
2013-02-12 20:57:08 +00:00
|
|
|
else if (y2 >= global.screen_height)
|
|
|
|
side = Meta.Side.BOTTOM;
|
2011-07-25 13:56:51 +00:00
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
2013-02-12 20:57:08 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2011-07-25 13:56:51 +00:00
|
|
|
|
2013-02-12 20:57:08 +00:00
|
|
|
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);
|
|
|
|
}
|
2011-07-25 13:56:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
global.set_stage_input_region(rects);
|
2013-02-12 21:00:41 +00:00
|
|
|
this._isPopupWindowVisible = isPopupMenuVisible;
|
2011-07-25 13:56:51 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-11-29 00:45:39 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2011-07-25 13:56:51 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2013-01-28 04:34:33 +00:00
|
|
|
Signals.addSignalMethods(LayoutManager.prototype);
|
2013-01-28 04:32:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
// HotCorner:
|
|
|
|
//
|
|
|
|
// This class manages a "hot corner" that can toggle switching to
|
|
|
|
// overview.
|
|
|
|
const HotCorner = new Lang.Class({
|
|
|
|
Name: 'HotCorner',
|
|
|
|
|
2013-03-10 02:12:37 +00:00
|
|
|
_init : function(layoutManager, monitor, x, y) {
|
2013-01-28 04:32:40 +00:00
|
|
|
// 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;
|
|
|
|
|
2013-03-10 02:12:37 +00:00
|
|
|
this._monitor = monitor;
|
|
|
|
|
2013-03-01 20:07:11 +00:00
|
|
|
this._x = x;
|
|
|
|
this._y = y;
|
|
|
|
|
2013-03-05 06:49:35 +00:00
|
|
|
this._setupFallbackCornerIfNeeded(layoutManager);
|
2013-01-28 04:32:40 +00:00
|
|
|
|
2013-03-01 21:19:07 +00:00
|
|
|
this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
|
|
|
|
HOT_CORNER_PRESSURE_TIMEOUT,
|
|
|
|
Shell.KeyBindingMode.NORMAL |
|
|
|
|
Shell.KeyBindingMode.OVERVIEW);
|
|
|
|
this._pressureBarrier.connect('trigger', Lang.bind(this, this._toggleOverview));
|
2013-01-28 04:32:40 +00:00
|
|
|
|
|
|
|
// Cache the three ripples instead of dynamically creating and destroying them.
|
|
|
|
this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
|
|
|
this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
|
|
|
this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
|
|
|
|
|
2013-01-10 21:02:20 +00:00
|
|
|
layoutManager.uiGroup.add_actor(this._ripple1);
|
|
|
|
layoutManager.uiGroup.add_actor(this._ripple2);
|
|
|
|
layoutManager.uiGroup.add_actor(this._ripple3);
|
2013-01-28 04:32:40 +00:00
|
|
|
},
|
|
|
|
|
2013-03-01 20:07:11 +00:00
|
|
|
setBarrierSize: function(size) {
|
|
|
|
if (this._verticalBarrier) {
|
2013-03-01 21:19:07 +00:00
|
|
|
this._pressureBarrier.removeBarrier(this._verticalBarrier);
|
2013-03-01 20:07:11 +00:00
|
|
|
this._verticalBarrier.destroy();
|
|
|
|
this._verticalBarrier = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._horizontalBarrier) {
|
2013-03-01 21:19:07 +00:00
|
|
|
this._pressureBarrier.removeBarrier(this._horizontalBarrier);
|
2013-03-01 20:07:11 +00:00
|
|
|
this._horizontalBarrier.destroy();
|
|
|
|
this._horizontalBarrier = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (size > 0) {
|
2013-04-20 15:48:28 +00:00
|
|
|
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
|
|
|
|
this._verticalBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
|
|
|
|
directions: Meta.BarrierDirection.NEGATIVE_X });
|
|
|
|
this._horizontalBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: this._x - size, x2: this._x, y1: this._y, y2: this._y,
|
|
|
|
directions: Meta.BarrierDirection.POSITIVE_Y });
|
|
|
|
} else {
|
|
|
|
this._verticalBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
|
|
|
|
directions: Meta.BarrierDirection.POSITIVE_X });
|
|
|
|
this._horizontalBarrier = new Meta.Barrier({ display: global.display,
|
|
|
|
x1: this._x, x2: this._x + size, y1: this._y, y2: this._y,
|
|
|
|
directions: Meta.BarrierDirection.POSITIVE_Y });
|
|
|
|
}
|
2013-03-01 21:19:07 +00:00
|
|
|
|
|
|
|
this._pressureBarrier.addBarrier(this._verticalBarrier);
|
|
|
|
this._pressureBarrier.addBarrier(this._horizontalBarrier);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-03-05 06:49:35 +00:00
|
|
|
_setupFallbackCornerIfNeeded: function(layoutManager) {
|
2013-03-01 21:19:07 +00:00
|
|
|
if (!global.display.supports_extended_barriers()) {
|
|
|
|
this.actor = new Clutter.Actor({ name: 'hot-corner-environs',
|
2013-03-05 06:49:35 +00:00
|
|
|
x: this._x, y: this._y,
|
2013-03-01 21:19:07 +00:00
|
|
|
width: 3,
|
|
|
|
height: 3,
|
|
|
|
reactive: true });
|
|
|
|
|
2013-05-09 01:53:20 +00:00
|
|
|
this._corner = new Clutter.Actor({ name: 'hot-corner',
|
|
|
|
width: 1,
|
|
|
|
height: 1,
|
|
|
|
opacity: 0,
|
|
|
|
reactive: true });
|
2013-03-01 21:19:07 +00:00
|
|
|
this._corner._delegate = this;
|
|
|
|
|
|
|
|
this.actor.add_child(this._corner);
|
|
|
|
layoutManager.addChrome(this.actor);
|
|
|
|
|
|
|
|
if (Clutter.get_default_text_direction() == Clutter.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.actor.connect('leave-event',
|
|
|
|
Lang.bind(this, this._onEnvironsLeft));
|
|
|
|
|
|
|
|
this._corner.connect('enter-event',
|
|
|
|
Lang.bind(this, this._onCornerEntered));
|
|
|
|
this._corner.connect('leave-event',
|
|
|
|
Lang.bind(this, this._onCornerLeft));
|
2013-03-01 20:07:11 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-01-28 04:32:40 +00:00
|
|
|
destroy: function() {
|
2013-03-01 20:07:11 +00:00
|
|
|
this.setBarrierSize(0);
|
2013-03-01 21:19:07 +00:00
|
|
|
this._pressureBarrier.destroy();
|
|
|
|
this._pressureBarrier = null;
|
|
|
|
|
|
|
|
if (this.actor)
|
|
|
|
this.actor.destroy();
|
2013-01-28 04:32:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_animRipple : function(ripple, delay, time, startScale, startOpacity, finalScale) {
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
ripple._opacity = startOpacity;
|
|
|
|
|
|
|
|
if (ripple.get_text_direction() == Clutter.TextDirection.RTL)
|
|
|
|
ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
|
|
|
|
|
|
|
|
ripple.visible = true;
|
|
|
|
ripple.opacity = 255 * Math.sqrt(startOpacity);
|
|
|
|
ripple.scale_x = ripple.scale_y = startScale;
|
|
|
|
|
2013-03-03 19:08:30 +00:00
|
|
|
ripple.x = this._x;
|
|
|
|
ripple.y = this._y;
|
2013-01-28 04:32:40 +00:00
|
|
|
|
|
|
|
Tweener.addTween(ripple, { _opacity: 0,
|
|
|
|
scale_x: finalScale,
|
|
|
|
scale_y: finalScale,
|
|
|
|
delay: delay,
|
|
|
|
time: time,
|
|
|
|
transition: 'linear',
|
|
|
|
onUpdate: function() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
|
|
|
|
onComplete: function() { ripple.visible = false; } });
|
|
|
|
},
|
|
|
|
|
2013-03-01 21:19:07 +00:00
|
|
|
_rippleAnimation: function() {
|
2013-01-28 04:32:40 +00:00
|
|
|
// 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
|
|
|
|
this._animRipple(this._ripple1, 0.0, 0.83, 0.25, 1.0, 1.5);
|
|
|
|
this._animRipple(this._ripple2, 0.05, 1.0, 0.0, 0.7, 1.25);
|
|
|
|
this._animRipple(this._ripple3, 0.35, 1.0, 0.0, 0.3, 1);
|
|
|
|
},
|
|
|
|
|
2013-03-01 21:19:07 +00:00
|
|
|
_toggleOverview: function() {
|
2013-03-10 02:12:37 +00:00
|
|
|
if (this._monitor.inFullscreen)
|
|
|
|
return;
|
|
|
|
|
2013-03-04 22:02:02 +00:00
|
|
|
if (Main.overview.shouldToggleByCornerOrButton()) {
|
2013-03-01 21:19:07 +00:00
|
|
|
this._rippleAnimation();
|
2013-03-04 22:02:02 +00:00
|
|
|
Main.overview.toggle();
|
2013-01-28 04:32:40 +00:00
|
|
|
}
|
2013-03-01 21:19:07 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
handleDragOver: function(source, actor, x, y, time) {
|
|
|
|
if (source != Main.xdndHandler)
|
|
|
|
return DND.DragMotionResult.CONTINUE;
|
2013-01-28 04:32:40 +00:00
|
|
|
|
2013-03-23 09:51:43 +00:00
|
|
|
this._toggleOverview();
|
|
|
|
|
2013-01-28 04:32:40 +00:00
|
|
|
return DND.DragMotionResult.CONTINUE;
|
|
|
|
},
|
|
|
|
|
|
|
|
_onCornerEntered : function() {
|
|
|
|
if (!this._entered) {
|
|
|
|
this._entered = true;
|
2013-03-01 21:19:07 +00:00
|
|
|
this._toggleOverview();
|
2013-01-28 04:32:40 +00:00
|
|
|
}
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2013-01-28 04:32:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onCornerLeft : function(actor, event) {
|
|
|
|
if (event.get_related() != this.actor)
|
|
|
|
this._entered = false;
|
|
|
|
// Consume event, otherwise this will confuse onEnvironsLeft
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_STOP;
|
2013-01-28 04:32:40 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onEnvironsLeft : function(actor, event) {
|
|
|
|
if (event.get_related() != this._corner)
|
|
|
|
this._entered = false;
|
2013-11-29 18:17:34 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2013-01-28 04:32:40 +00:00
|
|
|
}
|
|
|
|
});
|
2012-07-30 19:25:07 +00:00
|
|
|
|
|
|
|
const PressureBarrier = new Lang.Class({
|
|
|
|
Name: 'PressureBarrier',
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
_init: function(threshold, timeout, keybindingMode) {
|
2012-07-30 19:25:07 +00:00
|
|
|
this._threshold = threshold;
|
|
|
|
this._timeout = timeout;
|
2013-03-01 20:45:56 +00:00
|
|
|
this._keybindingMode = keybindingMode;
|
2013-03-01 19:57:38 +00:00
|
|
|
this._barriers = [];
|
2013-03-01 20:33:39 +00:00
|
|
|
this._eventFilter = null;
|
2012-07-30 19:25:07 +00:00
|
|
|
|
2013-03-01 20:29:42 +00:00
|
|
|
this._isTriggered = false;
|
2012-07-30 19:25:07 +00:00
|
|
|
this._reset();
|
2013-03-01 19:57:38 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
addBarrier: function(barrier) {
|
|
|
|
barrier._pressureHitId = barrier.connect('hit', Lang.bind(this, this._onBarrierHit));
|
|
|
|
barrier._pressureLeftId = barrier.connect('left', Lang.bind(this, this._onBarrierLeft));
|
2012-07-30 19:25:07 +00:00
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
this._barriers.push(barrier);
|
|
|
|
},
|
|
|
|
|
|
|
|
_disconnectBarrier: function(barrier) {
|
|
|
|
barrier.disconnect(barrier._pressureHitId);
|
|
|
|
barrier.disconnect(barrier._pressureLeftId);
|
|
|
|
},
|
|
|
|
|
|
|
|
removeBarrier: function(barrier) {
|
|
|
|
this._disconnectBarrier(barrier);
|
|
|
|
this._barriers.splice(this._barriers.indexOf(barrier), 1);
|
2012-07-30 19:25:07 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
2013-03-01 19:57:38 +00:00
|
|
|
this._barriers.forEach(Lang.bind(this, this._disconnectBarrier));
|
|
|
|
this._barriers = [];
|
2012-07-30 19:25:07 +00:00
|
|
|
},
|
|
|
|
|
2013-03-01 20:33:39 +00:00
|
|
|
setEventFilter: function(filter) {
|
|
|
|
this._eventFilter = filter;
|
|
|
|
},
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
_reset: function() {
|
|
|
|
this._barrierEvents = [];
|
|
|
|
this._currentPressure = 0;
|
|
|
|
this._lastTime = 0;
|
|
|
|
},
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
_isHorizontal: function(barrier) {
|
|
|
|
return barrier.y1 == barrier.y2;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getDistanceAcrossBarrier: function(barrier, event) {
|
|
|
|
if (this._isHorizontal(barrier))
|
2012-07-30 19:25:07 +00:00
|
|
|
return Math.abs(event.dy);
|
|
|
|
else
|
|
|
|
return Math.abs(event.dx);
|
|
|
|
},
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
_getDistanceAlongBarrier: function(barrier, event) {
|
|
|
|
if (this._isHorizontal(barrier))
|
2012-07-30 19:25:07 +00:00
|
|
|
return Math.abs(event.dx);
|
|
|
|
else
|
|
|
|
return Math.abs(event.dy);
|
|
|
|
},
|
|
|
|
|
|
|
|
_trimBarrierEvents: function() {
|
|
|
|
// Events are guaranteed to be sorted in time order from
|
|
|
|
// oldest to newest, so just look for the first old event,
|
|
|
|
// and then chop events after that off.
|
|
|
|
let i = 0;
|
2013-03-01 20:21:25 +00:00
|
|
|
let threshold = this._lastTime - this._timeout;
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
while (i < this._barrierEvents.length) {
|
2013-03-01 20:21:25 +00:00
|
|
|
let [time, distance] = this._barrierEvents[i];
|
|
|
|
if (time >= threshold)
|
2012-07-30 19:25:07 +00:00
|
|
|
break;
|
2013-02-15 02:21:20 +00:00
|
|
|
i++;
|
2012-07-30 19:25:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let firstNewEvent = i;
|
|
|
|
|
|
|
|
for (i = 0; i < firstNewEvent; i++) {
|
2013-03-01 20:21:25 +00:00
|
|
|
let [time, distance] = this._barrierEvents[i];
|
|
|
|
this._currentPressure -= distance;
|
2012-07-30 19:25:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this._barrierEvents = this._barrierEvents.slice(firstNewEvent);
|
|
|
|
},
|
|
|
|
|
|
|
|
_onBarrierLeft: function(barrier, event) {
|
|
|
|
this._reset();
|
2013-03-01 20:29:42 +00:00
|
|
|
this._isTriggered = false;
|
2012-07-30 19:25:07 +00:00
|
|
|
},
|
|
|
|
|
2013-02-21 22:21:25 +00:00
|
|
|
_trigger: function() {
|
2013-03-01 20:29:42 +00:00
|
|
|
this._isTriggered = true;
|
2013-02-21 22:21:25 +00:00
|
|
|
this.emit('trigger');
|
|
|
|
this._reset();
|
|
|
|
},
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
_onBarrierHit: function(barrier, event) {
|
2013-03-01 20:29:42 +00:00
|
|
|
// If we've triggered the barrier, wait until the pointer has the
|
|
|
|
// left the barrier hitbox until we trigger it again.
|
|
|
|
if (this._isTriggered)
|
|
|
|
return;
|
|
|
|
|
2013-03-01 20:33:39 +00:00
|
|
|
if (this._eventFilter && this._eventFilter(event))
|
2013-02-16 21:17:11 +00:00
|
|
|
return;
|
|
|
|
|
2013-03-01 20:45:56 +00:00
|
|
|
// Throw out all events not in the proper keybinding mode
|
|
|
|
if (!(this._keybindingMode & Main.keybindingMode))
|
2012-07-30 19:25:07 +00:00
|
|
|
return;
|
|
|
|
|
2013-03-01 19:57:38 +00:00
|
|
|
let slide = this._getDistanceAlongBarrier(barrier, event);
|
|
|
|
let distance = this._getDistanceAcrossBarrier(barrier, event);
|
2012-07-30 19:25:07 +00:00
|
|
|
|
2013-02-21 22:21:25 +00:00
|
|
|
if (distance >= this._threshold) {
|
|
|
|
this._trigger();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
// Throw out events where the cursor is move more
|
|
|
|
// along the axis of the barrier than moving with
|
|
|
|
// the barrier.
|
|
|
|
if (slide > distance)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._lastTime = event.time;
|
|
|
|
|
|
|
|
this._trimBarrierEvents();
|
2013-03-01 20:21:25 +00:00
|
|
|
distance = Math.min(15, distance);
|
|
|
|
|
|
|
|
this._barrierEvents.push([event.time, distance]);
|
|
|
|
this._currentPressure += distance;
|
2012-07-30 19:25:07 +00:00
|
|
|
|
2013-02-21 22:21:25 +00:00
|
|
|
if (this._currentPressure >= this._threshold)
|
|
|
|
this._trigger();
|
2012-07-30 19:25:07 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(PressureBarrier.prototype);
|