2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Atk from 'gi://Atk';
|
|
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import Gio from 'gi://Gio';
|
|
|
|
import GLib from 'gi://GLib';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Meta from 'gi://Meta';
|
|
|
|
import St from 'gi://St';
|
|
|
|
import Shell from 'gi://Shell';
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as Main from './main.js';
|
|
|
|
import * as SwitcherPopup from './switcherPopup.js';
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
2011-01-06 19:21:27 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const THUMBNAIL_DEFAULT_SIZE = 256;
|
|
|
|
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
|
|
|
const THUMBNAIL_FADE_TIME = 100; // milliseconds
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const WINDOW_PREVIEW_SIZE = 128;
|
|
|
|
const APP_ICON_SIZE = 96;
|
|
|
|
const APP_ICON_SIZE_SMALL = 48;
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2014-02-16 04:25:49 +00:00
|
|
|
const baseIconSizes = [96, 64, 48, 32, 22];
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2023-07-30 12:56:59 +00:00
|
|
|
/** @enum {number} */
|
2023-07-10 09:53:00 +00:00
|
|
|
const AppIconMode = {
|
2012-11-23 02:20:10 +00:00
|
|
|
THUMBNAIL_ONLY: 1,
|
|
|
|
APP_ICON_ONLY: 2,
|
|
|
|
BOTH: 3,
|
|
|
|
};
|
|
|
|
|
2012-12-04 15:26:00 +00:00
|
|
|
function _createWindowClone(window, size) {
|
2017-02-01 18:01:47 +00:00
|
|
|
let [width, height] = window.get_size();
|
2012-12-04 15:26:00 +00:00
|
|
|
let scale = Math.min(1.0, size / width, size / height);
|
2020-03-29 21:51:13 +00:00
|
|
|
return new Clutter.Clone({
|
|
|
|
source: window,
|
|
|
|
width: width * scale,
|
|
|
|
height: height * scale,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
// usual hack for the usual bug in ClutterBinLayout...
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
});
|
2019-01-29 01:18:52 +00:00
|
|
|
}
|
2012-12-04 15:26:00 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
/**
|
|
|
|
* @param {Meta.Workspace} workspace
|
|
|
|
* @returns {Meta.Window}
|
|
|
|
*/
|
2016-10-06 21:32:08 +00:00
|
|
|
function getWindows(workspace) {
|
|
|
|
// We ignore skip-taskbar windows in switchers, but if they are attached
|
|
|
|
// to their parent, their position in the MRU list may be more appropriate
|
|
|
|
// than the parent; so start with the complete list ...
|
2023-07-10 09:53:00 +00:00
|
|
|
let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace);
|
2016-10-06 21:32:08 +00:00
|
|
|
// ... map windows to their parent where appropriate ...
|
|
|
|
return windows.map(w => {
|
|
|
|
return w.is_attached_dialog() ? w.get_transient_for() : w;
|
|
|
|
// ... and filter out skip-taskbar windows and duplicates
|
2023-07-10 09:53:00 +00:00
|
|
|
}).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) === i);
|
2016-10-06 21:32:08 +00:00
|
|
|
}
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const AppSwitcherPopup = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init() {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init();
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2012-11-30 15:16:48 +00:00
|
|
|
this._thumbnails = null;
|
2009-10-02 15:02:46 +00:00
|
|
|
this._thumbnailTimeoutId = 0;
|
2012-11-30 15:16:48 +00:00
|
|
|
this._currentWindow = -1;
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2011-01-06 19:21:27 +00:00
|
|
|
this.thumbnailsVisible = false;
|
2014-09-03 15:15:31 +00:00
|
|
|
|
2019-08-19 17:55:49 +00:00
|
|
|
let apps = Shell.AppSystem.get_default().get_running();
|
2014-09-03 15:15:31 +00:00
|
|
|
|
|
|
|
this._switcherList = new AppSwitcher(apps, this);
|
|
|
|
this._items = this._switcherList.icons;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2020-05-09 19:30:26 +00:00
|
|
|
vfunc_allocate(box) {
|
|
|
|
super.vfunc_allocate(box);
|
2010-03-17 17:31:03 +00:00
|
|
|
|
|
|
|
// Allocate the thumbnails
|
|
|
|
// We try to avoid overflowing the screen so we base the resulting size on
|
|
|
|
// those calculations
|
|
|
|
if (this._thumbnails) {
|
2018-06-28 15:34:01 +00:00
|
|
|
let childBox = this._switcherList.get_allocation_box();
|
2012-11-30 15:16:48 +00:00
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
|
|
|
|
2018-08-13 23:14:55 +00:00
|
|
|
let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
|
|
|
|
let rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT);
|
|
|
|
let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM);
|
2012-11-30 15:16:48 +00:00
|
|
|
let hPadding = leftPadding + rightPadding;
|
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
let icon = this._items[this._selectedIndex];
|
2019-02-01 13:41:55 +00:00
|
|
|
let [posX] = icon.get_transformed_position();
|
2010-03-17 17:31:03 +00:00
|
|
|
let thumbnailCenter = posX + icon.width / 2;
|
2019-02-01 13:41:55 +00:00
|
|
|
let [, childNaturalWidth] = this._thumbnails.get_preferred_width(-1);
|
2010-05-27 17:52:49 +00:00
|
|
|
childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
|
|
|
|
if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
|
|
|
|
let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
|
|
|
|
childBox.x1 = Math.max(primary.x + leftPadding, childBox.x1 - offset - hPadding);
|
2010-03-17 17:31:03 +00:00
|
|
|
}
|
|
|
|
|
2018-08-13 23:14:55 +00:00
|
|
|
let spacing = this.get_theme_node().get_length('spacing');
|
2010-03-26 16:47:12 +00:00
|
|
|
|
2010-03-17 17:31:03 +00:00
|
|
|
childBox.x2 = childBox.x1 + childNaturalWidth;
|
2010-05-27 17:52:49 +00:00
|
|
|
if (childBox.x2 > primary.x + primary.width - rightPadding)
|
|
|
|
childBox.x2 = primary.x + primary.width - rightPadding;
|
2018-06-28 15:34:01 +00:00
|
|
|
childBox.y1 = this._switcherList.allocation.y2 + spacing;
|
2012-02-23 18:38:01 +00:00
|
|
|
this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1);
|
2019-02-01 13:41:55 +00:00
|
|
|
let [, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
|
2010-03-17 17:31:03 +00:00
|
|
|
childBox.y2 = childBox.y1 + childNaturalHeight;
|
2020-05-09 19:30:26 +00:00
|
|
|
this._thumbnails.allocate(childBox);
|
2010-03-17 17:31:03 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_initialSelection(backward, binding) {
|
2011-06-27 01:54:26 +00:00
|
|
|
if (binding == 'switch-group') {
|
2019-08-20 02:10:46 +00:00
|
|
|
if (backward)
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(0, this._items[0].cachedWindows.length - 1);
|
2019-08-20 02:10:46 +00:00
|
|
|
else if (this._items[0].cachedWindows.length > 1)
|
|
|
|
this._select(0, 1);
|
|
|
|
else
|
|
|
|
this._select(0, 0);
|
2011-06-27 01:54:26 +00:00
|
|
|
} else if (binding == 'switch-group-backward') {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(0, this._items[0].cachedWindows.length - 1);
|
2012-11-23 00:18:06 +00:00
|
|
|
} else if (binding == 'switch-applications-backward') {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._items.length - 1);
|
|
|
|
} else if (this._items.length == 1) {
|
2011-02-23 10:52:25 +00:00
|
|
|
this._select(0);
|
2009-10-02 15:02:46 +00:00
|
|
|
} else if (backward) {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._items.length - 1);
|
2009-10-02 15:02:46 +00:00
|
|
|
} else {
|
2011-02-23 10:52:25 +00:00
|
|
|
this._select(1);
|
2009-10-01 09:23:00 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-09-29 17:29:17 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_nextWindow() {
|
2010-05-06 17:36:01 +00:00
|
|
|
// We actually want the second window if we're in the unset state
|
|
|
|
if (this._currentWindow == -1)
|
|
|
|
this._currentWindow = 0;
|
2012-11-30 15:16:48 +00:00
|
|
|
return SwitcherPopup.mod(this._currentWindow + 1,
|
|
|
|
this._items[this._selectedIndex].cachedWindows.length);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_previousWindow() {
|
2010-05-06 17:36:01 +00:00
|
|
|
// Also assume second window here
|
|
|
|
if (this._currentWindow == -1)
|
|
|
|
this._currentWindow = 1;
|
2012-11-30 15:16:48 +00:00
|
|
|
return SwitcherPopup.mod(this._currentWindow - 1,
|
|
|
|
this._items[this._selectedIndex].cachedWindows.length);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_closeAppWindow(appIndex, windowIndex) {
|
2017-12-05 10:02:23 +00:00
|
|
|
let appIcon = this._items[appIndex];
|
|
|
|
if (!appIcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let window = appIcon.cachedWindows[windowIndex];
|
|
|
|
if (!window)
|
|
|
|
return;
|
|
|
|
|
|
|
|
window.delete(global.get_current_time());
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_quitApplication(appIndex) {
|
2010-06-01 18:03:45 +00:00
|
|
|
let appIcon = this._items[appIndex];
|
|
|
|
if (!appIcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
appIcon.app.request_quit();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-06-01 18:03:45 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyPressHandler(keysym, action) {
|
2021-05-07 11:26:21 +00:00
|
|
|
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
|
2012-11-30 15:16:48 +00:00
|
|
|
if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
|
2017-09-14 07:30:16 +00:00
|
|
|
if (!this._thumbnailsFocused)
|
|
|
|
this._select(this._selectedIndex, 0);
|
|
|
|
else
|
|
|
|
this._select(this._selectedIndex, this._nextWindow());
|
2011-07-05 22:05:45 +00:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, this._previousWindow());
|
2012-11-23 00:18:06 +00:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS) {
|
2014-08-12 15:55:22 +00:00
|
|
|
this._select(this._next());
|
2012-11-23 00:18:06 +00:00
|
|
|
} else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD) {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._previous());
|
2018-05-21 08:32:52 +00:00
|
|
|
} else if (keysym == Clutter.KEY_q || keysym === Clutter.KEY_Q) {
|
2010-06-01 18:03:45 +00:00
|
|
|
this._quitApplication(this._selectedIndex);
|
2011-02-23 10:52:25 +00:00
|
|
|
} else if (this._thumbnailsFocused) {
|
2019-11-05 19:37:28 +00:00
|
|
|
if (keysym === Clutter.KEY_Left)
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(this._selectedIndex, rtl ? this._nextWindow() : this._previousWindow());
|
2019-11-05 19:37:28 +00:00
|
|
|
else if (keysym === Clutter.KEY_Right)
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(this._selectedIndex, rtl ? this._previousWindow() : this._nextWindow());
|
2019-11-05 19:37:28 +00:00
|
|
|
else if (keysym === Clutter.KEY_Up)
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, null, true);
|
2018-05-21 08:32:52 +00:00
|
|
|
else if (keysym === Clutter.KEY_w || keysym === Clutter.KEY_W || keysym === Clutter.KEY_F4)
|
2017-12-05 10:02:23 +00:00
|
|
|
this._closeAppWindow(this._selectedIndex, this._currentWindow);
|
2014-05-26 13:27:16 +00:00
|
|
|
else
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2019-08-20 02:10:46 +00:00
|
|
|
} else if (keysym == Clutter.KEY_Left) {
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(rtl ? this._next() : this._previous());
|
2019-08-20 02:10:46 +00:00
|
|
|
} else if (keysym == Clutter.KEY_Right) {
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(rtl ? this._previous() : this._next());
|
2019-08-20 02:10:46 +00:00
|
|
|
} else if (keysym == Clutter.KEY_Down) {
|
|
|
|
this._select(this._selectedIndex, 0);
|
2009-10-02 15:02:46 +00:00
|
|
|
} else {
|
2019-08-20 02:10:46 +00:00
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2009-09-26 14:56:41 +00:00
|
|
|
}
|
2014-05-26 13:27:16 +00:00
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_scrollHandler(direction) {
|
2009-10-09 13:43:31 +00:00
|
|
|
if (direction == Clutter.ScrollDirection.UP) {
|
|
|
|
if (this._thumbnailsFocused) {
|
2010-05-06 17:36:01 +00:00
|
|
|
if (this._currentWindow == 0 || this._currentWindow == -1)
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._previous());
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, this._previousWindow());
|
2009-10-09 13:43:31 +00:00
|
|
|
} else {
|
2012-11-30 15:16:48 +00:00
|
|
|
let nwindows = this._items[this._selectedIndex].cachedWindows.length;
|
2009-10-09 13:43:31 +00:00
|
|
|
if (nwindows > 1)
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, nwindows - 1);
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._previous());
|
2009-10-09 13:43:31 +00:00
|
|
|
}
|
|
|
|
} else if (direction == Clutter.ScrollDirection.DOWN) {
|
|
|
|
if (this._thumbnailsFocused) {
|
2012-11-30 15:16:48 +00:00
|
|
|
if (this._currentWindow == this._items[this._selectedIndex].cachedWindows.length - 1)
|
|
|
|
this._select(this._next());
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, this._nextWindow());
|
2009-10-09 13:43:31 +00:00
|
|
|
} else {
|
2012-11-30 15:16:48 +00:00
|
|
|
let nwindows = this._items[this._selectedIndex].cachedWindows.length;
|
2009-10-09 13:43:31 +00:00
|
|
|
if (nwindows > 1)
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, 0);
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._next());
|
2009-10-09 13:43:31 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_itemActivatedHandler(n) {
|
2009-10-09 13:43:31 +00:00
|
|
|
// If the user clicks on the selected app, activate the
|
|
|
|
// selected window; otherwise (eg, they click on an app while
|
2012-11-23 03:39:19 +00:00
|
|
|
// !mouseActive) activate the clicked-on app.
|
2012-11-30 15:16:48 +00:00
|
|
|
if (n == this._selectedIndex && this._currentWindow >= 0)
|
|
|
|
this._select(n, this._currentWindow);
|
2012-11-23 03:39:19 +00:00
|
|
|
else
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(n);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-05-05 13:32:16 +00:00
|
|
|
_windowActivated(thumbnailSwitcher, n) {
|
2012-11-30 15:16:48 +00:00
|
|
|
let appIcon = this._items[this._selectedIndex];
|
2010-05-06 17:36:01 +00:00
|
|
|
Main.activateWindow(appIcon.cachedWindows[n]);
|
2018-08-13 23:05:50 +00:00
|
|
|
this.fadeAndDestroy();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-05-05 13:32:16 +00:00
|
|
|
_windowEntered(thumbnailSwitcher, n) {
|
2012-11-30 15:16:48 +00:00
|
|
|
if (!this.mouseActive)
|
2009-10-02 15:02:46 +00:00
|
|
|
return;
|
|
|
|
|
2012-11-30 15:16:48 +00:00
|
|
|
this._select(this._selectedIndex, n);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-05-05 13:32:16 +00:00
|
|
|
_windowRemoved(thumbnailSwitcher, n) {
|
2017-12-05 10:02:23 +00:00
|
|
|
let appIcon = this._items[this._selectedIndex];
|
|
|
|
if (!appIcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (appIcon.cachedWindows.length > 0) {
|
|
|
|
let newIndex = Math.min(n, appIcon.cachedWindows.length - 1);
|
|
|
|
this._select(this._selectedIndex, newIndex);
|
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_finish(timestamp) {
|
2012-11-30 15:16:48 +00:00
|
|
|
let appIcon = this._items[this._selectedIndex];
|
2013-04-07 14:42:42 +00:00
|
|
|
if (this._currentWindow < 0)
|
2013-05-15 06:37:12 +00:00
|
|
|
appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp);
|
2018-02-08 10:38:04 +00:00
|
|
|
else if (appIcon.cachedWindows[this._currentWindow])
|
2013-04-07 14:42:42 +00:00
|
|
|
Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);
|
2013-03-22 15:21:18 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
super._finish(timestamp);
|
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2009-10-02 15:02:46 +00:00
|
|
|
if (this._thumbnailTimeoutId != 0)
|
2019-08-19 18:50:33 +00:00
|
|
|
GLib.source_remove(this._thumbnailTimeoutId);
|
2020-01-27 22:42:28 +00:00
|
|
|
|
|
|
|
super._onDestroy();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
/**
|
|
|
|
* _select:
|
|
|
|
*
|
2023-07-30 12:56:59 +00:00
|
|
|
* @param {number} app index of the app to select
|
|
|
|
* @param {number} [window] index of which of `app`'s windows to select
|
|
|
|
* @param {boolean} [forceAppFocus] optional flag, see below
|
|
|
|
*
|
|
|
|
* Selects the indicated `app`, and optional `window`, and sets
|
2009-10-09 13:43:31 +00:00
|
|
|
* this._thumbnailsFocused appropriately to indicate whether the
|
|
|
|
* arrow keys should act on the app list or the thumbnail list.
|
|
|
|
*
|
2023-07-30 12:56:59 +00:00
|
|
|
* If `app` is specified and `window` is unspecified or %null, then
|
2009-10-09 13:43:31 +00:00
|
|
|
* the app is highlighted (ie, given a light background), and the
|
2023-07-30 12:56:59 +00:00
|
|
|
* current thumbnail list, if any, is destroyed. If `app` has
|
|
|
|
* multiple windows, and `forceAppFocus` is not %true, then a
|
2009-10-09 13:43:31 +00:00
|
|
|
* timeout is started to open a thumbnail list.
|
|
|
|
*
|
2023-07-30 12:56:59 +00:00
|
|
|
* If `app` and `window` are specified (and `forceAppFocus` is not),
|
|
|
|
* then `app` will be outlined, a thumbnail list will be created
|
|
|
|
* and focused (if it hasn't been already), and the `window`'th
|
2009-10-09 13:43:31 +00:00
|
|
|
* window in it will be highlighted.
|
|
|
|
*
|
2023-07-30 12:56:59 +00:00
|
|
|
* If `app` and `window` are specified and `forceAppFocus` is %true,
|
|
|
|
* then `app` will be highlighted, and `window` outlined, and the
|
2009-10-09 13:43:31 +00:00
|
|
|
* app list will have the keyboard focus.
|
|
|
|
*/
|
2017-10-31 00:03:21 +00:00
|
|
|
_select(app, window, forceAppFocus) {
|
2012-11-30 15:16:48 +00:00
|
|
|
if (app != this._selectedIndex || window == null) {
|
2009-10-09 13:43:31 +00:00
|
|
|
if (this._thumbnails)
|
|
|
|
this._destroyThumbnails();
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (this._thumbnailTimeoutId != 0) {
|
2019-08-19 18:50:33 +00:00
|
|
|
GLib.source_remove(this._thumbnailTimeoutId);
|
2009-10-02 15:02:46 +00:00
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
|
|
|
|
2012-11-30 15:16:48 +00:00
|
|
|
this._selectedIndex = app;
|
2010-05-06 17:36:01 +00:00
|
|
|
this._currentWindow = window ? window : -1;
|
2012-11-30 15:16:48 +00:00
|
|
|
this._switcherList.highlight(app, this._thumbnailsFocused);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
|
|
|
if (window != null) {
|
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
2009-10-09 13:43:31 +00:00
|
|
|
this._currentWindow = window;
|
|
|
|
this._thumbnails.highlight(window, forceAppFocus);
|
2012-11-30 15:16:48 +00:00
|
|
|
} else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
|
2009-10-09 13:43:31 +00:00
|
|
|
!forceAppFocus) {
|
2019-08-19 18:50:33 +00:00
|
|
|
this._thumbnailTimeoutId = GLib.timeout_add(
|
|
|
|
GLib.PRIORITY_DEFAULT,
|
2009-10-02 15:02:46 +00:00
|
|
|
THUMBNAIL_POPUP_TIME,
|
2017-12-02 00:27:35 +00:00
|
|
|
this._timeoutPopupThumbnails.bind(this));
|
2014-04-10 17:26:52 +00:00
|
|
|
GLib.Source.set_name_by_id(this._thumbnailTimeoutId, '[gnome-shell] this._timeoutPopupThumbnails');
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_timeoutPopupThumbnails() {
|
2010-05-06 17:36:01 +00:00
|
|
|
if (!this._thumbnails)
|
|
|
|
this._createThumbnails();
|
|
|
|
this._thumbnailTimeoutId = 0;
|
|
|
|
this._thumbnailsFocused = false;
|
2013-11-29 00:45:39 +00:00
|
|
|
return GLib.SOURCE_REMOVE;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-05-06 17:36:01 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_destroyThumbnails() {
|
2018-06-28 15:34:01 +00:00
|
|
|
let thumbnailsActor = this._thumbnails;
|
2018-07-20 19:46:19 +00:00
|
|
|
this._thumbnails.ease({
|
|
|
|
opacity: 0,
|
|
|
|
duration: THUMBNAIL_FADE_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
|
|
|
thumbnailsActor.destroy();
|
|
|
|
this.thumbnailsVisible = false;
|
2019-08-20 21:43:54 +00:00
|
|
|
},
|
2018-07-20 19:46:19 +00:00
|
|
|
});
|
2009-10-09 13:43:31 +00:00
|
|
|
this._thumbnails = null;
|
2018-04-26 09:19:40 +00:00
|
|
|
this._switcherList.removeAccessibleState(this._selectedIndex, Atk.StateType.EXPANDED);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createThumbnails() {
|
2018-05-05 13:32:16 +00:00
|
|
|
this._thumbnails = new ThumbnailSwitcher(this._items[this._selectedIndex].cachedWindows);
|
2017-12-02 00:27:35 +00:00
|
|
|
this._thumbnails.connect('item-activated', this._windowActivated.bind(this));
|
|
|
|
this._thumbnails.connect('item-entered', this._windowEntered.bind(this));
|
|
|
|
this._thumbnails.connect('item-removed', this._windowRemoved.bind(this));
|
2018-06-28 15:34:01 +00:00
|
|
|
this._thumbnails.connect('destroy', () => {
|
2018-02-08 10:38:04 +00:00
|
|
|
this._thumbnails = null;
|
|
|
|
this._thumbnailsFocused = false;
|
|
|
|
});
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this.add_actor(this._thumbnails);
|
2011-07-21 17:58:43 +00:00
|
|
|
|
|
|
|
// Need to force an allocation so we can figure out whether we
|
|
|
|
// need to scroll when selecting
|
2018-06-28 15:34:01 +00:00
|
|
|
this._thumbnails.get_allocation_box();
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this._thumbnails.opacity = 0;
|
2018-07-20 19:46:19 +00:00
|
|
|
this._thumbnails.ease({
|
|
|
|
opacity: 255,
|
|
|
|
duration: THUMBNAIL_FADE_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
|
|
|
this.thumbnailsVisible = true;
|
2019-08-20 21:43:54 +00:00
|
|
|
},
|
2018-07-20 19:46:19 +00:00
|
|
|
});
|
2012-03-07 18:29:13 +00:00
|
|
|
|
2018-04-26 09:19:40 +00:00
|
|
|
this._switcherList.addAccessibleState(this._selectedIndex, Atk.StateType.EXPANDED);
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const CyclerHighlight = GObject.registerClass(
|
2019-07-16 09:24:13 +00:00
|
|
|
class CyclerHighlight extends St.Widget {
|
|
|
|
_init() {
|
2023-08-06 22:40:20 +00:00
|
|
|
super._init({layout_manager: new Clutter.BinLayout()});
|
2016-09-08 14:05:06 +00:00
|
|
|
this._window = null;
|
|
|
|
|
|
|
|
this._clone = new Clutter.Clone();
|
2019-07-16 09:24:13 +00:00
|
|
|
this.add_actor(this._clone);
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
this._highlight = new St.Widget({style_class: 'cycler-highlight'});
|
2019-07-16 09:24:13 +00:00
|
|
|
this.add_actor(this._highlight);
|
2016-09-08 14:05:06 +00:00
|
|
|
|
|
|
|
let coordinate = Clutter.BindCoordinate.ALL;
|
2023-08-06 22:40:20 +00:00
|
|
|
let constraint = new Clutter.BindConstraint({coordinate});
|
2016-09-08 14:05:06 +00:00
|
|
|
this._clone.bind_property('source', constraint, 'source', 0);
|
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.add_constraint(constraint);
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-09-08 14:05:06 +00:00
|
|
|
|
|
|
|
set window(w) {
|
|
|
|
if (this._window == w)
|
|
|
|
return;
|
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
this._window?.disconnectObject(this);
|
2021-05-11 14:43:37 +00:00
|
|
|
|
2016-09-08 14:05:06 +00:00
|
|
|
this._window = w;
|
|
|
|
|
|
|
|
if (this._clone.source)
|
2016-09-13 19:18:17 +00:00
|
|
|
this._clone.source.sync_visibility();
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2021-04-23 14:10:52 +00:00
|
|
|
const windowActor = this._window?.get_compositor_private() ?? null;
|
2016-09-08 14:05:06 +00:00
|
|
|
|
|
|
|
if (windowActor)
|
|
|
|
windowActor.hide();
|
|
|
|
|
|
|
|
this._clone.source = windowActor;
|
|
|
|
|
2021-05-11 14:43:37 +00:00
|
|
|
if (this._window) {
|
|
|
|
this._onSizeChanged();
|
2021-08-15 22:36:59 +00:00
|
|
|
this._window.connectObject('size-changed',
|
|
|
|
this._onSizeChanged.bind(this), this);
|
2021-05-11 14:43:37 +00:00
|
|
|
} else {
|
2016-09-08 14:05:06 +00:00
|
|
|
this._highlight.set_size(0, 0);
|
|
|
|
this._highlight.hide();
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2021-05-11 14:43:37 +00:00
|
|
|
_onSizeChanged() {
|
|
|
|
const bufferRect = this._window.get_buffer_rect();
|
|
|
|
const rect = this._window.get_frame_rect();
|
|
|
|
this._highlight.set_size(rect.width, rect.height);
|
|
|
|
this._highlight.set_position(
|
|
|
|
rect.x - bufferRect.x,
|
|
|
|
rect.y - bufferRect.y);
|
|
|
|
this._highlight.show();
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2016-09-08 14:05:06 +00:00
|
|
|
this.window = null;
|
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
// We don't show an actual popup, so just provide what SwitcherPopup
|
|
|
|
// expects instead of inheriting from SwitcherList
|
2023-07-10 09:53:00 +00:00
|
|
|
const CyclerList = GObject.registerClass({
|
2020-03-29 21:51:13 +00:00
|
|
|
Signals: {
|
2023-08-06 22:40:20 +00:00
|
|
|
'item-activated': {param_types: [GObject.TYPE_INT]},
|
|
|
|
'item-entered': {param_types: [GObject.TYPE_INT]},
|
|
|
|
'item-removed': {param_types: [GObject.TYPE_INT]},
|
|
|
|
'item-highlighted': {param_types: [GObject.TYPE_INT]},
|
2020-03-29 21:51:13 +00:00
|
|
|
},
|
2017-10-31 01:23:39 +00:00
|
|
|
}, class CyclerList extends St.Widget {
|
2019-01-31 14:08:10 +00:00
|
|
|
highlight(index, _justOutline) {
|
2018-06-28 15:34:01 +00:00
|
|
|
this.emit('item-highlighted', index);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const CyclerPopup = GObject.registerClass({
|
2019-08-20 21:43:54 +00:00
|
|
|
GTypeFlags: GObject.TypeFlags.ABSTRACT,
|
2019-04-27 18:45:14 +00:00
|
|
|
}, class CyclerPopup extends SwitcherPopup.SwitcherPopup {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init() {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init();
|
2016-05-19 12:41:24 +00:00
|
|
|
|
|
|
|
this._items = this._getWindows();
|
|
|
|
|
2016-09-08 14:05:06 +00:00
|
|
|
this._highlight = new CyclerHighlight();
|
2019-07-16 09:24:13 +00:00
|
|
|
global.window_group.add_actor(this._highlight);
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this._switcherList = new CyclerList();
|
|
|
|
this._switcherList.connect('item-highlighted', (list, index) => {
|
|
|
|
this._highlightItem(index);
|
|
|
|
});
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2019-01-31 14:08:10 +00:00
|
|
|
_highlightItem(index, _justOutline) {
|
2016-09-08 14:05:06 +00:00
|
|
|
this._highlight.window = this._items[index];
|
2019-07-16 09:24:13 +00:00
|
|
|
global.window_group.set_child_above_sibling(this._highlight, null);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_finish() {
|
2016-09-14 20:44:12 +00:00
|
|
|
let window = this._items[this._selectedIndex];
|
|
|
|
let ws = window.get_workspace();
|
2018-01-03 07:55:38 +00:00
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
let activeWs = workspaceManager.get_active_workspace();
|
2016-09-14 20:44:12 +00:00
|
|
|
|
2016-09-15 15:41:52 +00:00
|
|
|
if (window.minimized) {
|
|
|
|
Main.wm.skipNextEffect(window.get_compositor_private());
|
|
|
|
window.unminimize();
|
|
|
|
}
|
|
|
|
|
2016-09-14 20:44:12 +00:00
|
|
|
if (activeWs == ws) {
|
|
|
|
Main.activateWindow(window);
|
|
|
|
} else {
|
|
|
|
// If the selected window is on a different workspace, we don't
|
|
|
|
// want it to disappear, then slide in with the workspace; instead,
|
|
|
|
// always activate it on the active workspace ...
|
|
|
|
activeWs.activate_with_focus(window, global.get_current_time());
|
|
|
|
|
|
|
|
// ... then slide it over to the original workspace if necessary
|
|
|
|
Main.wm.actionMoveWindow(window, ws);
|
|
|
|
}
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
super._finish();
|
|
|
|
}
|
2016-09-08 14:05:06 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2019-07-16 09:24:13 +00:00
|
|
|
this._highlight.destroy();
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
super._onDestroy();
|
2016-05-19 12:41:24 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const GroupCyclerPopup = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class GroupCyclerPopup extends CyclerPopup {
|
2020-09-03 07:51:44 +00:00
|
|
|
_init() {
|
2023-08-06 22:40:20 +00:00
|
|
|
this._settings = new Gio.Settings({schema_id: 'org.gnome.shell.app-switcher'});
|
2020-09-03 07:51:44 +00:00
|
|
|
super._init();
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_getWindows() {
|
2016-05-19 12:41:24 +00:00
|
|
|
let app = Shell.WindowTracker.get_default().focus_app;
|
2020-08-12 18:59:01 +00:00
|
|
|
let appWindows = app?.get_windows() ?? [];
|
2020-09-03 07:51:44 +00:00
|
|
|
|
|
|
|
if (this._settings.get_boolean('current-workspace-only')) {
|
|
|
|
const workspaceManager = global.workspace_manager;
|
|
|
|
const workspace = workspaceManager.get_active_workspace();
|
|
|
|
appWindows = appWindows.filter(
|
|
|
|
window => window.located_on_workspace(workspace));
|
|
|
|
}
|
|
|
|
|
|
|
|
return appWindows;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyPressHandler(keysym, action) {
|
2016-05-19 12:41:24 +00:00
|
|
|
if (action == Meta.KeyBindingAction.CYCLE_GROUP)
|
|
|
|
this._select(this._next());
|
|
|
|
else if (action == Meta.KeyBindingAction.CYCLE_GROUP_BACKWARD)
|
|
|
|
this._select(this._previous());
|
|
|
|
else
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const WindowSwitcherPopup = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class WindowSwitcherPopup extends SwitcherPopup.SwitcherPopup {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init() {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init();
|
2023-08-06 22:40:20 +00:00
|
|
|
this._settings = new Gio.Settings({schema_id: 'org.gnome.shell.window-switcher'});
|
2013-09-09 20:53:44 +00:00
|
|
|
|
2012-11-23 02:20:10 +00:00
|
|
|
let windows = this._getWindowList();
|
|
|
|
|
2013-09-09 20:53:44 +00:00
|
|
|
let mode = this._settings.get_enum('app-icon-mode');
|
2018-05-05 13:32:16 +00:00
|
|
|
this._switcherList = new WindowSwitcher(windows, mode);
|
2012-11-23 02:20:10 +00:00
|
|
|
this._items = this._switcherList.icons;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_getWindowList() {
|
2018-01-03 07:55:38 +00:00
|
|
|
let workspace = null;
|
|
|
|
|
|
|
|
if (this._settings.get_boolean('current-workspace-only')) {
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
|
|
|
|
workspace = workspaceManager.get_active_workspace();
|
|
|
|
}
|
|
|
|
|
2016-10-06 21:32:08 +00:00
|
|
|
return getWindows(workspace);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_closeWindow(windowIndex) {
|
2017-12-05 10:02:23 +00:00
|
|
|
let windowIcon = this._items[windowIndex];
|
|
|
|
if (!windowIcon)
|
|
|
|
return;
|
|
|
|
|
|
|
|
windowIcon.window.delete(global.get_current_time());
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyPressHandler(keysym, action) {
|
2021-05-07 11:26:21 +00:00
|
|
|
const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
|
2019-08-20 02:10:46 +00:00
|
|
|
if (action == Meta.KeyBindingAction.SWITCH_WINDOWS)
|
2014-08-18 17:31:31 +00:00
|
|
|
this._select(this._next());
|
2019-08-20 02:10:46 +00:00
|
|
|
else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD)
|
2012-11-23 02:20:10 +00:00
|
|
|
this._select(this._previous());
|
2019-08-20 02:10:46 +00:00
|
|
|
else if (keysym == Clutter.KEY_Left)
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(rtl ? this._next() : this._previous());
|
2019-08-20 02:10:46 +00:00
|
|
|
else if (keysym == Clutter.KEY_Right)
|
2021-05-07 11:26:21 +00:00
|
|
|
this._select(rtl ? this._previous() : this._next());
|
2018-05-21 08:32:52 +00:00
|
|
|
else if (keysym === Clutter.KEY_w || keysym === Clutter.KEY_W || keysym === Clutter.KEY_F4)
|
2019-08-20 02:10:46 +00:00
|
|
|
this._closeWindow(this._selectedIndex);
|
|
|
|
else
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2014-05-26 13:27:16 +00:00
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_finish() {
|
2012-11-23 02:20:10 +00:00
|
|
|
Main.activateWindow(this._items[this._selectedIndex].window);
|
2013-03-22 15:21:18 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
super._finish();
|
2012-11-23 02:20:10 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const WindowCyclerPopup = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class WindowCyclerPopup extends CyclerPopup {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init() {
|
2023-08-06 22:40:20 +00:00
|
|
|
this._settings = new Gio.Settings({schema_id: 'org.gnome.shell.window-switcher'});
|
2019-03-16 22:22:29 +00:00
|
|
|
super._init();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_getWindows() {
|
2018-01-03 07:55:38 +00:00
|
|
|
let workspace = null;
|
|
|
|
|
|
|
|
if (this._settings.get_boolean('current-workspace-only')) {
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
|
|
|
|
workspace = workspaceManager.get_active_workspace();
|
|
|
|
}
|
|
|
|
|
2016-10-06 21:32:08 +00:00
|
|
|
return getWindows(workspace);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2016-05-19 12:41:24 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyPressHandler(keysym, action) {
|
2016-05-19 12:41:24 +00:00
|
|
|
if (action == Meta.KeyBindingAction.CYCLE_WINDOWS)
|
|
|
|
this._select(this._next());
|
|
|
|
else if (action == Meta.KeyBindingAction.CYCLE_WINDOWS_BACKWARD)
|
|
|
|
this._select(this._previous());
|
|
|
|
else
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const AppIcon = GObject.registerClass(
|
2019-10-28 18:35:33 +00:00
|
|
|
class AppIcon extends St.BoxLayout {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(app) {
|
2020-03-29 21:51:13 +00:00
|
|
|
super._init({
|
|
|
|
style_class: 'alt-tab-app',
|
|
|
|
vertical: true,
|
|
|
|
});
|
2018-06-28 15:34:01 +00:00
|
|
|
|
2009-11-12 22:46:59 +00:00
|
|
|
this.app = app;
|
2010-03-17 17:31:03 +00:00
|
|
|
this.icon = null;
|
2019-10-17 21:40:24 +00:00
|
|
|
this._iconBin = new St.Bin();
|
2010-02-17 09:52:11 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this.add_child(this._iconBin);
|
|
|
|
this.label = new St.Label({
|
|
|
|
text: this.app.get_name(),
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
this.add_child(this.label);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2019-02-01 00:16:20 +00:00
|
|
|
// eslint-disable-next-line camelcase
|
2017-10-31 00:03:21 +00:00
|
|
|
set_size(size) {
|
2010-03-17 17:31:03 +00:00
|
|
|
this.icon = this.app.create_icon_texture(size);
|
|
|
|
this._iconBin.child = this.icon;
|
2009-11-12 22:46:59 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2009-11-12 22:46:59 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const AppSwitcher = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class AppSwitcher extends SwitcherPopup.SwitcherList {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(apps, altTabPopup) {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init(true);
|
2009-09-15 15:11:32 +00:00
|
|
|
|
2011-09-29 21:43:59 +00:00
|
|
|
this.icons = [];
|
|
|
|
this._arrows = [];
|
|
|
|
|
|
|
|
let windowTracker = Shell.WindowTracker.get_default();
|
2023-08-06 22:40:20 +00:00
|
|
|
let settings = new Gio.Settings({schema_id: 'org.gnome.shell.app-switcher'});
|
2018-01-03 07:55:38 +00:00
|
|
|
|
|
|
|
let workspace = null;
|
|
|
|
if (settings.get_boolean('current-workspace-only')) {
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
|
|
|
|
workspace = workspaceManager.get_active_workspace();
|
|
|
|
}
|
|
|
|
|
2018-05-17 19:19:33 +00:00
|
|
|
let allWindows = getWindows(workspace);
|
2011-09-29 21:43:59 +00:00
|
|
|
|
2011-07-14 21:23:16 +00:00
|
|
|
// Construct the AppIcons, add to the popup
|
2011-09-29 21:43:59 +00:00
|
|
|
for (let i = 0; i < apps.length; i++) {
|
|
|
|
let appIcon = new AppIcon(apps[i]);
|
Create ShellApp, rebase things on it
Previously, we had ShellAppInfo, which contains fundamental
information about an application, and methods on ShellAppMonitor
to retrieve "live" information like the window list.
AppIcon ended up being used as the "App" class which was painful
for various reasons; among them that we need to handle window
list changes, and some consumers weren't ready for that.
Clean things up a bit by introducing a new ShellApp class in C,
which currently wraps a ShellAppInfo.
AppIcon then is more like the display actor for a ShellApp. Notably,
the ".windows" property moves out of it. The altTab code which
won't handle dynamic changes instead is changed to maintain a
cached version.
ShellAppMonitor gains some more methods related to ShellApp now.
In the future, we might consider changing ShellApp to be a GInterface,
which could be implemented by ShellDesktopFileApp, ShellWindowApp.
Then we could axe ShellAppInfo from the "public" API and it would
return to being an internal loss mitigation layer for GMenu.
https://bugzilla.gnome.org/show_bug.cgi?id=598227
2009-10-11 20:40:00 +00:00
|
|
|
// Cache the window list now; we don't handle dynamic changes here,
|
|
|
|
// and we don't want to be continually retrieving it
|
2017-10-31 00:38:18 +00:00
|
|
|
appIcon.cachedWindows = allWindows.filter(
|
2020-04-03 23:52:29 +00:00
|
|
|
w => windowTracker.get_window_app(w) === appIcon.app);
|
2014-01-17 15:05:09 +00:00
|
|
|
if (appIcon.cachedWindows.length > 0)
|
2013-07-04 17:24:30 +00:00
|
|
|
this._addIcon(appIcon);
|
2009-10-02 15:02:46 +00:00
|
|
|
}
|
2009-08-13 16:52:50 +00:00
|
|
|
|
2011-01-06 19:21:27 +00:00
|
|
|
this._altTabPopup = altTabPopup;
|
2018-04-25 19:34:00 +00:00
|
|
|
this._delayedHighlighted = -1;
|
2011-01-06 19:21:27 +00:00
|
|
|
this._mouseTimeOutId = 0;
|
2012-12-01 19:40:13 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-12-01 19:40:13 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2012-12-01 19:40:13 +00:00
|
|
|
if (this._mouseTimeOutId != 0)
|
2019-08-19 18:50:33 +00:00
|
|
|
GLib.source_remove(this._mouseTimeOutId);
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
this.icons.forEach(
|
|
|
|
icon => icon.app.disconnectObject(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_setIconSize() {
|
2010-03-17 17:31:03 +00:00
|
|
|
let j = 0;
|
2019-08-20 00:51:42 +00:00
|
|
|
while (this._items.length > 1 && this._items[j].style_class != 'item-box')
|
2019-01-29 19:36:54 +00:00
|
|
|
j++;
|
2019-08-20 00:51:42 +00:00
|
|
|
|
2010-12-22 14:36:23 +00:00
|
|
|
let themeNode = this._items[j].get_theme_node();
|
2019-06-20 21:01:52 +00:00
|
|
|
this._list.ensure_style();
|
2014-02-16 03:45:28 +00:00
|
|
|
|
2010-12-22 14:36:23 +00:00
|
|
|
let iconPadding = themeNode.get_horizontal_padding();
|
|
|
|
let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
|
2019-06-20 21:09:37 +00:00
|
|
|
let [, labelNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
|
|
|
|
let iconSpacing = labelNaturalHeight + iconPadding + iconBorder;
|
2010-03-17 17:31:03 +00:00
|
|
|
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
|
|
|
|
2020-08-19 09:26:11 +00:00
|
|
|
// We just assume the whole screen here due to weirdness happening with the passed width
|
2011-06-13 13:54:05 +00:00
|
|
|
let primary = Main.layoutManager.primaryMonitor;
|
2018-06-28 15:34:01 +00:00
|
|
|
let parentPadding = this.get_parent().get_theme_node().get_horizontal_padding();
|
|
|
|
let availWidth = primary.width - parentPadding - this.get_theme_node().get_horizontal_padding();
|
2010-06-17 15:17:57 +00:00
|
|
|
|
2014-02-16 04:25:49 +00:00
|
|
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
2017-10-31 00:38:18 +00:00
|
|
|
let iconSizes = baseIconSizes.map(s => s * scaleFactor);
|
2018-06-28 15:34:01 +00:00
|
|
|
let iconSize = baseIconSizes[0];
|
2014-02-16 04:25:49 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
if (this._items.length > 1) {
|
2019-01-29 01:27:05 +00:00
|
|
|
for (let i = 0; i < baseIconSizes.length; i++) {
|
2018-06-28 15:34:01 +00:00
|
|
|
iconSize = baseIconSizes[i];
|
2014-02-16 03:45:28 +00:00
|
|
|
let height = iconSizes[i] + iconSpacing;
|
2010-03-17 17:31:03 +00:00
|
|
|
let w = height * this._items.length + totalSpacing;
|
2010-06-17 15:17:57 +00:00
|
|
|
if (w <= availWidth)
|
2014-02-16 03:45:28 +00:00
|
|
|
break;
|
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
}
|
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this._iconSize = iconSize;
|
|
|
|
|
2019-01-29 01:27:05 +00:00
|
|
|
for (let i = 0; i < this.icons.length; i++) {
|
2010-03-17 17:31:03 +00:00
|
|
|
if (this.icons[i].icon != null)
|
|
|
|
break;
|
2018-06-28 15:34:01 +00:00
|
|
|
this.icons[i].set_size(iconSize);
|
2010-03-17 17:31:03 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
vfunc_get_preferred_height(forWidth) {
|
2018-04-25 23:36:41 +00:00
|
|
|
if (!this._iconSize)
|
|
|
|
this._setIconSize();
|
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
return super.vfunc_get_preferred_height(forWidth);
|
|
|
|
}
|
2010-04-18 20:59:19 +00:00
|
|
|
|
2020-05-09 19:30:26 +00:00
|
|
|
vfunc_allocate(box) {
|
2009-10-02 15:02:46 +00:00
|
|
|
// Allocate the main list items
|
2020-05-09 19:30:26 +00:00
|
|
|
super.vfunc_allocate(box);
|
2018-06-28 15:34:01 +00:00
|
|
|
|
|
|
|
let contentBox = this.get_theme_node().get_content_box(box);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
let arrowHeight = Math.floor(this.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
|
2009-10-02 15:02:46 +00:00
|
|
|
let arrowWidth = arrowHeight * 2;
|
|
|
|
|
|
|
|
// Now allocate each arrow underneath its item
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
|
|
|
let itemBox = this._items[i].allocation;
|
2018-06-28 15:34:01 +00:00
|
|
|
childBox.x1 = contentBox.x1 + Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
|
2009-10-02 15:02:46 +00:00
|
|
|
childBox.x2 = childBox.x1 + arrowWidth;
|
2018-06-28 15:34:01 +00:00
|
|
|
childBox.y1 = contentBox.y1 + itemBox.y2 + arrowHeight;
|
2009-10-02 15:02:46 +00:00
|
|
|
childBox.y2 = childBox.y1 + arrowHeight;
|
2020-05-09 19:30:26 +00:00
|
|
|
this._arrows[i].allocate(childBox);
|
2009-09-21 17:44:42 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-09-21 17:44:42 +00:00
|
|
|
|
2022-01-12 10:36:41 +00:00
|
|
|
// We override SwitcherList's _onItemMotion method to delay
|
2011-01-06 19:21:27 +00:00
|
|
|
// activation when the thumbnail list is open
|
2022-01-12 10:36:41 +00:00
|
|
|
_onItemMotion(item) {
|
2018-04-25 19:34:00 +00:00
|
|
|
if (item === this._items[this._highlighted] ||
|
|
|
|
item === this._items[this._delayedHighlighted])
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
2020-01-15 15:19:41 +00:00
|
|
|
const index = this._items.indexOf(item);
|
|
|
|
|
2018-04-25 19:34:00 +00:00
|
|
|
if (this._mouseTimeOutId !== 0) {
|
2019-08-19 18:50:33 +00:00
|
|
|
GLib.source_remove(this._mouseTimeOutId);
|
2018-04-25 19:34:00 +00:00
|
|
|
this._delayedHighlighted = -1;
|
|
|
|
this._mouseTimeOutId = 0;
|
|
|
|
}
|
|
|
|
|
2011-01-06 19:21:27 +00:00
|
|
|
if (this._altTabPopup.thumbnailsVisible) {
|
2018-04-25 19:34:00 +00:00
|
|
|
this._delayedHighlighted = index;
|
2019-08-19 18:50:33 +00:00
|
|
|
this._mouseTimeOutId = GLib.timeout_add(
|
|
|
|
GLib.PRIORITY_DEFAULT,
|
|
|
|
APP_ICON_HOVER_TIMEOUT,
|
|
|
|
() => {
|
|
|
|
this._enterItem(index);
|
2018-04-25 19:34:00 +00:00
|
|
|
this._delayedHighlighted = -1;
|
2019-08-19 18:50:33 +00:00
|
|
|
this._mouseTimeOutId = 0;
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
|
|
});
|
2014-04-10 17:26:52 +00:00
|
|
|
GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem');
|
2019-01-29 21:02:57 +00:00
|
|
|
} else {
|
2019-01-29 19:36:54 +00:00
|
|
|
this._itemEntered(index);
|
2019-01-29 21:02:57 +00:00
|
|
|
}
|
2018-04-25 20:48:27 +00:00
|
|
|
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2011-01-06 19:21:27 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_enterItem(index) {
|
2019-02-01 13:41:55 +00:00
|
|
|
let [x, y] = global.get_pointer();
|
2011-01-06 19:21:27 +00:00
|
|
|
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
|
|
|
if (this._items[index].contains(pickedActor))
|
|
|
|
this._itemEntered(index);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2011-01-06 19:21:27 +00:00
|
|
|
|
2009-10-09 13:43:31 +00:00
|
|
|
// We override SwitcherList's highlight() method to also deal with
|
2018-05-05 13:32:16 +00:00
|
|
|
// the AppSwitcher->ThumbnailSwitcher arrows. Apps with only 1 window
|
2009-10-09 13:43:31 +00:00
|
|
|
// will hide their arrows by default, but show them when their
|
|
|
|
// thumbnails are visible (ie, when the app icon is supposed to be
|
|
|
|
// in justOutline mode). Apps with multiple windows will normally
|
|
|
|
// show a dim arrow, but show a bright arrow when they are
|
2010-03-31 20:01:34 +00:00
|
|
|
// highlighted.
|
2017-10-31 00:03:21 +00:00
|
|
|
highlight(n, justOutline) {
|
2018-04-29 23:06:35 +00:00
|
|
|
if (this.icons[this._highlighted]) {
|
|
|
|
if (this.icons[this._highlighted].cachedWindows.length === 1)
|
|
|
|
this._arrows[this._highlighted].hide();
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2018-04-29 23:06:35 +00:00
|
|
|
this._arrows[this._highlighted].remove_style_pseudo_class('highlighted');
|
2009-10-09 13:43:31 +00:00
|
|
|
}
|
2009-09-21 17:44:42 +00:00
|
|
|
|
2017-10-31 01:23:39 +00:00
|
|
|
super.highlight(n, justOutline);
|
2009-09-29 03:31:54 +00:00
|
|
|
|
2018-04-29 23:06:35 +00:00
|
|
|
if (this._highlighted !== -1) {
|
|
|
|
if (justOutline && this.icons[this._highlighted].cachedWindows.length === 1)
|
|
|
|
this._arrows[this._highlighted].show();
|
2009-10-09 13:43:31 +00:00
|
|
|
else
|
2018-04-29 23:06:35 +00:00
|
|
|
this._arrows[this._highlighted].add_style_pseudo_class('highlighted');
|
2009-10-09 13:43:31 +00:00
|
|
|
}
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2009-04-13 14:55:41 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addIcon(appIcon) {
|
2009-10-02 15:02:46 +00:00
|
|
|
this.icons.push(appIcon);
|
2018-06-28 15:34:01 +00:00
|
|
|
let item = this.addItem(appIcon, appIcon.label);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
appIcon.app.connectObject('notify::state', app => {
|
2018-02-08 10:38:04 +00:00
|
|
|
if (app.state != Shell.AppState.RUNNING)
|
|
|
|
this._removeIcon(app);
|
2021-08-15 22:36:59 +00:00
|
|
|
}, this);
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
let arrow = new St.DrawingArea({style_class: 'switcher-arrow'});
|
2019-01-28 00:42:00 +00:00
|
|
|
arrow.connect('repaint', () => SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM));
|
2018-06-28 15:34:01 +00:00
|
|
|
this.add_actor(arrow);
|
2009-10-02 15:02:46 +00:00
|
|
|
this._arrows.push(arrow);
|
2009-10-09 13:43:31 +00:00
|
|
|
|
|
|
|
if (appIcon.cachedWindows.length == 1)
|
|
|
|
arrow.hide();
|
2012-03-07 18:29:13 +00:00
|
|
|
else
|
2019-08-19 17:55:49 +00:00
|
|
|
item.add_accessible_state(Atk.StateType.EXPANDABLE);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_removeIcon(app) {
|
2018-02-08 10:38:04 +00:00
|
|
|
let index = this.icons.findIndex(icon => {
|
|
|
|
return icon.app == app;
|
|
|
|
});
|
|
|
|
if (index === -1)
|
|
|
|
return;
|
|
|
|
|
2020-06-29 20:51:41 +00:00
|
|
|
this._arrows[index].destroy();
|
|
|
|
this._arrows.splice(index, 1);
|
|
|
|
|
2018-02-08 10:38:04 +00:00
|
|
|
this.icons.splice(index, 1);
|
|
|
|
this.removeItem(index);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2009-09-21 17:04:30 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const ThumbnailSwitcher = GObject.registerClass(
|
2018-05-05 13:32:16 +00:00
|
|
|
class ThumbnailSwitcher extends SwitcherPopup.SwitcherList {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(windows) {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init(false);
|
2009-10-02 15:02:46 +00:00
|
|
|
|
2019-08-19 19:18:54 +00:00
|
|
|
this._labels = [];
|
|
|
|
this._thumbnailBins = [];
|
|
|
|
this._clones = [];
|
2010-03-17 17:31:03 +00:00
|
|
|
this._windows = windows;
|
|
|
|
|
2009-10-02 15:02:46 +00:00
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2020-03-29 21:51:13 +00:00
|
|
|
const box = new St.BoxLayout({
|
|
|
|
style_class: 'thumbnail-box',
|
|
|
|
vertical: true,
|
|
|
|
});
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
let bin = new St.Bin({style_class: 'thumbnail'});
|
2009-12-18 23:50:40 +00:00
|
|
|
|
|
|
|
box.add_actor(bin);
|
2010-03-17 17:31:03 +00:00
|
|
|
this._thumbnailBins.push(bin);
|
2009-10-09 13:43:31 +00:00
|
|
|
|
2022-09-12 14:44:27 +00:00
|
|
|
const title = windows[i].get_title();
|
|
|
|
const name = new St.Label({
|
|
|
|
text: title,
|
|
|
|
// St.Label doesn't support text-align
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
this._labels.push(name);
|
|
|
|
box.add_actor(name);
|
2011-03-08 18:33:57 +00:00
|
|
|
|
2022-09-12 14:44:27 +00:00
|
|
|
this.addItem(box, name);
|
2009-09-21 17:04:30 +00:00
|
|
|
}
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
addClones(availHeight) {
|
2010-03-17 17:31:03 +00:00
|
|
|
if (!this._thumbnailBins.length)
|
|
|
|
return;
|
|
|
|
let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
|
2018-06-28 15:34:01 +00:00
|
|
|
totalPadding += this.get_theme_node().get_horizontal_padding() + this.get_theme_node().get_vertical_padding();
|
2019-02-01 13:41:55 +00:00
|
|
|
let [, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 21:38:36 +00:00
|
|
|
let spacing = this._items[0].child.get_theme_node().get_length('spacing');
|
2014-02-16 09:13:26 +00:00
|
|
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
|
|
|
let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor;
|
2010-03-17 17:31:03 +00:00
|
|
|
|
2014-02-16 09:13:26 +00:00
|
|
|
availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize);
|
2018-06-28 15:34:01 +00:00
|
|
|
let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.get_theme_node().get_vertical_padding() - spacing;
|
2014-02-16 09:13:26 +00:00
|
|
|
binHeight = Math.min(thumbnailSize, binHeight);
|
2010-03-17 17:31:03 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < this._thumbnailBins.length; i++) {
|
|
|
|
let mutterWindow = this._windows[i].get_compositor_private();
|
2011-02-09 19:42:44 +00:00
|
|
|
if (!mutterWindow)
|
|
|
|
continue;
|
|
|
|
|
2014-02-16 09:13:26 +00:00
|
|
|
let clone = _createWindowClone(mutterWindow, thumbnailSize);
|
2010-03-17 17:31:03 +00:00
|
|
|
this._thumbnailBins[i].set_height(binHeight);
|
|
|
|
this._thumbnailBins[i].add_actor(clone);
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
mutterWindow.connectObject('destroy',
|
|
|
|
source => this._removeThumbnail(source, clone), this);
|
2010-03-17 17:31:03 +00:00
|
|
|
this._clones.push(clone);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we only do this once
|
2019-08-19 19:18:54 +00:00
|
|
|
this._thumbnailBins = [];
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_removeThumbnail(source, clone) {
|
2018-02-08 10:38:04 +00:00
|
|
|
let index = this._clones.indexOf(clone);
|
|
|
|
if (index === -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._clones.splice(index, 1);
|
|
|
|
this._windows.splice(index, 1);
|
|
|
|
this._labels.splice(index, 1);
|
|
|
|
this.removeItem(index);
|
|
|
|
|
|
|
|
if (this._clones.length > 0)
|
|
|
|
this.highlight(SwitcherPopup.mod(index, this._clones.length));
|
|
|
|
else
|
2018-06-28 15:34:01 +00:00
|
|
|
this.destroy();
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2018-02-08 10:38:04 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2021-08-15 22:36:59 +00:00
|
|
|
this._clones.forEach(
|
|
|
|
clone => clone?.source.disconnectObject(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2011-02-10 19:41:24 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const WindowIcon = GObject.registerClass(
|
2017-10-31 01:23:39 +00:00
|
|
|
class WindowIcon extends St.BoxLayout {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(window, mode) {
|
2020-03-29 21:51:13 +00:00
|
|
|
super._init({
|
|
|
|
style_class: 'alt-tab-app',
|
|
|
|
vertical: true,
|
|
|
|
});
|
2018-08-15 19:02:49 +00:00
|
|
|
|
2012-11-23 02:20:10 +00:00
|
|
|
this.window = window;
|
|
|
|
|
2023-08-06 22:40:20 +00:00
|
|
|
this._icon = new St.Widget({layout_manager: new Clutter.BinLayout()});
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
this.add_child(this._icon);
|
2023-08-06 22:40:20 +00:00
|
|
|
this.label = new St.Label({text: window.get_title()});
|
2012-11-23 02:20:10 +00:00
|
|
|
|
|
|
|
let tracker = Shell.WindowTracker.get_default();
|
|
|
|
this.app = tracker.get_window_app(window);
|
|
|
|
|
|
|
|
let mutterWindow = this.window.get_compositor_private();
|
|
|
|
let size;
|
|
|
|
|
|
|
|
this._icon.destroy_all_children();
|
|
|
|
|
2015-11-25 23:04:11 +00:00
|
|
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
|
|
|
|
2013-09-09 20:53:44 +00:00
|
|
|
switch (mode) {
|
2019-02-01 12:21:00 +00:00
|
|
|
case AppIconMode.THUMBNAIL_ONLY:
|
|
|
|
size = WINDOW_PREVIEW_SIZE;
|
|
|
|
this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AppIconMode.BOTH:
|
|
|
|
size = WINDOW_PREVIEW_SIZE;
|
|
|
|
this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor));
|
|
|
|
|
2019-08-20 00:51:42 +00:00
|
|
|
if (this.app) {
|
2019-02-01 12:21:00 +00:00
|
|
|
this._icon.add_actor(this._createAppIcon(this.app,
|
|
|
|
APP_ICON_SIZE_SMALL));
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2019-02-01 12:21:00 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AppIconMode.APP_ICON_ONLY:
|
|
|
|
size = APP_ICON_SIZE;
|
|
|
|
this._icon.add_actor(this._createAppIcon(this.app, size));
|
2012-11-23 02:20:10 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 23:04:11 +00:00
|
|
|
this._icon.set_size(size * scaleFactor, size * scaleFactor);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_createAppIcon(app, size) {
|
2019-08-19 19:33:15 +00:00
|
|
|
let appIcon = app
|
|
|
|
? app.create_icon_texture(size)
|
2023-08-06 22:40:20 +00:00
|
|
|
: new St.Icon({icon_name: 'icon-missing', icon_size: size});
|
2012-11-23 02:20:10 +00:00
|
|
|
appIcon.x_expand = appIcon.y_expand = true;
|
|
|
|
appIcon.x_align = appIcon.y_align = Clutter.ActorAlign.END;
|
|
|
|
|
|
|
|
return appIcon;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const WindowSwitcher = GObject.registerClass(
|
2018-05-05 13:32:16 +00:00
|
|
|
class WindowSwitcher extends SwitcherPopup.SwitcherList {
|
2017-10-31 00:03:21 +00:00
|
|
|
_init(windows, mode) {
|
2017-10-31 01:23:39 +00:00
|
|
|
super._init(true);
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
this._label = new St.Label({
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
2018-06-28 15:34:01 +00:00
|
|
|
this.add_actor(this._label);
|
2012-11-23 02:20:10 +00:00
|
|
|
|
|
|
|
this.windows = windows;
|
|
|
|
this.icons = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
|
|
|
let win = windows[i];
|
2013-09-09 20:53:44 +00:00
|
|
|
let icon = new WindowIcon(win, mode);
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2018-08-15 19:02:49 +00:00
|
|
|
this.addItem(icon, icon.label);
|
2012-11-23 02:20:10 +00:00
|
|
|
this.icons.push(icon);
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2021-08-15 22:36:59 +00:00
|
|
|
icon.window.connectObject('unmanaged',
|
|
|
|
window => this._removeWindow(window), this);
|
2012-11-23 02:20:10 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onDestroy() {
|
2021-08-15 22:36:59 +00:00
|
|
|
this.icons.forEach(
|
|
|
|
icon => icon.window.disconnectObject(this));
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
vfunc_get_preferred_height(forWidth) {
|
2017-10-31 01:23:39 +00:00
|
|
|
let [minHeight, natHeight] = super.vfunc_get_preferred_height(forWidth);
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2018-06-28 15:34:01 +00:00
|
|
|
let spacing = this.get_theme_node().get_padding(St.Side.BOTTOM);
|
2012-11-23 02:20:10 +00:00
|
|
|
let [labelMin, labelNat] = this._label.get_preferred_height(-1);
|
2018-06-28 15:34:01 +00:00
|
|
|
|
|
|
|
minHeight += labelMin + spacing;
|
|
|
|
natHeight += labelNat + spacing;
|
|
|
|
|
|
|
|
return [minHeight, natHeight];
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2020-05-09 19:30:26 +00:00
|
|
|
vfunc_allocate(box) {
|
2018-06-28 15:34:01 +00:00
|
|
|
let themeNode = this.get_theme_node();
|
|
|
|
let contentBox = themeNode.get_content_box(box);
|
2020-03-12 12:37:53 +00:00
|
|
|
const labelHeight = this._label.height;
|
|
|
|
const totalLabelHeight =
|
|
|
|
labelHeight + themeNode.get_padding(St.Side.BOTTOM);
|
2018-06-28 15:34:01 +00:00
|
|
|
|
2020-03-12 12:37:53 +00:00
|
|
|
box.y2 -= totalLabelHeight;
|
2020-05-09 19:30:26 +00:00
|
|
|
super.vfunc_allocate(box);
|
2018-06-28 15:34:01 +00:00
|
|
|
|
|
|
|
// Hooking up the parent vfunc will call this.set_allocation() with
|
|
|
|
// the height without the label height, so call it again with the
|
|
|
|
// correct size here.
|
2020-03-12 12:37:53 +00:00
|
|
|
box.y2 += totalLabelHeight;
|
2020-05-09 19:30:26 +00:00
|
|
|
this.set_allocation(box);
|
2020-03-12 12:37:53 +00:00
|
|
|
|
|
|
|
const childBox = new Clutter.ActorBox();
|
|
|
|
childBox.x1 = contentBox.x1;
|
|
|
|
childBox.x2 = contentBox.x2;
|
|
|
|
childBox.y2 = contentBox.y2;
|
|
|
|
childBox.y1 = childBox.y2 - labelHeight;
|
2020-05-09 19:30:26 +00:00
|
|
|
this._label.allocate(childBox);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2012-11-23 02:20:10 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
highlight(index, justOutline) {
|
2017-10-31 01:23:39 +00:00
|
|
|
super.highlight(index, justOutline);
|
2012-11-23 02:20:10 +00:00
|
|
|
|
|
|
|
this._label.set_text(index == -1 ? '' : this.icons[index].label.text);
|
2017-10-31 01:23:39 +00:00
|
|
|
}
|
2017-12-05 10:02:23 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_removeWindow(window) {
|
2017-12-05 10:02:23 +00:00
|
|
|
let index = this.icons.findIndex(icon => {
|
|
|
|
return icon.window == window;
|
|
|
|
});
|
|
|
|
if (index === -1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.icons.splice(index, 1);
|
|
|
|
this.removeItem(index);
|
2012-11-23 02:20:10 +00:00
|
|
|
}
|
|
|
|
});
|