mirror of
https://github.com/gosticks/PaperWM.git
synced 2026-02-07 09:22:47 +00:00
801 lines
26 KiB
JavaScript
801 lines
26 KiB
JavaScript
/**
|
|
Some of Gnome Shell's default behavior is really sub-optimal when using
|
|
paperWM. Other features are simply not possible to implement without monkey
|
|
patching. This is a collection of monkey patches and preferences which works
|
|
around these problems and facilitates new features.
|
|
*/
|
|
|
|
var Extension;
|
|
if (imports.misc.extensionUtils.extensions) {
|
|
Extension = imports.misc.extensionUtils.extensions["paperwm@hedning:matrix.org"];
|
|
} else {
|
|
Extension = imports.ui.main.extensionManager.lookup("paperwm@hedning:matrix.org");
|
|
}
|
|
|
|
|
|
var Meta = imports.gi.Meta;
|
|
var Gio = imports.gi.Gio;
|
|
var Main = imports.ui.main;
|
|
var Mainloop = imports.mainloop;
|
|
var Workspace = imports.ui.workspace;
|
|
var WindowManager = imports.ui.windowManager;
|
|
var Shell = imports.gi.Shell;
|
|
var utils = Extension.imports.utils;
|
|
|
|
var Convenience = Extension.imports.convenience;
|
|
var Scratch = Extension.imports.scratch;
|
|
var Tiling = Extension.imports.tiling;
|
|
var settings = Convenience.getSettings();
|
|
var Clutter = imports.gi.Clutter;
|
|
let St = imports.gi.St;
|
|
|
|
function overrideHotCorners() {
|
|
for (let corner of Main.layoutManager.hotCorners) {
|
|
if (!corner)
|
|
continue;
|
|
|
|
corner._toggleOverview = function() {};
|
|
|
|
corner._pressureBarrier._trigger = function() {};
|
|
}
|
|
}
|
|
|
|
if (!global.display.get_monitor_scale) {
|
|
// `get_monitor_scale` first appeared in 3.31.92. Polyfill a fallback for 3.28
|
|
global.display.constructor.prototype.get_monitor_scale = () => 1.0;
|
|
}
|
|
|
|
if (!global.display.get_monitor_neighbor_index) {
|
|
// `get_monitor_neighbor_index` polyfill a fallback for 3.28
|
|
global.display.constructor.prototype.get_monitor_neighbor_index = function(...args) {
|
|
return global.screen.get_monitor_neighbor_index(...args);
|
|
}
|
|
}
|
|
|
|
// polyfill for 3.28
|
|
if (!Meta.DisplayDirection && Meta.ScreenDirection) {
|
|
Meta.DisplayDirection = Meta.ScreenDirection;
|
|
}
|
|
|
|
if (!St.Settings) {
|
|
// `St.Settings` doesn't exist in 3.28 - polyfill:
|
|
let Gtk = imports.gi.Gtk;
|
|
let gtkSettings = Gtk.Settings.get_default();
|
|
let polyfillSettings = new (class PolyfillStSettings {
|
|
get enable_animations() {
|
|
return gtkSettings.gtk_enable_animations;
|
|
}
|
|
set enable_animations(value) {
|
|
gtkSettings.gtk_enable_animations = value;
|
|
}
|
|
})();
|
|
|
|
St.Settings = {
|
|
get: function() { return polyfillSettings; } // ASSUMTION: no need to call get_default each time
|
|
};
|
|
}
|
|
|
|
if (!Clutter.Actor.prototype.set) {
|
|
// `set` doesn't exist in 3.28 - polyfill:
|
|
Clutter.Actor.prototype.set = function(params) {
|
|
Object.assign(this, params);
|
|
}
|
|
}
|
|
|
|
// Polyfill gnome-3.34 transition API, taken from gnome-shell/js/ui/environment.js
|
|
const version = imports.misc.config.PACKAGE_VERSION.split('.');
|
|
if (version[0] >= 3 && version[1] < 34) {
|
|
function _makeEaseCallback(params, cleanup) {
|
|
let onComplete = params.onComplete;
|
|
delete params.onComplete;
|
|
|
|
let onStopped = params.onStopped;
|
|
delete params.onStopped;
|
|
|
|
return isFinished => {
|
|
cleanup();
|
|
|
|
if (onStopped)
|
|
onStopped(isFinished);
|
|
if (onComplete && isFinished)
|
|
onComplete();
|
|
};
|
|
}
|
|
|
|
let enable_unredirect = () => Meta.enable_unredirect_for_display(global.display);
|
|
let disable_unredirect = () => Meta.disable_unredirect_for_display(global.display);;
|
|
// This is different in 3.28
|
|
if (version[0] >= 3 && version[1] < 30) {
|
|
enable_unredirect = () => Meta.enable_unredirect_for_screen(global.screen);
|
|
disable_unredirect = () => Meta.disable_unredirect_for_screen(global.screen);;
|
|
}
|
|
|
|
function _easeActor(actor, params) {
|
|
actor.save_easing_state();
|
|
|
|
if (params.duration != undefined)
|
|
actor.set_easing_duration(params.duration);
|
|
delete params.duration;
|
|
|
|
if (params.delay != undefined)
|
|
actor.set_easing_delay(params.delay);
|
|
delete params.delay;
|
|
|
|
if (params.mode != undefined)
|
|
actor.set_easing_mode(params.mode);
|
|
delete params.mode;
|
|
|
|
disable_unredirect();
|
|
|
|
let callback = _makeEaseCallback(params, enable_unredirect);
|
|
|
|
// cancel overwritten transitions
|
|
let animatedProps = Object.keys(params).map(p => p.replace('_', '-', 'g'));
|
|
animatedProps.forEach(p => actor.remove_transition(p));
|
|
|
|
actor.set(params);
|
|
actor.restore_easing_state();
|
|
|
|
let transition = animatedProps.map(p => actor.get_transition(p))
|
|
.find(t => t !== null);
|
|
|
|
if (transition)
|
|
transition.connect('stopped', (t, finished) => callback(finished));
|
|
else
|
|
callback(true);
|
|
}
|
|
|
|
// adjustAnimationTime:
|
|
// @msecs: time in milliseconds
|
|
//
|
|
// Adjust @msecs to account for St's enable-animations
|
|
// and slow-down-factor settings
|
|
function adjustAnimationTime(msecs) {
|
|
let settings = St.Settings.get();
|
|
|
|
if (!settings.enable_animations)
|
|
return 1;
|
|
// settings.slow_down_factor is new in 3.34
|
|
return St.get_slow_down_factor() * msecs;
|
|
}
|
|
|
|
let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration;
|
|
Clutter.Actor.prototype.set_easing_duration = function(msecs) {
|
|
origSetEasingDuration.call(this, adjustAnimationTime(msecs));
|
|
};
|
|
let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay;
|
|
Clutter.Actor.prototype.set_easing_delay = function(msecs) {
|
|
origSetEasingDelay.call(this, adjustAnimationTime(msecs));
|
|
};
|
|
|
|
Clutter.Actor.prototype.ease = function(props, easingParams) {
|
|
_easeActor(this, props, easingParams);
|
|
};
|
|
}
|
|
|
|
// Polyfill
|
|
if (!Clutter.Actor.prototype.raise) {
|
|
Clutter.Actor.prototype.raise = function raise(above) {
|
|
const parent = this.get_parent();
|
|
if (!parent)
|
|
return;
|
|
parent.set_child_above_sibling(this, above);
|
|
}
|
|
}
|
|
|
|
if (!Clutter.Actor.prototype.raise_top) {
|
|
Clutter.Actor.prototype.raise_top = function raise_top() {
|
|
this.raise(null);
|
|
}
|
|
}
|
|
|
|
if (!Clutter.Actor.prototype.reparent) {
|
|
Clutter.Actor.prototype.reparent = function reparent(newParent) {
|
|
const parent = this.get_parent();
|
|
if (parent) {
|
|
parent.remove_child(this);
|
|
}
|
|
newParent.add_child(this);
|
|
}
|
|
}
|
|
|
|
if (! Clutter.Vertex) {
|
|
const {Graphene} = imports.gi;
|
|
Clutter.Vertex = Graphene.Point3D;
|
|
}
|
|
|
|
// Workspace.Workspace._realRecalculateWindowPositions
|
|
// Sort tiled windows in the correct order
|
|
function _realRecalculateWindowPositions(flags) {
|
|
if (this._repositionWindowsId > 0) {
|
|
Mainloop.source_remove(this._repositionWindowsId);
|
|
this._repositionWindowsId = 0;
|
|
}
|
|
|
|
let clones = this._windows.slice();
|
|
if (clones.length == 0)
|
|
return;
|
|
|
|
let space = Tiling.spaces.spaceOf(this.metaWorkspace);
|
|
if (space) {
|
|
clones.sort((a, b) => {
|
|
let aw = a.metaWindow;
|
|
let bw = b.metaWindow;
|
|
let ia = space.indexOf(aw);
|
|
let ib = space.indexOf(bw);
|
|
if (ia === -1 && ib === -1) {
|
|
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
|
|
}
|
|
if (ia === -1) {
|
|
return -1;
|
|
}
|
|
if (ib === -1) {
|
|
return 1;
|
|
}
|
|
return ia - ib;
|
|
});
|
|
} else {
|
|
clones.sort((a, b) => {
|
|
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
|
|
});
|
|
}
|
|
|
|
if (this._reservedSlot)
|
|
clones.push(this._reservedSlot);
|
|
|
|
this._currentLayout = this._computeLayout(clones);
|
|
this._updateWindowPositions(flags);
|
|
}
|
|
|
|
// Workspace.WindowClone.getOriginalPosition
|
|
// Get the correct positions of tiled windows when animating to/from the overview
|
|
function getOriginalPosition() {
|
|
let c = this.metaWindow.clone;
|
|
let space = Tiling.spaces.spaceOfWindow(this.metaWindow);
|
|
if (!space || space.indexOf(this.metaWindow) === -1) {
|
|
return [this._boundingBox.x, this._boundingBox.y];
|
|
}
|
|
let [x, y] = [ space.monitor.x + space.targetX + c.targetX, space.monitor.y + c.y];
|
|
return [x, y];
|
|
}
|
|
|
|
function disableHotcorners() {
|
|
let override = settings.get_boolean("override-hot-corner");
|
|
if (override) {
|
|
overrideHotCorners();
|
|
signals.connect(Main.layoutManager,
|
|
'hot-corners-changed',
|
|
overrideHotCorners);
|
|
} else {
|
|
signals.disconnect(Main.layoutManager);
|
|
Main.layoutManager._updateHotCorners();
|
|
}
|
|
}
|
|
|
|
var savedProps;
|
|
savedProps = savedProps || new Map();
|
|
|
|
function registerOverrideProp(obj, name, override) {
|
|
if (!obj)
|
|
return
|
|
|
|
let saved = getSavedProp(obj, name) || obj[name];
|
|
let props = savedProps.get(obj);
|
|
if (!props) {
|
|
props = {};
|
|
savedProps.set(obj, props);
|
|
}
|
|
props[name] = {
|
|
saved,
|
|
override
|
|
};
|
|
}
|
|
|
|
function registerOverridePrototype(obj, name, override) {
|
|
if (!obj)
|
|
return
|
|
|
|
registerOverrideProp(obj.prototype, name, override);
|
|
}
|
|
|
|
function makeFallback(obj, method, ...args) {
|
|
let fallback = getSavedPrototype(obj, method);
|
|
return fallback.bind(...args);
|
|
}
|
|
|
|
function overrideWithFallback(obj, method, body) {
|
|
registerOverridePrototype(
|
|
obj, method, function(...args) {
|
|
let fallback = makeFallback(obj, method, this, ...args);
|
|
body(fallback, this, ...args);
|
|
}
|
|
);
|
|
}
|
|
|
|
function getSavedProp(obj, name) {
|
|
let props = savedProps.get(obj);
|
|
if (!props)
|
|
return undefined;
|
|
let prop = props[name];
|
|
if (!prop)
|
|
return undefined;
|
|
return prop.saved;
|
|
}
|
|
|
|
function getSavedPrototype(obj, name) {
|
|
return getSavedProp(obj.prototype, name);
|
|
}
|
|
|
|
|
|
function disableOverride(obj, name) {
|
|
obj[name] = getSavedProp(obj, name);
|
|
}
|
|
|
|
function enableOverride(obj, name) {
|
|
let props = savedProps.get(obj);
|
|
let override = props[name].override;
|
|
if (override !== undefined) {
|
|
obj[name] = override;
|
|
}
|
|
}
|
|
|
|
function enableOverrides() {
|
|
for (let [obj, props] of savedProps) {
|
|
for (let name in props) {
|
|
enableOverride(obj, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
function disableOverrides() {
|
|
for (let [obj, props] of savedProps) {
|
|
for (let name in props) {
|
|
obj[name] = props[name].saved;
|
|
}
|
|
}
|
|
}
|
|
|
|
function restoreMethod(obj, name) {
|
|
let method = getMethod(obj, name);
|
|
if (method)
|
|
obj[name] = method;
|
|
}
|
|
|
|
var signals;
|
|
function init() {
|
|
registerOverridePrototype(imports.ui.messageTray.MessageTray, '_updateState');
|
|
registerOverridePrototype(WindowManager.WindowManager, '_prepareWorkspaceSwitch');
|
|
registerOverridePrototype(Workspace.Workspace, '_isOverviewWindow');
|
|
if (Workspace.WindowClone)
|
|
registerOverridePrototype(Workspace.WindowClone, 'getOriginalPosition', getOriginalPosition);
|
|
|
|
registerOverridePrototype(Workspace.Workspace, '_realRecalculateWindowPositions', _realRecalculateWindowPositions);
|
|
|
|
registerOverridePrototype(Workspace.UnalignedLayoutStrategy, '_sortRow', row => row);
|
|
|
|
registerOverridePrototype(WindowManager.WorkspaceTracker, '_checkWorkspaces', _checkWorkspaces);
|
|
if (WindowManager.TouchpadWorkspaceSwitchAction) // disable 4-finger swipe
|
|
registerOverridePrototype(WindowManager.TouchpadWorkspaceSwitchAction, '_checkActivated', () => false);
|
|
|
|
|
|
// Work around https://gitlab.gnome.org/GNOME/gnome-shell/issues/1884
|
|
if (!WindowManager.WindowManager.prototype._removeEffect) {
|
|
registerOverridePrototype(WindowManager.WindowManager, '_mapWindowOverwrite',
|
|
function (shellwm, actor) {
|
|
if (this._mapping.delete(actor)) {
|
|
shellwm.completed_map(actor);
|
|
}
|
|
});
|
|
}
|
|
|
|
let layout = computeLayout
|
|
if (version[1] > 37) {
|
|
layout = computeLayout338
|
|
registerOverridePrototype(Workspace.WorkspaceLayout, 'addWindow', addWindow)
|
|
}
|
|
|
|
if (version[1] > 32)
|
|
registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout);
|
|
|
|
|
|
// Kill pinch gestures as they work pretty bad (especially when 3-finger swiping)
|
|
registerOverrideProp(imports.ui.viewSelector, "PINCH_GESTURE_THRESHOLD", 0);
|
|
|
|
if (Main.wm._swipeTracker)
|
|
registerOverrideProp(Main.wm._swipeTracker._touchpadGesture, "enabled", false);
|
|
|
|
registerOverridePrototype(Workspace.Workspace, '_isOverviewWindow', (win) => {
|
|
let metaWindow = win.meta_window || win;
|
|
if (settings.get_boolean('only-scratch-in-overview'))
|
|
return Scratch.isScratchWindow(metaWindow) && !metaWindow.skip_taskbar;
|
|
if (settings.get_boolean('disable-scratch-in-overview'))
|
|
return !Scratch.isScratchWindow(metaWindow) && !metaWindow.skip_taskbar;
|
|
});
|
|
|
|
signals = new utils.Signals();
|
|
}
|
|
|
|
var actions;
|
|
function enable() {
|
|
enableOverrides();
|
|
|
|
/*
|
|
* Some actions work rather poorly.
|
|
* In particular the 3-finger hold + tap can randomly activate a minimized
|
|
* window when tapping after a 3-finger swipe
|
|
*/
|
|
actions = global.stage.get_actions().filter(a => {
|
|
switch (a.constructor) {
|
|
case WindowManager.AppSwitchAction:
|
|
return true;
|
|
}
|
|
});
|
|
actions.forEach(a => global.stage.remove_action(a))
|
|
|
|
|
|
signals.connect(settings, 'changed::override-hot-corner',
|
|
disableHotcorners);
|
|
disableHotcorners();
|
|
|
|
function scratchInOverview() {
|
|
let onlyScratch = settings.get_boolean('only-scratch-in-overview');
|
|
let disableScratch = settings.get_boolean('disable-scratch-in-overview');
|
|
if (onlyScratch || disableScratch) {
|
|
enableOverride(Workspace.Workspace.prototype, '_isOverviewWindow');
|
|
} else {
|
|
disableOverride(Workspace.Workspace.prototype, '_isOverviewWindow');
|
|
}
|
|
}
|
|
signals.connect(settings, 'changed::only-scratch-in-overview',
|
|
scratchInOverview);
|
|
signals.connect(settings, 'changed::disable-scratch-in-overview',
|
|
scratchInOverview);
|
|
scratchInOverview();
|
|
|
|
|
|
/* The «native» workspace animation can be now (3.30) be disabled as it
|
|
calls out of the function bound to the `switch-workspace` signal.
|
|
*/
|
|
WindowManager.WindowManager.prototype._prepareWorkspaceSwitch =
|
|
function (from, to, direction) {
|
|
if (this._switchData)
|
|
return;
|
|
|
|
let wgroup = global.window_group;
|
|
let windows = global.get_window_actors();
|
|
let switchData = {};
|
|
|
|
this._switchData = switchData;
|
|
switchData.movingWindowBin = new Clutter.Actor();
|
|
switchData.windows = [];
|
|
switchData.surroundings = {};
|
|
switchData.gestureActivated = false;
|
|
switchData.inProgress = false;
|
|
|
|
switchData.container = new Clutter.Actor();
|
|
|
|
};
|
|
|
|
Workspace.Workspace.prototype._realRecalculateWindowPositions = _realRecalculateWindowPositions;
|
|
|
|
// Don't hide notifications when there's fullscreen windows in the workspace.
|
|
// Fullscreen windows aren't special in paperWM and might not even be
|
|
// visible, so hiding notifications makes no sense.
|
|
with (imports.ui.messageTray) {
|
|
MessageTray.prototype._updateState
|
|
= function () {
|
|
let hasMonitor = Main.layoutManager.primaryMonitor != null;
|
|
this.actor.visible = !this._bannerBlocked && hasMonitor && this._banner != null;
|
|
if (this._bannerBlocked || !hasMonitor)
|
|
return;
|
|
|
|
// If our state changes caused _updateState to be called,
|
|
// just exit now to prevent reentrancy issues.
|
|
if (this._updatingState)
|
|
return;
|
|
|
|
this._updatingState = true;
|
|
|
|
// Filter out acknowledged notifications.
|
|
let changed = false;
|
|
this._notificationQueue = this._notificationQueue.filter(function(n) {
|
|
changed = changed || n.acknowledged;
|
|
return !n.acknowledged;
|
|
});
|
|
|
|
if (changed)
|
|
this.emit('queue-changed');
|
|
|
|
let hasNotifications = Main.sessionMode.hasNotifications;
|
|
|
|
if (this._notificationState == State.HIDDEN) {
|
|
let nextNotification = this._notificationQueue[0] || null;
|
|
if (hasNotifications && nextNotification) {
|
|
// Monkeypatch here
|
|
let limited = this._busy;
|
|
let showNextNotification = (!limited || nextNotification.forFeedback || nextNotification.urgency == Urgency.CRITICAL);
|
|
if (showNextNotification)
|
|
this._showNotification();
|
|
}
|
|
} else if (this._notificationState == State.SHOWN) {
|
|
let expired = (this._userActiveWhileNotificationShown &&
|
|
this._notificationTimeoutId == 0 &&
|
|
this._notification.urgency != Urgency.CRITICAL &&
|
|
!this._banner.focused &&
|
|
!this._pointerInNotification) || this._notificationExpired;
|
|
let mustClose = (this._notificationRemoved || !hasNotifications || expired);
|
|
|
|
if (mustClose) {
|
|
let animate = hasNotifications && !this._notificationRemoved;
|
|
this._hideNotification(animate);
|
|
} else if (this._pointerInNotification && !this._banner.expanded) {
|
|
this._expandBanner(false);
|
|
} else if (this._pointerInNotification) {
|
|
this._ensureBannerFocused();
|
|
}
|
|
}
|
|
|
|
this._updatingState = false;
|
|
|
|
// Clean transient variables that are used to communicate actions
|
|
// to updateState()
|
|
this._notificationExpired = false;
|
|
};
|
|
}
|
|
|
|
|
|
}
|
|
|
|
function disable() {
|
|
disableOverrides();
|
|
actions.forEach(a => global.stage.add_action(a))
|
|
|
|
signals.destroy();
|
|
Main.layoutManager._updateHotCorners();
|
|
}
|
|
|
|
// 3.32 overivew layout
|
|
function computeLayout(windows, layout) {
|
|
let numRows = layout.numRows;
|
|
|
|
let rows = [];
|
|
let totalWidth = 0;
|
|
for (let i = 0; i < windows.length; i++) {
|
|
let window = windows[i];
|
|
let s = this._computeWindowScale(window);
|
|
totalWidth += window.width * s;
|
|
}
|
|
|
|
let idealRowWidth = totalWidth / numRows;
|
|
let windowIdx = 0;
|
|
for (let i = 0; i < numRows; i++) {
|
|
let col = 0;
|
|
let row = this._newRow();
|
|
rows.push(row);
|
|
|
|
for (; windowIdx < windows.length; windowIdx++) {
|
|
let window = windows[windowIdx];
|
|
let s = this._computeWindowScale(window);
|
|
let width = window.width * s;
|
|
let height = window.height * s;
|
|
row.fullHeight = Math.max(row.fullHeight, height);
|
|
|
|
// either new width is < idealWidth or new width is nearer from idealWidth then oldWidth
|
|
if (this._keepSameRow(row, window, width, idealRowWidth) || (i == numRows - 1)) {
|
|
row.windows.push(window);
|
|
row.fullWidth += width;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let gridHeight = 0;
|
|
let maxRow;
|
|
for (let i = 0; i < numRows; i++) {
|
|
let row = rows[i];
|
|
this._sortRow(row);
|
|
|
|
if (!maxRow || row.fullWidth > maxRow.fullWidth)
|
|
maxRow = row;
|
|
gridHeight += row.fullHeight;
|
|
}
|
|
|
|
layout.rows = rows;
|
|
layout.maxColumns = maxRow.windows.length;
|
|
layout.gridWidth = maxRow.fullWidth;
|
|
layout.gridHeight = gridHeight;
|
|
}
|
|
|
|
function sortWindows(a, b) {
|
|
let aw = a.metaWindow;
|
|
let bw = b.metaWindow;
|
|
let spaceA = Tiling.spaces.spaceOfWindow(aw)
|
|
let spaceB = Tiling.spaces.spaceOfWindow(bw)
|
|
let ia = spaceA.indexOf(aw);
|
|
let ib = spaceB.indexOf(bw);
|
|
print(aw.title, bw.title, spaceA.actor, spaceB.actor)
|
|
if ((ia === -1 && ib === -1)) {
|
|
return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
|
|
}
|
|
if (ia === -1) {
|
|
return -1;
|
|
}
|
|
if (ib === -1) {
|
|
return 1;
|
|
}
|
|
return ia - ib;
|
|
}
|
|
|
|
function computeLayout338(windows, layout) {
|
|
let numRows = layout.numRows;
|
|
|
|
let rows = [];
|
|
let totalWidth = 0;
|
|
for (let i = 0; i < windows.length; i++) {
|
|
let window = windows[i];
|
|
let s = this._computeWindowScale(window);
|
|
totalWidth += window.boundingBox.width * s;
|
|
}
|
|
|
|
let idealRowWidth = totalWidth / numRows;
|
|
|
|
let sortedWindows = windows.slice();
|
|
// addWindow should have made sure we're already sorted.
|
|
// sortedWindows.sort(sortWindows);
|
|
|
|
let windowIdx = 0;
|
|
for (let i = 0; i < numRows; i++) {
|
|
let row = this._newRow();
|
|
rows.push(row);
|
|
|
|
for (; windowIdx < sortedWindows.length; windowIdx++) {
|
|
let window = sortedWindows[windowIdx];
|
|
let s = this._computeWindowScale(window);
|
|
let width = window.boundingBox.width * s;
|
|
let height = window.boundingBox.height * s;
|
|
row.fullHeight = Math.max(row.fullHeight, height);
|
|
|
|
// either new width is < idealWidth or new width is nearer from idealWidth then oldWidth
|
|
if (this._keepSameRow(row, window, width, idealRowWidth) || (i == numRows - 1)) {
|
|
row.windows.push(window);
|
|
row.fullWidth += width;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let gridHeight = 0;
|
|
let maxRow;
|
|
for (let i = 0; i < numRows; i++) {
|
|
let row = rows[i];
|
|
this._sortRow(row);
|
|
|
|
if (!maxRow || row.fullWidth > maxRow.fullWidth)
|
|
maxRow = row;
|
|
gridHeight += row.fullHeight;
|
|
}
|
|
|
|
layout.rows = rows;
|
|
layout.maxColumns = maxRow.windows.length;
|
|
layout.gridWidth = maxRow.fullWidth;
|
|
layout.gridHeight = gridHeight;
|
|
}
|
|
|
|
const wmSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'});
|
|
function _checkWorkspaces() {
|
|
let workspaceManager = global.workspace_manager;
|
|
let i;
|
|
let emptyWorkspaces = [];
|
|
|
|
if (!Meta.prefs_get_dynamic_workspaces()) {
|
|
this._checkWorkspacesId = 0;
|
|
return false;
|
|
}
|
|
|
|
// Update workspaces only if Dynamic Workspace Management has not been paused by some other function
|
|
if (this._pauseWorkspaceCheck || Tiling.inPreview)
|
|
return true;
|
|
|
|
for (i = 0; i < this._workspaces.length; i++) {
|
|
let lastRemoved = this._workspaces[i]._lastRemovedWindow;
|
|
if ((lastRemoved &&
|
|
(lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
|
|
lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
|
|
lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
|
|
this._workspaces[i]._keepAliveId)
|
|
emptyWorkspaces[i] = false;
|
|
else
|
|
emptyWorkspaces[i] = true;
|
|
}
|
|
|
|
let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
|
|
for (i = 0; i < sequences.length; i++) {
|
|
let index = sequences[i].get_workspace();
|
|
if (index >= 0 && index <= workspaceManager.n_workspaces)
|
|
emptyWorkspaces[index] = false;
|
|
}
|
|
|
|
let windows = global.get_window_actors();
|
|
for (i = 0; i < windows.length; i++) {
|
|
let actor = windows[i];
|
|
let win = actor.get_meta_window();
|
|
|
|
if (win.is_on_all_workspaces())
|
|
continue;
|
|
|
|
let workspaceIndex = win.get_workspace().index();
|
|
emptyWorkspaces[workspaceIndex] = false;
|
|
}
|
|
|
|
let minimum = wmSettings.get_int('num-workspaces');
|
|
// Make sure we have a minimum number of spaces
|
|
for (i = 0; i < Math.max(Main.layoutManager.monitors.length, minimum); i++) {
|
|
if (i >= emptyWorkspaces.length) {
|
|
workspaceManager.append_new_workspace(false, global.get_current_time());
|
|
emptyWorkspaces.push(true);
|
|
}
|
|
}
|
|
|
|
// If we don't have an empty workspace at the end, add one
|
|
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
|
|
workspaceManager.append_new_workspace(false, global.get_current_time());
|
|
emptyWorkspaces.push(true);
|
|
}
|
|
|
|
let lastIndex = emptyWorkspaces.length - 1;
|
|
let lastEmptyIndex = emptyWorkspaces.lastIndexOf(false) + 1;
|
|
let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
|
|
|
|
// Keep the active workspace
|
|
emptyWorkspaces[activeWorkspaceIndex] = false;
|
|
|
|
// Keep a minimum number of spaces
|
|
for (i = 0; i < Math.max(Main.layoutManager.monitors.length, minimum); i++) {
|
|
emptyWorkspaces[i] = false;
|
|
}
|
|
|
|
// Keep visible spaces
|
|
for (let [monitor, space] of Tiling.spaces.monitors) {
|
|
emptyWorkspaces[space.workspace.index()] = false;
|
|
}
|
|
|
|
// Delete empty workspaces except for the last one; do it from the end
|
|
// to avoid index changes
|
|
for (i = lastIndex; i >= 0; i--) {
|
|
if (emptyWorkspaces[i] && i != lastEmptyIndex) {
|
|
workspaceManager.remove_workspace(this._workspaces[i], global.get_current_time());
|
|
}
|
|
}
|
|
|
|
this._checkWorkspacesId = 0;
|
|
return false;
|
|
};
|
|
|
|
|
|
function addWindow(window, metaWindow) {
|
|
if (this._windows.has(window))
|
|
return;
|
|
|
|
this._windows.set(window, {
|
|
metaWindow,
|
|
sizeChangedId: metaWindow.connect('size-changed', () => {
|
|
this._layout = null;
|
|
this.layout_changed();
|
|
}),
|
|
destroyId: window.connect('destroy', () =>
|
|
this.removeWindow(window)),
|
|
currentTransition: null,
|
|
});
|
|
|
|
this._sortedWindows.push(window);
|
|
this._sortedWindows.sort(sortWindows);
|
|
|
|
this._syncOverlay(window);
|
|
this._container.add_child(window);
|
|
|
|
this._layout = null;
|
|
this.layout_changed();
|
|
}
|