2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import Clutter from 'gi://Clutter';
|
|
|
|
import GObject from 'gi://GObject';
|
|
|
|
import Meta from 'gi://Meta';
|
|
|
|
import Shell from 'gi://Shell';
|
|
|
|
import St from 'gi://St';
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
import * as Main from './main.js';
|
|
|
|
import * as SwitcherPopup from './switcherPopup.js';
|
|
|
|
import * as Params from '../misc/params.js';
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const POPUP_APPICON_SIZE = 96;
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export const SortGroup = {
|
2011-02-07 16:29:34 +00:00
|
|
|
TOP: 0,
|
|
|
|
MIDDLE: 1,
|
2019-08-20 21:43:54 +00:00
|
|
|
BOTTOM: 2,
|
2011-02-07 16:29:34 +00:00
|
|
|
};
|
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
export class CtrlAltTabManager {
|
2017-10-31 01:19:44 +00:00
|
|
|
constructor() {
|
2010-07-01 18:13:42 +00:00
|
|
|
this._items = [];
|
2020-03-29 21:51:13 +00:00
|
|
|
this.addGroup(global.window_group,
|
|
|
|
_('Windows'),
|
|
|
|
'focus-windows-symbolic', {
|
|
|
|
sortGroup: SortGroup.TOP,
|
|
|
|
focusCallback: this._focusWindows.bind(this),
|
|
|
|
});
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
addGroup(root, name, icon, params) {
|
2020-03-29 21:51:13 +00:00
|
|
|
const item = Params.parse(params, {
|
|
|
|
sortGroup: SortGroup.MIDDLE,
|
|
|
|
proxy: root,
|
|
|
|
focusCallback: null,
|
|
|
|
});
|
2011-02-07 16:29:34 +00:00
|
|
|
|
|
|
|
item.root = root;
|
|
|
|
item.name = name;
|
|
|
|
item.iconName = icon;
|
|
|
|
|
|
|
|
this._items.push(item);
|
2019-01-28 00:42:00 +00:00
|
|
|
root.connect('destroy', () => this.removeGroup(root));
|
2012-12-04 19:02:15 +00:00
|
|
|
if (root instanceof St.Widget)
|
|
|
|
global.focus_manager.add_group(root);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
removeGroup(root) {
|
2012-12-04 19:02:15 +00:00
|
|
|
if (root instanceof St.Widget)
|
|
|
|
global.focus_manager.remove_group(root);
|
2010-07-01 18:13:42 +00:00
|
|
|
for (let i = 0; i < this._items.length; i++) {
|
2023-08-07 00:51:19 +00:00
|
|
|
if (this._items[i].root === root) {
|
2010-07-01 18:13:42 +00:00
|
|
|
this._items.splice(i, 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
focusGroup(item, timestamp) {
|
Rework window / actor focus handling
The duality of the Clutter's key focus and mutter's window focus has long been
a problem for us in lots of case, and caused us to create large and complicated
hacks to get around the issue, including GrabHelper's focus grab model.
Instead of doing this, tie basic focus management into the core of gnome-shell,
instead of requiring complex "application-level" management to get it done
right.
Do this by making sure that only one of an actor or window can be focused at
the same time, and apply the appropriate logic to drop one or the other,
reactively.
Modals are considered a special case, as we grab all keyboard events, but at
the X level, the client window still has focus. Make sure to not do any input
synchronization when we have a modal.
At the same time, remove the FOCUSED input mode, as it's no longer necessary.
https://bugzilla.gnome.org/show_bug.cgi?id=700735
2013-05-18 04:18:13 +00:00
|
|
|
if (item.focusCallback)
|
2012-12-04 18:45:52 +00:00
|
|
|
item.focusCallback(timestamp);
|
Rework window / actor focus handling
The duality of the Clutter's key focus and mutter's window focus has long been
a problem for us in lots of case, and caused us to create large and complicated
hacks to get around the issue, including GrabHelper's focus grab model.
Instead of doing this, tie basic focus management into the core of gnome-shell,
instead of requiring complex "application-level" management to get it done
right.
Do this by making sure that only one of an actor or window can be focused at
the same time, and apply the appropriate logic to drop one or the other,
reactively.
Modals are considered a special case, as we grab all keyboard events, but at
the X level, the client window still has focus. Make sure to not do any input
synchronization when we have a modal.
At the same time, remove the FOCUSED input mode, as it's no longer necessary.
https://bugzilla.gnome.org/show_bug.cgi?id=700735
2013-05-18 04:18:13 +00:00
|
|
|
else
|
2018-11-27 12:58:25 +00:00
|
|
|
item.root.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2011-02-07 16:29:34 +00:00
|
|
|
|
|
|
|
// Sort the items into a consistent order; panel first, tray last,
|
|
|
|
// and everything else in between, sorted by X coordinate, so that
|
|
|
|
// they will have the same left-to-right ordering in the
|
|
|
|
// Ctrl-Alt-Tab dialog as they do onscreen.
|
2017-10-31 00:03:21 +00:00
|
|
|
_sortItems(a, b) {
|
2023-08-07 00:51:19 +00:00
|
|
|
if (a.sortGroup !== b.sortGroup)
|
2011-02-07 16:29:34 +00:00
|
|
|
return a.sortGroup - b.sortGroup;
|
|
|
|
|
2019-02-01 13:41:55 +00:00
|
|
|
let [ax] = a.proxy.get_transformed_position();
|
|
|
|
let [bx] = b.proxy.get_transformed_position();
|
2011-02-07 16:29:34 +00:00
|
|
|
|
2012-12-04 18:48:08 +00:00
|
|
|
return ax - bx;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
popup(backward, binding, mask) {
|
2010-07-01 18:13:42 +00:00
|
|
|
// Start with the set of focus groups that are currently mapped
|
2017-10-31 00:38:18 +00:00
|
|
|
let items = this._items.filter(item => item.proxy.mapped);
|
2010-07-01 18:13:42 +00:00
|
|
|
|
|
|
|
// And add the windows metacity would show in its Ctrl-Alt-Tab list
|
2013-05-07 18:25:06 +00:00
|
|
|
if (Main.sessionMode.hasWindows && !Main.overview.visible) {
|
2018-01-03 07:55:38 +00:00
|
|
|
let display = global.display;
|
|
|
|
let workspaceManager = global.workspace_manager;
|
|
|
|
let activeWorkspace = workspaceManager.get_active_workspace();
|
|
|
|
let windows = display.get_tab_list(Meta.TabList.DOCKS,
|
2023-08-06 23:45:22 +00:00
|
|
|
activeWorkspace);
|
2011-02-07 16:29:34 +00:00
|
|
|
let windowTracker = Shell.WindowTracker.get_default();
|
|
|
|
let textureCache = St.TextureCache.get_default();
|
|
|
|
for (let i = 0; i < windows.length; i++) {
|
2013-04-18 04:20:45 +00:00
|
|
|
let icon = null;
|
|
|
|
let iconName = null;
|
2023-08-07 00:51:19 +00:00
|
|
|
if (windows[i].get_window_type() === Meta.WindowType.DESKTOP) {
|
2013-04-18 04:20:45 +00:00
|
|
|
iconName = 'video-display-symbolic';
|
|
|
|
} else {
|
|
|
|
let app = windowTracker.get_window_app(windows[i]);
|
2019-08-20 00:51:42 +00:00
|
|
|
if (app) {
|
2013-04-18 04:20:45 +00:00
|
|
|
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
2019-08-20 00:51:42 +00:00
|
|
|
} else {
|
2021-02-25 23:00:06 +00:00
|
|
|
icon = new St.Icon({
|
|
|
|
gicon: textureCache.bind_cairo_surface_property(windows[i], 'icon'),
|
|
|
|
icon_size: POPUP_APPICON_SIZE,
|
|
|
|
});
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2013-04-18 04:20:45 +00:00
|
|
|
}
|
|
|
|
|
2020-03-29 21:51:13 +00:00
|
|
|
items.push({
|
|
|
|
name: windows[i].title,
|
|
|
|
proxy: windows[i].get_compositor_private(),
|
|
|
|
focusCallback: timestamp => {
|
|
|
|
Main.activateWindow(windows[i], timestamp);
|
|
|
|
},
|
|
|
|
iconActor: icon,
|
|
|
|
iconName,
|
|
|
|
sortGroup: SortGroup.MIDDLE,
|
|
|
|
});
|
2011-02-07 16:29:34 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!items.length)
|
|
|
|
return;
|
|
|
|
|
2017-12-02 00:27:35 +00:00
|
|
|
items.sort(this._sortItems.bind(this));
|
2011-09-17 21:09:47 +00:00
|
|
|
|
|
|
|
if (!this._popup) {
|
2012-11-30 15:16:48 +00:00
|
|
|
this._popup = new CtrlAltTabPopup(items);
|
|
|
|
this._popup.show(backward, binding, mask);
|
2011-09-17 21:09:47 +00:00
|
|
|
|
2023-08-06 23:45:22 +00:00
|
|
|
this._popup.connect('destroy', () => {
|
|
|
|
this._popup = null;
|
|
|
|
});
|
2011-09-17 21:09:47 +00:00
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2012-12-04 18:46:47 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_focusWindows(timestamp) {
|
2018-01-03 07:55:38 +00:00
|
|
|
global.display.focus_default_window(timestamp);
|
2010-07-01 18:13:42 +00:00
|
|
|
}
|
2023-07-10 09:53:00 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const CtrlAltTabPopup = GObject.registerClass(
|
2017-10-31 01:19:44 +00:00
|
|
|
class CtrlAltTabPopup extends SwitcherPopup.SwitcherPopup {
|
2019-02-15 16:15:04 +00:00
|
|
|
_init(items) {
|
|
|
|
super._init(items);
|
2014-09-03 15:15:31 +00:00
|
|
|
|
2012-11-30 15:16:48 +00:00
|
|
|
this._switcherList = new CtrlAltTabSwitcher(this._items);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_keyPressHandler(keysym, action) {
|
2023-08-07 00:51:19 +00:00
|
|
|
if (action === Meta.KeyBindingAction.SWITCH_PANELS)
|
2014-08-12 15:55:22 +00:00
|
|
|
this._select(this._next());
|
2023-08-07 00:51:19 +00:00
|
|
|
else if (action === Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
|
2014-08-12 15:55:22 +00:00
|
|
|
this._select(this._previous());
|
2023-08-07 00:51:19 +00:00
|
|
|
else if (keysym === Clutter.KEY_Left)
|
2010-07-01 18:13:42 +00:00
|
|
|
this._select(this._previous());
|
2023-08-07 00:51:19 +00:00
|
|
|
else if (keysym === Clutter.KEY_Right)
|
2010-07-01 18:13:42 +00:00
|
|
|
this._select(this._next());
|
2014-05-26 13:27:16 +00:00
|
|
|
else
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
|
|
|
|
return Clutter.EVENT_STOP;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_finish(time) {
|
2017-10-31 01:19:44 +00:00
|
|
|
super._finish(time);
|
2012-12-04 18:45:52 +00:00
|
|
|
Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex], time);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2019-02-15 16:15:04 +00:00
|
|
|
});
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2023-07-10 09:53:00 +00:00
|
|
|
const CtrlAltTabSwitcher = GObject.registerClass(
|
2017-10-31 01:19:44 +00:00
|
|
|
class CtrlAltTabSwitcher extends SwitcherPopup.SwitcherList {
|
2019-02-15 16:15:04 +00:00
|
|
|
_init(items) {
|
|
|
|
super._init(true);
|
2010-07-01 18:13:42 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < items.length; i++)
|
|
|
|
this._addIcon(items[i]);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addIcon(item) {
|
2020-03-29 21:51:13 +00:00
|
|
|
const box = new St.BoxLayout({
|
|
|
|
style_class: 'alt-tab-app',
|
|
|
|
vertical: true,
|
|
|
|
});
|
2010-07-01 18:13:42 +00:00
|
|
|
|
|
|
|
let icon = item.iconActor;
|
|
|
|
if (!icon) {
|
2020-03-29 21:51:13 +00:00
|
|
|
icon = new St.Icon({
|
|
|
|
icon_name: item.iconName,
|
|
|
|
icon_size: POPUP_APPICON_SIZE,
|
|
|
|
});
|
2010-07-01 18:13:42 +00:00
|
|
|
}
|
2019-10-21 18:44:00 +00:00
|
|
|
box.add_child(icon);
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2019-10-21 18:44:00 +00:00
|
|
|
let text = new St.Label({
|
|
|
|
text: item.name,
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
});
|
|
|
|
box.add_child(text);
|
2010-07-01 18:13:42 +00:00
|
|
|
|
2011-03-08 18:33:57 +00:00
|
|
|
this.addItem(box, text);
|
2010-07-01 18:13:42 +00:00
|
|
|
}
|
2019-02-15 16:15:04 +00:00
|
|
|
});
|