gnome-shell/js/ui/windowManager.js

1937 lines
73 KiB
JavaScript
Raw Normal View History

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WindowManager */
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const AltTab = imports.ui.altTab;
const AppFavorites = imports.ui.appFavorites;
const Dialog = imports.ui.dialog;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const WindowMenu = imports.ui.windowMenu;
const PadOsd = imports.ui.padOsd;
const EdgeDragAction = imports.ui.edgeDragAction;
const CloseDialog = imports.ui.closeDialog;
const SwitchMonitor = imports.ui.switchMonitor;
const IBusManager = imports.misc.ibusManager;
const WorkspaceAnimation = imports.ui.workspaceAnimation;
const { loadInterfaceXML } = imports.misc.fileUtils;
var SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var MINIMIZE_WINDOW_ANIMATION_TIME = 400;
var MINIMIZE_WINDOW_ANIMATION_MODE = Clutter.AnimationMode.EASE_OUT_EXPO;
var SHOW_WINDOW_ANIMATION_TIME = 150;
var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 100;
var DESTROY_WINDOW_ANIMATION_TIME = 150;
var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 100;
var WINDOW_ANIMATION_TIME = 250;
var SCROLL_TIMEOUT_TIME = 150;
var DIM_BRIGHTNESS = -0.3;
var DIM_TIME = 500;
var UNDIM_TIME = 250;
var APP_MOTION_THRESHOLD = 30;
var ONE_SECOND = 1000; // in ms
var MIN_NUM_WORKSPACES = 2;
const GSD_WACOM_BUS_NAME = 'org.gnome.SettingsDaemon.Wacom';
const GSD_WACOM_OBJECT_PATH = '/org/gnome/SettingsDaemon/Wacom';
const GsdWacomIface = loadInterfaceXML('org.gnome.SettingsDaemon.Wacom');
const GsdWacomProxy = Gio.DBusProxy.makeProxyWrapper(GsdWacomIface);
const WINDOW_DIMMER_EFFECT_NAME = "gnome-shell-window-dimmer";
Gio._promisify(Shell, 'util_start_systemd_unit');
Gio._promisify(Shell, 'util_stop_systemd_unit');
var DisplayChangeDialog = GObject.registerClass(
class DisplayChangeDialog extends ModalDialog.ModalDialog {
_init(wm) {
super._init();
this._wm = wm;
this._countDown = Meta.MonitorManager.get_display_configuration_timeout();
// Translators: This string should be shorter than 30 characters
let title = _('Keep these display settings?');
let description = this._formatCountDown();
this._content = new Dialog.MessageDialogContent({ title, description });
this.contentLayout.add_child(this._content);
/* Translators: this and the following message should be limited in length,
to avoid ellipsizing the labels.
*/
this._cancelButton = this.addButton({
label: _('Revert Settings'),
action: this._onFailure.bind(this),
key: Clutter.KEY_Escape,
});
this._okButton = this.addButton({
label: _('Keep Changes'),
action: this._onSuccess.bind(this),
default: true,
});
this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ONE_SECOND, this._tick.bind(this));
GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._tick');
}
close(timestamp) {
if (this._timeoutId > 0) {
GLib.source_remove(this._timeoutId);
this._timeoutId = 0;
}
super.close(timestamp);
}
_formatCountDown() {
const fmt = ngettext(
'Settings changes will revert in %d second',
'Settings changes will revert in %d seconds',
this._countDown);
return fmt.format(this._countDown);
}
_tick() {
this._countDown--;
if (this._countDown == 0) {
/* mutter already takes care of failing at timeout */
this._timeoutId = 0;
this.close();
return GLib.SOURCE_REMOVE;
}
this._content.description = this._formatCountDown();
return GLib.SOURCE_CONTINUE;
}
_onFailure() {
this._wm.complete_display_change(false);
this.close();
}
_onSuccess() {
this._wm.complete_display_change(true);
this.close();
}
});
var WindowDimmer = GObject.registerClass(
class WindowDimmer extends Clutter.BrightnessContrastEffect {
_init() {
super._init({
name: WINDOW_DIMMER_EFFECT_NAME,
enabled: false,
});
this._enabled = true;
}
_syncEnabled() {
let animating = this.actor.get_transition(`@effects.${this.name}.brightness`) !== null;
let dimmed = this.brightness.red != 127;
this.enabled = this._enabled && (animating || dimmed);
}
setEnabled(enabled) {
this._enabled = enabled;
this._syncEnabled();
}
setDimmed(dimmed, animate) {
let val = 127 * (1 + (dimmed ? 1 : 0) * DIM_BRIGHTNESS);
let color = Clutter.Color.new(val, val, val, 255);
this.actor.ease_property(`@effects.${this.name}.brightness`, color, {
mode: Clutter.AnimationMode.LINEAR,
duration: (dimmed ? DIM_TIME : UNDIM_TIME) * (animate ? 1 : 0),
onComplete: () => this._syncEnabled(),
});
this._syncEnabled();
}
});
function getWindowDimmer(actor) {
let enabled = Meta.prefs_get_attach_modal_dialogs();
let effect = actor.get_effect(WINDOW_DIMMER_EFFECT_NAME);
if (effect) {
effect.setEnabled(enabled);
} else if (enabled) {
effect = new WindowDimmer();
actor.add_effect(effect);
}
return effect;
}
/*
* When the last window closed on a workspace is a dialog or splash
* screen, we assume that it might be an initial window shown before
* the main window of an application, and give the app a grace period
* where it can map another window before we remove the workspace.
*/
var LAST_WINDOW_GRACE_TIME = 1000;
var WorkspaceTracker = class {
constructor(wm) {
this._wm = wm;
this._workspaces = [];
this._checkWorkspacesId = 0;
this._pauseWorkspaceCheck = false;
let tracker = Shell.WindowTracker.get_default();
tracker.connect('startup-sequence-changed', this._queueCheckWorkspaces.bind(this));
let workspaceManager = global.workspace_manager;
workspaceManager.connect('notify::n-workspaces',
this._nWorkspacesChanged.bind(this));
workspaceManager.connect('workspaces-reordered', () => {
this._workspaces.sort((a, b) => a.index() - b.index());
});
global.window_manager.connect('switch-workspace',
this._queueCheckWorkspaces.bind(this));
global.display.connect('window-entered-monitor',
this._windowEnteredMonitor.bind(this));
global.display.connect('window-left-monitor',
this._windowLeftMonitor.bind(this));
this._workspaceSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter' });
this._workspaceSettings.connect('changed::dynamic-workspaces', this._queueCheckWorkspaces.bind(this));
this._nWorkspacesChanged();
}
blockUpdates() {
this._pauseWorkspaceCheck = true;
}
unblockUpdates() {
this._pauseWorkspaceCheck = false;
}
_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)
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;
}
// 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);
}
// Enforce minimum number of workspaces
while (emptyWorkspaces.length < MIN_NUM_WORKSPACES) {
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();
emptyWorkspaces[activeWorkspaceIndex] = 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 (workspaceManager.n_workspaces === MIN_NUM_WORKSPACES)
break;
if (emptyWorkspaces[i] && i != lastEmptyIndex)
workspaceManager.remove_workspace(this._workspaces[i], global.get_current_time());
}
this._checkWorkspacesId = 0;
return false;
}
keepWorkspaceAlive(workspace, duration) {
if (workspace._keepAliveId)
GLib.source_remove(workspace._keepAliveId);
workspace._keepAliveId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, duration, () => {
workspace._keepAliveId = 0;
this._queueCheckWorkspaces();
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(workspace._keepAliveId, '[gnome-shell] this._queueCheckWorkspaces');
}
_windowRemoved(workspace, window) {
workspace._lastRemovedWindow = window;
this._queueCheckWorkspaces();
let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, LAST_WINDOW_GRACE_TIME, () => {
if (workspace._lastRemovedWindow == window) {
workspace._lastRemovedWindow = null;
this._queueCheckWorkspaces();
}
return GLib.SOURCE_REMOVE;
});
GLib.Source.set_name_by_id(id, '[gnome-shell] this._queueCheckWorkspaces');
}
_windowLeftMonitor(metaDisplay, monitorIndex, _metaWin) {
// If the window left the primary monitor, that
// might make that workspace empty
if (monitorIndex == Main.layoutManager.primaryIndex)
this._queueCheckWorkspaces();
}
_windowEnteredMonitor(metaDisplay, monitorIndex, _metaWin) {
// If the window entered the primary monitor, that
// might make that workspace non-empty
if (monitorIndex == Main.layoutManager.primaryIndex)
this._queueCheckWorkspaces();
}
_queueCheckWorkspaces() {
if (this._checkWorkspacesId == 0)
this._checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, this._checkWorkspaces.bind(this));
}
_nWorkspacesChanged() {
let workspaceManager = global.workspace_manager;
let oldNumWorkspaces = this._workspaces.length;
let newNumWorkspaces = workspaceManager.n_workspaces;
if (oldNumWorkspaces == newNumWorkspaces)
return false;
if (newNumWorkspaces > oldNumWorkspaces) {
let w;
// Assume workspaces are only added at the end
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
this._workspaces[w] = workspaceManager.get_workspace_by_index(w);
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
this._workspaces[w].connectObject(
'window-added', this._queueCheckWorkspaces.bind(this),
'window-removed', this._windowRemoved.bind(this), this);
}
} else {
// Assume workspaces are only removed sequentially
// (e.g. 2,3,4 - not 2,4,7)
let removedIndex;
let removedNum = oldNumWorkspaces - newNumWorkspaces;
for (let w = 0; w < oldNumWorkspaces; w++) {
let workspace = workspaceManager.get_workspace_by_index(w);
if (this._workspaces[w] != workspace) {
removedIndex = w;
break;
}
}
let lostWorkspaces = this._workspaces.splice(removedIndex, removedNum);
lostWorkspaces.forEach(workspace => workspace.disconnectObject(this));
}
this._queueCheckWorkspaces();
return false;
}
};
var TilePreview = GObject.registerClass(
class TilePreview extends St.Widget {
_init() {
super._init();
global.window_group.add_actor(this);
this._reset();
this._showing = false;
}
open(window, tileRect, monitorIndex) {
let windowActor = window.get_compositor_private();
if (!windowActor)
return;
global.window_group.set_child_below_sibling(this, windowActor);
if (this._rect && this._rect.equal(tileRect))
return;
let changeMonitor = this._monitorIndex == -1 ||
this._monitorIndex != monitorIndex;
this._monitorIndex = monitorIndex;
this._rect = tileRect;
let monitor = Main.layoutManager.monitors[monitorIndex];
this._updateStyle(monitor);
if (!this._showing || changeMonitor) {
const monitorRect = new Meta.Rectangle({
x: monitor.x,
y: monitor.y,
width: monitor.width,
height: monitor.height,
});
let [, rect] = window.get_frame_rect().intersect(monitorRect);
this.set_size(rect.width, rect.height);
this.set_position(rect.x, rect.y);
this.opacity = 0;
}
this._showing = true;
this.show();
this.ease({
x: tileRect.x,
y: tileRect.y,
width: tileRect.width,
height: tileRect.height,
opacity: 255,
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
}
close() {
if (!this._showing)
return;
this._showing = false;
this.ease({
opacity: 0,
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._reset(),
});
}
_reset() {
this.hide();
this._rect = null;
this._monitorIndex = -1;
}
_updateStyle(monitor) {
let styles = ['tile-preview'];
if (this._monitorIndex == Main.layoutManager.primaryIndex)
styles.push('on-primary');
if (this._rect.x == monitor.x)
styles.push('tile-preview-left');
if (this._rect.x + this._rect.width == monitor.x + monitor.width)
styles.push('tile-preview-right');
this.style_class = styles.join(' ');
}
});
var AppSwitchAction = GObject.registerClass({
Signals: { 'activated': {} },
}, class AppSwitchAction extends Clutter.GestureAction {
_init() {
super._init();
this.set_n_touch_points(3);
global.display.connect('grab-op-begin', () => {
this.cancel();
});
}
vfunc_gesture_prepare(_actor) {
if (Main.actionMode != Shell.ActionMode.NORMAL) {
this.cancel();
return false;
}
return this.get_n_current_points() <= 4;
}
vfunc_gesture_begin(_actor) {
// in milliseconds
const LONG_PRESS_TIMEOUT = 250;
let nPoints = this.get_n_current_points();
let event = this.get_last_event(nPoints - 1);
if (nPoints == 3) {
this._longPressStartTime = event.get_time();
} else if (nPoints == 4) {
// Check whether the 4th finger press happens after a 3-finger long press,
// this only needs to be checked on the first 4th finger press
if (this._longPressStartTime != null &&
event.get_time() < this._longPressStartTime + LONG_PRESS_TIMEOUT) {
this.cancel();
} else {
this._longPressStartTime = null;
this.emit('activated');
}
}
return this.get_n_current_points() <= 4;
}
vfunc_gesture_progress(_actor) {
if (this.get_n_current_points() == 3) {
for (let i = 0; i < this.get_n_current_points(); i++) {
let [startX, startY] = this.get_press_coords(i);
let [x, y] = this.get_motion_coords(i);
if (Math.abs(x - startX) > APP_MOTION_THRESHOLD ||
Math.abs(y - startY) > APP_MOTION_THRESHOLD)
return false;
}
}
return true;
}
});
var ResizePopup = GObject.registerClass(
class ResizePopup extends St.Widget {
_init() {
super._init({ layout_manager: new Clutter.BinLayout() });
this._label = new St.Label({
style_class: 'resize-popup',
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
x_expand: true,
y_expand: true,
});
this.add_child(this._label);
Main.uiGroup.add_actor(this);
}
set(rect, displayW, displayH) {
/* Translators: This represents the size of a window. The first number is
* the width of the window and the second is the height. */
let text = _("%d × %d").format(displayW, displayH);
this._label.set_text(text);
this.set_position(rect.x, rect.y);
this.set_size(rect.width, rect.height);
}
});
var WindowManager = class {
constructor() {
this._shellwm = global.window_manager;
this._minimizing = new Set();
this._unminimizing = new Set();
this._mapping = new Set();
this._resizing = new Set();
this._resizePending = new Set();
this._destroying = new Set();
this._dimmedWindows = [];
this._skippedActors = new Set();
this._allowedKeybindings = {};
this._isWorkspacePrepended = false;
this._canScroll = true; // limiting scrolling speed
this._shellwm.connect('kill-window-effects', (shellwm, actor) => {
this._minimizeWindowDone(shellwm, actor);
this._mapWindowDone(shellwm, actor);
this._destroyWindowDone(shellwm, actor);
this._sizeChangeWindowDone(shellwm, actor);
});
this._shellwm.connect('switch-workspace', this._switchWorkspace.bind(this));
this._shellwm.connect('show-tile-preview', this._showTilePreview.bind(this));
this._shellwm.connect('hide-tile-preview', this._hideTilePreview.bind(this));
this._shellwm.connect('show-window-menu', this._showWindowMenu.bind(this));
this._shellwm.connect('minimize', this._minimizeWindow.bind(this));
this._shellwm.connect('unminimize', this._unminimizeWindow.bind(this));
this._shellwm.connect('size-change', this._sizeChangeWindow.bind(this));
this._shellwm.connect('size-changed', this._sizeChangedWindow.bind(this));
this._shellwm.connect('map', this._mapWindow.bind(this));
this._shellwm.connect('destroy', this._destroyWindow.bind(this));
this._shellwm.connect('filter-keybinding', this._filterKeybinding.bind(this));
this._shellwm.connect('confirm-display-change', this._confirmDisplayChange.bind(this));
this._shellwm.connect('create-close-dialog', this._createCloseDialog.bind(this));
this._shellwm.connect('create-inhibit-shortcuts-dialog', this._createInhibitShortcutsDialog.bind(this));
this._workspaceSwitcherPopup = null;
this._tilePreview = null;
this.allowKeybinding('switch-to-session-1', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-2', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-3', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-4', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-5', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-6', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-7', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-8', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-9', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-10', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-11', Shell.ActionMode.ALL);
this.allowKeybinding('switch-to-session-12', Shell.ActionMode.ALL);
this.setCustomKeybindingHandler('switch-to-workspace-left',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-right',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-up',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-down',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-last',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-left',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-right',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-up',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-down',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-1',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-2',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-3',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-4',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-5',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-6',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-7',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-8',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-9',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-10',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-11',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-to-workspace-12',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-1',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-2',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-3',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-4',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-5',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-6',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-7',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-8',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-9',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-10',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-11',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-12',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('move-to-workspace-last',
Shell.ActionMode.NORMAL,
this._showWorkspaceSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-applications',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-group',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-applications-backward',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-group-backward',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-windows',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-windows-backward',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('cycle-windows',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('cycle-windows-backward',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('cycle-group',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('cycle-group-backward',
Shell.ActionMode.NORMAL,
this._startSwitcher.bind(this));
this.setCustomKeybindingHandler('switch-panels',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW |
Shell.ActionMode.LOCK_SCREEN |
Shell.ActionMode.UNLOCK_SCREEN |
Shell.ActionMode.LOGIN_SCREEN,
this._startA11ySwitcher.bind(this));
this.setCustomKeybindingHandler('switch-panels-backward',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW |
Shell.ActionMode.LOCK_SCREEN |
Shell.ActionMode.UNLOCK_SCREEN |
Shell.ActionMode.LOGIN_SCREEN,
this._startA11ySwitcher.bind(this));
this.setCustomKeybindingHandler('switch-monitor',
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._startSwitcher.bind(this));
this.addKeybinding('open-application-menu',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.POPUP,
this._toggleAppMenu.bind(this));
this.addKeybinding('toggle-message-tray',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW |
Shell.ActionMode.POPUP,
this._toggleCalendar.bind(this));
this.addKeybinding('switch-to-application-1',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-2',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-3',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-4',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-5',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-6',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-7',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-8',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
this.addKeybinding('switch-to-application-9',
new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL |
Shell.ActionMode.OVERVIEW,
this._switchToApplication.bind(this));
global.stage.connect('scroll-event', (stage, event) => {
const allowedModes = Shell.ActionMode.NORMAL;
if ((allowedModes & Main.actionMode) === 0)
return Clutter.EVENT_PROPAGATE;
if (this._workspaceAnimation.canHandleScrollEvent(event))
return Clutter.EVENT_PROPAGATE;
if ((event.get_state() & global.display.compositor_modifiers) === 0)
return Clutter.EVENT_PROPAGATE;
return this.handleWorkspaceScroll(event);
});
global.display.connect('show-resize-popup', this._showResizePopup.bind(this));
global.display.connect('show-pad-osd', this._showPadOsd.bind(this));
global.display.connect('show-osd', (display, monitorIndex, iconName, label) => {
let icon = Gio.Icon.new_for_string(iconName);
Main.osdWindowManager.show(monitorIndex, icon, label, null);
});
this._gsdWacomProxy = new GsdWacomProxy(Gio.DBus.session, GSD_WACOM_BUS_NAME,
GSD_WACOM_OBJECT_PATH,
(proxy, error) => {
if (error)
log(error.message);
});
global.display.connect('pad-mode-switch', (display, pad, _group, _mode) => {
let labels = [];
// FIXME: Fix num buttons
for (let i = 0; i < 50; i++) {
let str = display.get_pad_action_label(pad, Meta.PadActionType.BUTTON, i);
labels.push(str ?? '');
}
if (this._gsdWacomProxy)
this._gsdWacomProxy.SetOLEDLabelsRemote(pad.get_device_node(), labels);
});
global.display.connect('init-xserver', (display, task) => {
IBusManager.getIBusManager().restartDaemon(['--xim']);
this._startX11Services(task);
return true;
});
global.display.connect('x11-display-closing', () => {
if (!Meta.is_wayland_compositor())
return;
this._stopX11Services(null);
IBusManager.getIBusManager().restartDaemon();
});
Main.overview.connect('showing', () => {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._undimWindow(this._dimmedWindows[i]);
});
Main.overview.connect('hiding', () => {
for (let i = 0; i < this._dimmedWindows.length; i++)
this._dimWindow(this._dimmedWindows[i]);
});
this._windowMenuManager = new WindowMenu.WindowMenuManager();
if (Main.sessionMode.hasWorkspaces)
this._workspaceTracker = new WorkspaceTracker(this);
let appSwitchAction = new AppSwitchAction();
appSwitchAction.connect('activated', this._switchApp.bind(this));
global.stage.add_action_full('app-switch', Clutter.EventPhase.CAPTURE, appSwitchAction);
let mode = Shell.ActionMode.NORMAL;
let topDragAction = new EdgeDragAction.EdgeDragAction(St.Side.TOP, mode);
topDragAction.connect('activated', () => {
let currentWindow = global.display.focus_window;
if (currentWindow)
currentWindow.unmake_fullscreen();
});
let updateUnfullscreenGesture = () => {
let currentWindow = global.display.focus_window;
topDragAction.enabled = currentWindow && currentWindow.is_fullscreen();
};
global.display.connect('notify::focus-window', updateUnfullscreenGesture);
global.display.connect('in-fullscreen-changed', updateUnfullscreenGesture);
updateUnfullscreenGesture();
global.stage.add_action_full('unfullscreen', Clutter.EventPhase.CAPTURE, topDragAction);
this._workspaceAnimation =
new WorkspaceAnimation.WorkspaceAnimationController();
this._shellwm.connect('kill-switch-workspace', () => {
this._workspaceAnimation.cancelSwitchAnimation();
this._switchWorkspaceDone();
});
}
async _startX11Services(task) {
let status = true;
try {
await Shell.util_start_systemd_unit(
'gnome-session-x11-services-ready.target', 'fail', null);
} catch (e) {
// Ignore NOT_SUPPORTED error, which indicates we are not systemd
// managed and gnome-session will have taken care of everything
// already.
// Note that we do log cancellation from here.
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_SUPPORTED)) {
log(`Error starting X11 services: ${e.message}`);
status = false;
}
} finally {
task.return_boolean(status);
}
}
async _stopX11Services(cancellable) {
try {
await Shell.util_stop_systemd_unit(
'gnome-session-x11-services.target', 'fail', cancellable);
} catch (e) {
// Ignore NOT_SUPPORTED error, which indicates we are not systemd
// managed and gnome-session will have taken care of everything
// already.
// Note that we do log cancellation from here.
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_SUPPORTED))
log(`Error stopping X11 services: ${e.message}`);
}
}
_showPadOsd(display, device, settings, imagePath, editionMode, monitorIndex) {
this._currentPadOsd = new PadOsd.PadOsd(device, settings, imagePath, editionMode, monitorIndex);
this._currentPadOsd.connect('closed', () => (this._currentPadOsd = null));
return this._currentPadOsd;
}
_lookupIndex(windows, metaWindow) {
for (let i = 0; i < windows.length; i++) {
if (windows[i].metaWindow == metaWindow)
return i;
}
return -1;
}
_switchApp() {
let windows = global.get_window_actors().filter(actor => {
let win = actor.metaWindow;
let workspaceManager = global.workspace_manager;
let activeWorkspace = workspaceManager.get_active_workspace();
return !win.is_override_redirect() &&
win.located_on_workspace(activeWorkspace);
});
if (windows.length == 0)
return;
let focusWindow = global.display.focus_window;
let nextWindow;
if (focusWindow == null) {
nextWindow = windows[0].metaWindow;
} else {
let index = this._lookupIndex(windows, focusWindow) + 1;
if (index >= windows.length)
index = 0;
nextWindow = windows[index].metaWindow;
}
Main.activateWindow(nextWindow);
}
insertWorkspace(pos) {
let workspaceManager = global.workspace_manager;
if (!Meta.prefs_get_dynamic_workspaces())
return;
workspaceManager.append_new_workspace(false, global.get_current_time());
let windows = global.get_window_actors().map(a => a.meta_window);
// To create a new workspace, we slide all the windows on workspaces
// below us to the next workspace, leaving a blank workspace for us
// to recycle.
windows.forEach(window => {
// If the window is attached to an ancestor, we don't need/want
// to move it
if (window.get_transient_for() != null)
return;
// Same for OR windows
if (window.is_override_redirect())
return;
// Sticky windows don't need moving, in fact moving would
// unstick them
if (window.on_all_workspaces)
return;
// Windows on workspaces below pos don't need moving
let index = window.get_workspace().index();
if (index < pos)
return;
window.change_workspace_by_index(index + 1, true);
});
// If the new workspace was inserted before the active workspace,
// activate the workspace to which its windows went
let activeIndex = workspaceManager.get_active_workspace_index();
if (activeIndex >= pos) {
let newWs = workspaceManager.get_workspace_by_index(activeIndex + 1);
this._blockAnimations = true;
newWs.activate(global.get_current_time());
this._blockAnimations = false;
}
}
keepWorkspaceAlive(workspace, duration) {
if (!this._workspaceTracker)
return;
this._workspaceTracker.keepWorkspaceAlive(workspace, duration);
}
skipNextEffect(actor) {
this._skippedActors.add(actor);
}
setCustomKeybindingHandler(name, modes, handler) {
if (Meta.keybindings_set_custom_handler(name, handler))
this.allowKeybinding(name, modes);
}
addKeybinding(name, settings, flags, modes, handler) {
let action = global.display.add_keybinding(name, settings, flags, handler);
if (action != Meta.KeyBindingAction.NONE)
this.allowKeybinding(name, modes);
return action;
}
removeKeybinding(name) {
if (global.display.remove_keybinding(name))
this.allowKeybinding(name, Shell.ActionMode.NONE);
}
allowKeybinding(name, modes) {
this._allowedKeybindings[name] = modes;
}
_shouldAnimate() {
const overviewOpen = Main.overview.visible && !Main.overview.closing;
return !(overviewOpen || this._workspaceAnimation.gestureActive);
}
_shouldAnimateActor(actor, types) {
if (this._skippedActors.delete(actor))
return false;
if (!this._shouldAnimate())
return false;
if (!actor.get_texture())
return false;
let type = actor.meta_window.get_window_type();
return types.includes(type);
}
_minimizeWindow(shellwm, actor) {
const types = [
Meta.WindowType.NORMAL,
Meta.WindowType.MODAL_DIALOG,
Meta.WindowType.DIALOG,
];
if (!this._shouldAnimateActor(actor, types)) {
shellwm.completed_minimize(actor);
return;
}
actor.set_scale(1.0, 1.0);
this._minimizing.add(actor);
if (actor.meta_window.is_monitor_sized()) {
actor.ease({
opacity: 0,
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
onStopped: () => this._minimizeWindowDone(shellwm, actor),
});
} else {
let xDest, yDest, xScale, yScale;
let [success, geom] = actor.meta_window.get_icon_geometry();
if (success) {
xDest = geom.x;
yDest = geom.y;
xScale = geom.width / actor.width;
yScale = geom.height / actor.height;
} else {
let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
if (!monitor) {
this._minimizeWindowDone();
return;
}
xDest = monitor.x;
yDest = monitor.y;
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
xDest += monitor.width;
xScale = 0;
yScale = 0;
}
actor.ease({
scale_x: xScale,
scale_y: yScale,
x: xDest,
y: yDest,
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
onStopped: () => this._minimizeWindowDone(shellwm, actor),
});
}
}
_minimizeWindowDone(shellwm, actor) {
if (this._minimizing.delete(actor)) {
actor.remove_all_transitions();
actor.set_scale(1.0, 1.0);
actor.set_opacity(255);
actor.set_pivot_point(0, 0);
shellwm.completed_minimize(actor);
}
}
_unminimizeWindow(shellwm, actor) {
const types = [
Meta.WindowType.NORMAL,
Meta.WindowType.MODAL_DIALOG,
Meta.WindowType.DIALOG,
];
if (!this._shouldAnimateActor(actor, types)) {
shellwm.completed_unminimize(actor);
return;
}
this._unminimizing.add(actor);
if (actor.meta_window.is_monitor_sized()) {
actor.opacity = 0;
actor.set_scale(1.0, 1.0);
actor.ease({
opacity: 255,
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
onStopped: () => this._unminimizeWindowDone(shellwm, actor),
});
} else {
let [success, geom] = actor.meta_window.get_icon_geometry();
if (success) {
actor.set_position(geom.x, geom.y);
actor.set_scale(geom.width / actor.width,
geom.height / actor.height);
} else {
let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
if (!monitor) {
actor.show();
this._unminimizeWindowDone();
return;
}
actor.set_position(monitor.x, monitor.y);
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
actor.x += monitor.width;
actor.set_scale(0, 0);
}
let rect = actor.meta_window.get_buffer_rect();
let [xDest, yDest] = [rect.x, rect.y];
actor.show();
actor.ease({
scale_x: 1,
scale_y: 1,
x: xDest,
y: yDest,
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
onStopped: () => this._unminimizeWindowDone(shellwm, actor),
});
}
}
_unminimizeWindowDone(shellwm, actor) {
if (this._unminimizing.delete(actor)) {
actor.remove_all_transitions();
actor.set_scale(1.0, 1.0);
actor.set_opacity(255);
actor.set_pivot_point(0, 0);
shellwm.completed_unminimize(actor);
}
}
_sizeChangeWindow(shellwm, actor, whichChange, oldFrameRect, _oldBufferRect) {
const types = [Meta.WindowType.NORMAL];
const shouldAnimate =
this._shouldAnimateActor(actor, types) &&
oldFrameRect.width > 0 &&
oldFrameRect.height > 0;
if (shouldAnimate)
this._prepareAnimationInfo(shellwm, actor, oldFrameRect, whichChange);
else
shellwm.completed_size_change(actor);
}
_prepareAnimationInfo(shellwm, actor, oldFrameRect, _change) {
// Position a clone of the window on top of the old position,
// while actor updates are frozen.
let actorContent = actor.paint_to_content(oldFrameRect);
let actorClone = new St.Widget({ content: actorContent });
actorClone.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
actorClone.set_position(oldFrameRect.x, oldFrameRect.y);
actorClone.set_size(oldFrameRect.width, oldFrameRect.height);
windowManager: Use new mutter API to freeze window actors ourself We're currently using the hack of telling mutter that our effect is completed (even though it isn't) in order to unfreeze updates of the window actor. This causes a bug with detecting the wl_outputs a window is visible on, because the MetaWindowActor emits its "effects-completed" signal too early, making Mutter update the wl_outputs while we're doing the animation. Now since meta_wayland_actor_surface_is_on_logical_monitor() uses the transformed position and size of the MetaSurfaceActor and is being called right after we setup the animation (but before it actually starts, that happens at the next paint cycle) it will use a "very wrong" rectangle: The transformation has been set to move the actor back to its old position, and while we did already unfreeze updates and called clutter_actor_set_position() in meta_window_actor_sync_actor_geometry(), the actual allocation is not updated yet; this makes clutter_actor_get_transformed_position() return a position including in the new transformation, but not including the new allocation, and the rectangle ends up being moved to the next monitor or completely out of the stage. To fix this issue properly, we need to decouple unfreezing actor updates from emitting the "effects-completed" signal, which is now possible with the new meta_window_actor_freeze() and meta_window_actor_thaw() APIs. So use those new methods to freeze and thaw actor updates ourselves and make sure to call shellwm.completed_size_change() only after the animation has finished. Mutter MR: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1250 Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/513 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1251
2020-05-12 11:50:05 +00:00
actor.freeze();
if (this._clearAnimationInfo(actor)) {
log(`Old animationInfo removed from actor ${actor}`);
this._shellwm.completed_size_change(actor);
}
actor.connectObject('destroy',
() => this._clearAnimationInfo(actor), actorClone);
this._resizePending.add(actor);
windowManager: Use new mutter API to freeze window actors ourself We're currently using the hack of telling mutter that our effect is completed (even though it isn't) in order to unfreeze updates of the window actor. This causes a bug with detecting the wl_outputs a window is visible on, because the MetaWindowActor emits its "effects-completed" signal too early, making Mutter update the wl_outputs while we're doing the animation. Now since meta_wayland_actor_surface_is_on_logical_monitor() uses the transformed position and size of the MetaSurfaceActor and is being called right after we setup the animation (but before it actually starts, that happens at the next paint cycle) it will use a "very wrong" rectangle: The transformation has been set to move the actor back to its old position, and while we did already unfreeze updates and called clutter_actor_set_position() in meta_window_actor_sync_actor_geometry(), the actual allocation is not updated yet; this makes clutter_actor_get_transformed_position() return a position including in the new transformation, but not including the new allocation, and the rectangle ends up being moved to the next monitor or completely out of the stage. To fix this issue properly, we need to decouple unfreezing actor updates from emitting the "effects-completed" signal, which is now possible with the new meta_window_actor_freeze() and meta_window_actor_thaw() APIs. So use those new methods to freeze and thaw actor updates ourselves and make sure to call shellwm.completed_size_change() only after the animation has finished. Mutter MR: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1250 Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/513 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1251
2020-05-12 11:50:05 +00:00
actor.__animationInfo = {
clone: actorClone,
oldRect: oldFrameRect,
frozen: true,
};
}
_sizeChangedWindow(shellwm, actor) {
if (!actor.__animationInfo)
return;
if (this._resizing.has(actor))
return;
let actorClone = actor.__animationInfo.clone;
let targetRect = actor.meta_window.get_frame_rect();
let sourceRect = actor.__animationInfo.oldRect;
let scaleX = targetRect.width / sourceRect.width;
let scaleY = targetRect.height / sourceRect.height;
this._resizePending.delete(actor);
this._resizing.add(actor);
Main.uiGroup.add_child(actorClone);
// Now scale and fade out the clone
actorClone.ease({
x: targetRect.x,
y: targetRect.y,
scale_x: scaleX,
scale_y: scaleY,
opacity: 0,
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
actor.translation_x = -targetRect.x + sourceRect.x;
actor.translation_y = -targetRect.y + sourceRect.y;
// Now set scale the actor to size it as the clone.
actor.scale_x = 1 / scaleX;
actor.scale_y = 1 / scaleY;
// Scale it to its actual new size
actor.ease({
scale_x: 1,
scale_y: 1,
translation_x: 0,
translation_y: 0,
duration: WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => this._sizeChangeWindowDone(shellwm, actor),
});
// ease didn't animate and cleared the info, we are done
if (!actor.__animationInfo)
return;
// Now unfreeze actor updates, to get it to the new size.
// It's important that we don't wait until the animation is completed to
// do this, otherwise our scale will be applied to the old texture size.
windowManager: Use new mutter API to freeze window actors ourself We're currently using the hack of telling mutter that our effect is completed (even though it isn't) in order to unfreeze updates of the window actor. This causes a bug with detecting the wl_outputs a window is visible on, because the MetaWindowActor emits its "effects-completed" signal too early, making Mutter update the wl_outputs while we're doing the animation. Now since meta_wayland_actor_surface_is_on_logical_monitor() uses the transformed position and size of the MetaSurfaceActor and is being called right after we setup the animation (but before it actually starts, that happens at the next paint cycle) it will use a "very wrong" rectangle: The transformation has been set to move the actor back to its old position, and while we did already unfreeze updates and called clutter_actor_set_position() in meta_window_actor_sync_actor_geometry(), the actual allocation is not updated yet; this makes clutter_actor_get_transformed_position() return a position including in the new transformation, but not including the new allocation, and the rectangle ends up being moved to the next monitor or completely out of the stage. To fix this issue properly, we need to decouple unfreezing actor updates from emitting the "effects-completed" signal, which is now possible with the new meta_window_actor_freeze() and meta_window_actor_thaw() APIs. So use those new methods to freeze and thaw actor updates ourselves and make sure to call shellwm.completed_size_change() only after the animation has finished. Mutter MR: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1250 Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/513 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1251
2020-05-12 11:50:05 +00:00
actor.thaw();
actor.__animationInfo.frozen = false;
}
_clearAnimationInfo(actor) {
if (actor.__animationInfo) {
actor.__animationInfo.clone.destroy();
windowManager: Use new mutter API to freeze window actors ourself We're currently using the hack of telling mutter that our effect is completed (even though it isn't) in order to unfreeze updates of the window actor. This causes a bug with detecting the wl_outputs a window is visible on, because the MetaWindowActor emits its "effects-completed" signal too early, making Mutter update the wl_outputs while we're doing the animation. Now since meta_wayland_actor_surface_is_on_logical_monitor() uses the transformed position and size of the MetaSurfaceActor and is being called right after we setup the animation (but before it actually starts, that happens at the next paint cycle) it will use a "very wrong" rectangle: The transformation has been set to move the actor back to its old position, and while we did already unfreeze updates and called clutter_actor_set_position() in meta_window_actor_sync_actor_geometry(), the actual allocation is not updated yet; this makes clutter_actor_get_transformed_position() return a position including in the new transformation, but not including the new allocation, and the rectangle ends up being moved to the next monitor or completely out of the stage. To fix this issue properly, we need to decouple unfreezing actor updates from emitting the "effects-completed" signal, which is now possible with the new meta_window_actor_freeze() and meta_window_actor_thaw() APIs. So use those new methods to freeze and thaw actor updates ourselves and make sure to call shellwm.completed_size_change() only after the animation has finished. Mutter MR: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1250 Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/513 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1251
2020-05-12 11:50:05 +00:00
if (actor.__animationInfo.frozen)
actor.thaw();
delete actor.__animationInfo;
return true;
}
return false;
}
_sizeChangeWindowDone(shellwm, actor) {
if (this._resizing.delete(actor)) {
actor.remove_all_transitions();
actor.scale_x = 1.0;
actor.scale_y = 1.0;
actor.translation_x = 0;
actor.translation_y = 0;
this._clearAnimationInfo(actor);
windowManager: Use new mutter API to freeze window actors ourself We're currently using the hack of telling mutter that our effect is completed (even though it isn't) in order to unfreeze updates of the window actor. This causes a bug with detecting the wl_outputs a window is visible on, because the MetaWindowActor emits its "effects-completed" signal too early, making Mutter update the wl_outputs while we're doing the animation. Now since meta_wayland_actor_surface_is_on_logical_monitor() uses the transformed position and size of the MetaSurfaceActor and is being called right after we setup the animation (but before it actually starts, that happens at the next paint cycle) it will use a "very wrong" rectangle: The transformation has been set to move the actor back to its old position, and while we did already unfreeze updates and called clutter_actor_set_position() in meta_window_actor_sync_actor_geometry(), the actual allocation is not updated yet; this makes clutter_actor_get_transformed_position() return a position including in the new transformation, but not including the new allocation, and the rectangle ends up being moved to the next monitor or completely out of the stage. To fix this issue properly, we need to decouple unfreezing actor updates from emitting the "effects-completed" signal, which is now possible with the new meta_window_actor_freeze() and meta_window_actor_thaw() APIs. So use those new methods to freeze and thaw actor updates ourselves and make sure to call shellwm.completed_size_change() only after the animation has finished. Mutter MR: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1250 Fixes https://gitlab.gnome.org/GNOME/mutter/-/issues/513 https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1251
2020-05-12 11:50:05 +00:00
this._shellwm.completed_size_change(actor);
}
if (this._resizePending.delete(actor)) {
this._clearAnimationInfo(actor);
this._shellwm.completed_size_change(actor);
}
}
_checkDimming(window) {
const shouldDim = window.has_attached_dialogs();
if (shouldDim && !window._dimmed) {
window._dimmed = true;
this._dimmedWindows.push(window);
this._dimWindow(window);
} else if (!shouldDim && window._dimmed) {
window._dimmed = false;
this._dimmedWindows =
this._dimmedWindows.filter(win => win != window);
this._undimWindow(window);
}
}
_dimWindow(window) {
let actor = window.get_compositor_private();
if (!actor)
return;
let dimmer = getWindowDimmer(actor);
if (!dimmer)
return;
dimmer.setDimmed(true, this._shouldAnimate());
}
_undimWindow(window) {
let actor = window.get_compositor_private();
if (!actor)
return;
let dimmer = getWindowDimmer(actor);
if (!dimmer)
return;
dimmer.setDimmed(false, this._shouldAnimate());
}
_waitForOverviewToHide() {
if (!Main.overview.visible)
return Promise.resolve();
return new Promise(resolve => {
const id = Main.overview.connect('hidden', () => {
Main.overview.disconnect(id);
resolve();
});
});
}
async _mapWindow(shellwm, actor) {
actor._windowType = actor.meta_window.get_window_type();
actor.meta_window.connectObject('notify::window-type', () => {
let type = actor.meta_window.get_window_type();
if (type === actor._windowType)
return;
if (type === Meta.WindowType.MODAL_DIALOG ||
actor._windowType === Meta.WindowType.MODAL_DIALOG) {
let parent = actor.get_meta_window().get_transient_for();
if (parent)
this._checkDimming(parent);
}
actor._windowType = type;
}, actor);
actor.meta_window.connect('unmanaged', window => {
let parent = window.get_transient_for();
if (parent)
this._checkDimming(parent);
});
if (actor.meta_window.is_attached_dialog())
this._checkDimming(actor.get_meta_window().get_transient_for());
const types = [
Meta.WindowType.NORMAL,
Meta.WindowType.DIALOG,
Meta.WindowType.MODAL_DIALOG,
];
if (!this._shouldAnimateActor(actor, types)) {
shellwm.completed_map(actor);
return;
}
switch (actor._windowType) {
case Meta.WindowType.NORMAL:
actor.set_pivot_point(0.5, 1.0);
actor.scale_x = 0.01;
actor.scale_y = 0.05;
actor.opacity = 0;
actor.show();
this._mapping.add(actor);
await this._waitForOverviewToHide();
actor.ease({
opacity: 255,
scale_x: 1,
scale_y: 1,
duration: SHOW_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_EXPO,
onStopped: () => this._mapWindowDone(shellwm, actor),
});
break;
case Meta.WindowType.MODAL_DIALOG:
case Meta.WindowType.DIALOG:
actor.set_pivot_point(0.5, 0.5);
actor.scale_y = 0;
actor.opacity = 0;
actor.show();
this._mapping.add(actor);
await this._waitForOverviewToHide();
actor.ease({
opacity: 255,
scale_x: 1,
scale_y: 1,
duration: DIALOG_SHOW_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => this._mapWindowDone(shellwm, actor),
});
break;
default:
shellwm.completed_map(actor);
}
}
_mapWindowDone(shellwm, actor) {
if (this._mapping.delete(actor)) {
actor.remove_all_transitions();
actor.opacity = 255;
actor.set_pivot_point(0, 0);
actor.scale_y = 1;
actor.scale_x = 1;
actor.translation_y = 0;
actor.translation_x = 0;
shellwm.completed_map(actor);
}
}
_destroyWindow(shellwm, actor) {
let window = actor.meta_window;
window.disconnectObject(actor);
if (window._dimmed) {
this._dimmedWindows =
this._dimmedWindows.filter(win => win != window);
}
if (window.is_attached_dialog())
this._checkDimming(window.get_transient_for());
const types = [
Meta.WindowType.NORMAL,
Meta.WindowType.DIALOG,
Meta.WindowType.MODAL_DIALOG,
];
if (!this._shouldAnimateActor(actor, types)) {
shellwm.completed_destroy(actor);
return;
}
switch (actor.meta_window.window_type) {
case Meta.WindowType.NORMAL:
actor.set_pivot_point(0.5, 0.5);
this._destroying.add(actor);
actor.ease({
opacity: 0,
scale_x: 0.8,
scale_y: 0.8,
duration: DESTROY_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => this._destroyWindowDone(shellwm, actor),
});
break;
case Meta.WindowType.MODAL_DIALOG:
case Meta.WindowType.DIALOG:
actor.set_pivot_point(0.5, 0.5);
this._destroying.add(actor);
if (window.is_attached_dialog()) {
let parent = window.get_transient_for();
parent.connectObject('unmanaged', () => {
actor.remove_all_transitions();
this._destroyWindowDone(shellwm, actor);
}, actor);
}
actor.ease({
scale_y: 0,
duration: DIALOG_DESTROY_WINDOW_ANIMATION_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => this._destroyWindowDone(shellwm, actor),
});
break;
default:
shellwm.completed_destroy(actor);
}
}
_destroyWindowDone(shellwm, actor) {
if (this._destroying.delete(actor)) {
const parent = actor.get_meta_window()?.get_transient_for();
parent?.disconnectObject(actor);
shellwm.completed_destroy(actor);
}
}
_filterKeybinding(shellwm, binding) {
if (Main.actionMode == Shell.ActionMode.NONE)
return true;
// There's little sense in implementing a keybinding in mutter and
// not having it work in NORMAL mode; handle this case generically
// so we don't have to explicitly allow all builtin keybindings in
// NORMAL mode.
if (Main.actionMode == Shell.ActionMode.NORMAL &&
binding.is_builtin())
return false;
return !(this._allowedKeybindings[binding.get_name()] & Main.actionMode);
}
_switchWorkspace(shellwm, from, to, direction) {
if (!Main.sessionMode.hasWorkspaces || !this._shouldAnimate()) {
shellwm.completed_switch_workspace();
return;
}
this._switchInProgress = true;
this._workspaceAnimation.animateSwitch(from, to, direction, () => {
this._shellwm.completed_switch_workspace();
this._switchInProgress = false;
});
}
_switchWorkspaceDone() {
if (!this._switchInProgress)
return;
this._shellwm.completed_switch_workspace();
this._switchInProgress = false;
}
_showTilePreview(shellwm, window, tileRect, monitorIndex) {
if (!this._tilePreview)
this._tilePreview = new TilePreview();
this._tilePreview.open(window, tileRect, monitorIndex);
}
_hideTilePreview() {
if (!this._tilePreview)
return;
this._tilePreview.close();
}
_showWindowMenu(shellwm, window, menu, rect) {
this._windowMenuManager.showWindowMenuForWindow(window, menu, rect);
}
_startSwitcher(display, window, binding) {
let constructor = null;
switch (binding.get_name()) {
case 'switch-applications':
case 'switch-applications-backward':
case 'switch-group':
case 'switch-group-backward':
constructor = AltTab.AppSwitcherPopup;
break;
case 'switch-windows':
case 'switch-windows-backward':
constructor = AltTab.WindowSwitcherPopup;
break;
case 'cycle-windows':
case 'cycle-windows-backward':
constructor = AltTab.WindowCyclerPopup;
break;
case 'cycle-group':
case 'cycle-group-backward':
constructor = AltTab.GroupCyclerPopup;
break;
case 'switch-monitor':
constructor = SwitchMonitor.SwitchMonitorPopup;
break;
}
if (!constructor)
return;
/* prevent a corner case where both popups show up at once */
if (this._workspaceSwitcherPopup != null)
this._workspaceSwitcherPopup.destroy();
let tabPopup = new constructor();
if (!tabPopup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()))
tabPopup.destroy();
}
_startA11ySwitcher(display, window, binding) {
Main.ctrlAltTabManager.popup(binding.is_reversed(), binding.get_name(), binding.get_mask());
}
_allowFavoriteShortcuts() {
return Main.sessionMode.hasOverview;
}
_switchToApplication(display, window, binding) {
if (!this._allowFavoriteShortcuts())
return;
let [, , , target] = binding.get_name().split('-');
let apps = AppFavorites.getAppFavorites().getFavorites();
let app = apps[target - 1];
if (app) {
Main.overview.hide();
app.activate();
}
}
_toggleAppMenu() {
Main.panel.toggleAppMenu();
}
_toggleCalendar() {
Main.panel.toggleCalendar();
}
_showWorkspaceSwitcher(display, window, binding) {
let workspaceManager = display.get_workspace_manager();
if (!Main.sessionMode.hasWorkspaces)
return;
if (workspaceManager.n_workspaces == 1)
return;
let [action,,, target] = binding.get_name().split('-');
let newWs;
let direction;
let vertical = workspaceManager.layout_rows == -1;
let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
if (action == 'move') {
// "Moving" a window to another workspace doesn't make sense when
// it cannot be unstuck, and is potentially confusing if a new
// workspaces is added at the start/end
if (window.is_always_on_all_workspaces() ||
(Meta.prefs_get_workspaces_only_on_primary() &&
window.get_monitor() != Main.layoutManager.primaryIndex))
return;
}
if (target == 'last') {
if (vertical)
direction = Meta.MotionDirection.DOWN;
else if (rtl)
direction = Meta.MotionDirection.LEFT;
else
direction = Meta.MotionDirection.RIGHT;
newWs = workspaceManager.get_workspace_by_index(workspaceManager.n_workspaces - 1);
} else if (isNaN(target)) {
// Prepend a new workspace dynamically
let prependTarget;
if (vertical)
prependTarget = 'up';
else if (rtl)
prependTarget = 'right';
else
prependTarget = 'left';
if (workspaceManager.get_active_workspace_index() === 0 &&
action === 'move' && target === prependTarget &&
this._isWorkspacePrepended === false) {
this.insertWorkspace(0);
this._isWorkspacePrepended = true;
}
direction = Meta.MotionDirection[target.toUpperCase()];
newWs = workspaceManager.get_active_workspace().get_neighbor(direction);
} else if ((target > 0) && (target <= workspaceManager.n_workspaces)) {
target--;
newWs = workspaceManager.get_workspace_by_index(target);
if (workspaceManager.get_active_workspace().index() > target) {
if (vertical)
direction = Meta.MotionDirection.UP;
else if (rtl)
direction = Meta.MotionDirection.RIGHT;
else
direction = Meta.MotionDirection.LEFT;
} else {
if (vertical) // eslint-disable-line no-lonely-if
direction = Meta.MotionDirection.DOWN;
else if (rtl)
direction = Meta.MotionDirection.LEFT;
else
direction = Meta.MotionDirection.RIGHT;
}
}
if (workspaceManager.layout_rows == -1 &&
direction != Meta.MotionDirection.UP &&
direction != Meta.MotionDirection.DOWN)
return;
if (workspaceManager.layout_columns == -1 &&
direction != Meta.MotionDirection.LEFT &&
direction != Meta.MotionDirection.RIGHT)
return;
if (action == 'switch')
this.actionMoveWorkspace(newWs);
else
this.actionMoveWindow(window, newWs);
if (!Main.overview.visible) {
if (this._workspaceSwitcherPopup == null) {
this._workspaceTracker.blockUpdates();
this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
this._workspaceSwitcherPopup.connect('destroy', () => {
this._workspaceTracker.unblockUpdates();
this._workspaceSwitcherPopup = null;
this._isWorkspacePrepended = false;
});
}
this._workspaceSwitcherPopup.display(newWs.index());
}
}
actionMoveWorkspace(workspace) {
if (!Main.sessionMode.hasWorkspaces)
return;
if (!workspace.active)
workspace.activate(global.get_current_time());
}
actionMoveWindow(window, workspace) {
if (!Main.sessionMode.hasWorkspaces)
return;
if (!workspace.active) {
// This won't have any effect for "always sticky" windows
// (like desktop windows or docks)
this._workspaceAnimation.movingWindow = window;
window.change_workspace(workspace);
global.display.clear_mouse_mode();
workspace.activate_with_focus(window, global.get_current_time());
}
}
handleWorkspaceScroll(event) {
if (!this._canScroll)
return Clutter.EVENT_PROPAGATE;
if (event.type() !== Clutter.EventType.SCROLL)
return Clutter.EVENT_PROPAGATE;
const direction = event.get_scroll_direction();
if (direction === Clutter.ScrollDirection.SMOOTH)
return Clutter.EVENT_PROPAGATE;
const workspaceManager = global.workspace_manager;
const vertical = workspaceManager.layout_rows === -1;
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
const activeWs = workspaceManager.get_active_workspace();
let ws;
switch (direction) {
case Clutter.ScrollDirection.UP:
if (vertical)
ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
else if (rtl)
ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
else
ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
break;
case Clutter.ScrollDirection.DOWN:
if (vertical)
ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
else if (rtl)
ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
else
ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
break;
case Clutter.ScrollDirection.LEFT:
ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
break;
case Clutter.ScrollDirection.RIGHT:
ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
break;
default:
return Clutter.EVENT_PROPAGATE;
}
this.actionMoveWorkspace(ws);
this._canScroll = false;
GLib.timeout_add(GLib.PRIORITY_DEFAULT,
SCROLL_TIMEOUT_TIME, () => {
this._canScroll = true;
return GLib.SOURCE_REMOVE;
});
return Clutter.EVENT_STOP;
}
_confirmDisplayChange() {
let dialog = new DisplayChangeDialog(this._shellwm);
dialog.open();
}
_createCloseDialog(shellwm, window) {
return new CloseDialog.CloseDialog(window);
}
_createInhibitShortcutsDialog(shellwm, window) {
return new InhibitShortcutsDialog.InhibitShortcutsDialog(window);
}
_showResizePopup(display, show, rect, displayW, displayH) {
if (show) {
if (!this._resizePopup)
this._resizePopup = new ResizePopup();
this._resizePopup.set(rect, displayW, displayH);
} else {
if (!this._resizePopup)
return;
this._resizePopup.destroy();
this._resizePopup = null;
}
}
};