1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
3
const Clutter = imports.gi.Clutter;
4
const Gdk = imports.gi.Gdk;
5
const Gio = imports.gi.Gio;
6
const Gtk = imports.gi.Gtk;
7
const Lang = imports.lang;
8
const Mainloop = imports.mainloop;
9
const Meta = imports.gi.Meta;
10
const Shell = imports.gi.Shell;
11
const St = imports.gi.St;
13
const AltTab = imports.ui.altTab;
14
const Main = imports.ui.main;
15
const ModalDialog = imports.ui.modalDialog;
16
const Tweener = imports.ui.tweener;
17
const WindowManager = imports.ui.windowManager;
19
const Gettext = imports.gettext.domain('gnome-shell-extensions');
20
const _ = Gettext.gettext;
21
const N_ = function(e) { return e };
23
const SETTINGS_HIGHLIGHT_SELECTED_KEY = 'highlight-selected';
25
const AltTabPopupWorkspaceIcons = new Lang.Class({
26
Name: 'AlternateTab.AltTabPopupWorkspaceIcons',
27
Extends: AltTab.AltTabPopup,
29
_init: function(settings) {
32
this._settings = settings;
35
_windowActivated : function(thumbnailList, n) { },
37
show : function(backward, binding, mask) {
38
let appSys = Shell.AppSystem.get_default();
39
let apps = appSys.get_running ();
44
if (!Main.pushModal(this.actor)) {
45
// Probably someone else has a pointer grab, try again with keyboard only
46
if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) {
50
this._haveModal = true;
51
this._modifierMask = AltTab.primaryModifier(mask);
53
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
54
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
56
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
57
this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
59
this._appSwitcher = new WindowSwitcher(apps, this);
60
this.actor.add_actor(this._appSwitcher.actor);
61
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
62
this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
64
this._appIcons = this._appSwitcher.icons;
66
// Need to force an allocation so we can figure out whether we
67
// need to scroll when selecting
68
this.actor.opacity = 0;
70
this.actor.get_allocation_box();
72
this._highlight_selected = this._settings.get_boolean(SETTINGS_HIGHLIGHT_SELECTED_KEY);
74
// Make the initial selection
75
if (binding == 'switch_group') {
76
//see AltTab.AltTabPopup.show function
77
//cached windows are always of length one, so select first app and the window
78
//the direction doesn't matter, so ignore backward
80
} else if (binding == 'switch_group_backward') {
82
} else if (binding == 'switch_windows_backward') {
83
this._select(this._appIcons.length - 1);
84
} else if (this._appIcons.length == 1) {
86
} else if (backward) {
87
this._select(this._appIcons.length - 1);
93
// There's a race condition; if the user released Alt before
94
// we got the grab, then we won't be notified. (See
95
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
96
// details.) So we check now. (Have to do this after updating
98
let [x, y, mods] = global.get_pointer();
99
if (!(mods & this._modifierMask)) {
104
// We delay showing the popup so that fast Alt+Tab users aren't
105
// disturbed by the popup briefly flashing.
106
this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT,
107
Lang.bind(this, function () {
108
this.actor.opacity = 255;
109
this._initialDelayTimeoutId = 0;
115
_select : function(app, window, forceAppFocus) {
116
if (app != this._currentApp || window == null) {
117
if (this._thumbnails)
118
this._destroyThumbnails();
121
if (this._thumbnailTimeoutId != 0) {
122
Mainloop.source_remove(this._thumbnailTimeoutId);
123
this._thumbnailTimeoutId = 0;
126
this._thumbnailsFocused = (window != null) && !forceAppFocus;
128
this._currentApp = app;
129
this._currentWindow = window ? window : -1;
130
this._appSwitcher.highlight(app, this._thumbnailsFocused);
132
if (window != null) {
133
if (!this._thumbnails)
134
this._createThumbnails();
135
this._currentWindow = window;
136
this._thumbnails.highlight(window, forceAppFocus);
137
} else if (this._appIcons[this._currentApp].cachedWindows.length > 1 &&
139
this._thumbnailTimeoutId = Mainloop.timeout_add (
140
AltTab.THUMBNAIL_POPUP_TIME,
141
Lang.bind(this, this._timeoutPopupThumbnails));
143
if (this._highlight_selected) {
144
let current_app = this._appIcons[this._currentApp];
145
Main.activateWindow(current_app.cachedWindows[0]);
149
_finish : function() {
150
let app = this._appIcons[this._currentApp];
155
* We've to restore the original Z-depth and order of all windows.
157
* Gnome-shell doesn't give an option to change Z-depth without
158
* messing the window's user_time.
160
* Pointless if the popup wasn't showed.
162
if (this._highlight_selected && this.actor.opacity == 255) {
163
for (let i = this._appIcons.length - 2; i >= 0; i--) {
164
let app_walker = this._appIcons[i];
165
Main.activateWindow(app_walker.cachedWindows[0], global.get_current_time() - i - 1);
169
Main.activateWindow(app.cachedWindows[0]);
175
const AppIcon = new Lang.Class({
176
Name: 'AlternateTab.AppIcon',
177
Extends: AltTab.AppIcon,
179
_init: function(app, window) {
182
this.cachedWindows = [];
183
this.cachedWindows.push(window);
185
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
188
this._iconBin = new St.Bin({ x_fill: true, y_fill: true });
190
this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
192
let title = window.get_title();
194
this.label = new St.Label({ text: title });
195
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
196
bin.add_actor(this.label);
200
this.label = new St.Label({ text: this.app.get_name() });
201
this.actor.add(this.label, { x_fill: false });
206
const WindowSwitcher = new Lang.Class({
207
Name: 'AlternateTab.WindowSwitcher',
208
Extends: AltTab.AppSwitcher,
210
_init : function(apps, altTabPopup) {
212
// We inherit from AltTab.AppSwitcher, but only chain up to
213
// AltTab.SwitcherList._init, to bypass AltTab.AppSwitcher._init
214
AltTab.SwitcherList.prototype._init.call(this, true);
216
// Construct the AppIcons, sort by time, add to the popup
217
let activeWorkspace = global.screen.get_active_workspace();
218
let workspaceIcons = [];
220
for (let i = 0; i < apps.length; i++) {
221
// Cache the window list now; we don't handle dynamic changes here,
222
// and we don't want to be continually retrieving it
223
let windows = apps[i].get_windows();
225
for(let j = 0; j < windows.length; j++) {
226
let appIcon = new AppIcon(apps[i], windows[j]);
227
if (this._isWindowOnWorkspace(windows[j], activeWorkspace)) {
228
workspaceIcons.push(appIcon);
231
otherIcons.push(appIcon);
236
workspaceIcons.sort(Lang.bind(this, this._sortAppIcon));
237
otherIcons.sort(Lang.bind(this, this._sortAppIcon));
239
if(otherIcons.length > 0) {
240
let mostRecentOtherIcon = otherIcons[0];
242
otherIcons.push(mostRecentOtherIcon);
247
for (let i = 0; i < workspaceIcons.length; i++)
248
this._addIcon(workspaceIcons[i]);
249
if (workspaceIcons.length > 0 && otherIcons.length > 0)
251
for (let i = 0; i < otherIcons.length; i++)
252
this._addIcon(otherIcons[i]);
256
this._altTabPopup = altTabPopup;
257
this._mouseTimeOutId = 0;
261
_isWindowOnWorkspace: function(w, workspace) {
262
if (w.get_workspace() == workspace)
267
_sortAppIcon : function(appIcon1, appIcon2) {
268
let t1 = appIcon1.cachedWindows[0].get_user_time();
269
let t2 = appIcon2.cachedWindows[0].get_user_time();
270
if (t2 > t1) return 1;