├── stylesheet.css
├── metadata.json
├── README.md
└── extension.js
/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* This extensions requires no special styling */
2 | .switcher-list .separator {
3 | width: 1px;
4 | background: rgba(255,255,255,0.33);
5 | }
6 |
--------------------------------------------------------------------------------
/metadata.json:
--------------------------------------------------------------------------------
1 | { "shell-version": ["3.8","3.10","3.12","3.16","3.15","3.14","3.13"], "uuid": "workspaceAltTab@KrahnacK", "name": "workspaceAltTab", "description": "AltTab that groups by window and sort according to current workspace (as in gnome-shell 3.6)", "extension-id": "workspaceAltTab", "gettext-domain": "gnome-shell-extensions", "url": "http://krahnack.github.io/workspaceAltTab/" }
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | *** Workspace-aware AltTab for gnome-shell (3.8 and higher)
2 |
3 |
What's that
4 | re-enables gnome-shell 3.6 AltTab behaviour in later versions, so that AltTab
5 | - Main Goal: ensure that a quick Alt-Tab won't switch workspace if you have at least 2 windows opened in the current workspace
6 | - Groups by applications (uses the AppSwitcher, instead of the simple WindowSwitcher)
7 | - Is not restricted to the current workspace (diplays every window)
8 | - Sorts the Apps and Windows according to who's running where (current workspace first, then others)
9 |
10 | Official Gnome Extension URL
11 | https://extensions.gnome.org/extension/748/workspacealttab/
12 |
13 | Screenshot
14 |
15 |
16 | Manual install instructions
17 | - cd ~/.local/share/gnome-shell/extensions
18 | - git clone https://github.com/KrahnacK/workspaceAltTab.git
19 | - mv workspaceAltTab workspaceAltTab@KrahnacK
20 | - restart gnome-shell: Alt+F2 + r, or gnome-shell --replace &
21 |
22 | Debug instructions for gnome-shell extensions
23 | - see the logs in ~/.cache/gdm/session.log or ~/.xsession-errors
24 | - if none of the above work, then get the shell to output in a file: gnome-shell --replace &> shelllog &
25 |
26 |
--------------------------------------------------------------------------------
/extension.js:
--------------------------------------------------------------------------------
1 | /* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
2 |
3 | const Config = imports.misc.config;
4 |
5 | const Clutter = imports.gi.Clutter;
6 | const Lang = imports.lang;
7 | const Meta = imports.gi.Meta;
8 | const Shell = imports.gi.Shell;
9 |
10 | const AltTab = imports.ui.altTab;
11 | const SwitcherPopup = imports.ui.switcherPopup;
12 | const St = imports.gi.St;
13 | const Main = imports.ui.main;
14 |
15 |
16 | let oldCreateSwitcherPopup;
17 | let oldInitAppSwitcher;
18 | let oldGetPreferredHeightAppSwitcher;
19 | let oldInitThumbnailList;
20 |
21 | function isOlderThan(version) {
22 | var myver = Config.PACKAGE_VERSION.split(".");
23 | var refver = version.split(".");
24 | return Number(myver[1]) < Number(refver[1]);
25 | }
26 |
27 | function newInitThumbnailList (windows) {
28 | var parent = Lang.bind(this, SwitcherPopup.SwitcherList.prototype._init);
29 | var addSep = Lang.bind(this, SwitcherPopup.SwitcherList.prototype._addSeparator);
30 | parent(false);
31 |
32 | let activeWorkspace = global.screen.get_active_workspace();
33 |
34 | // We fake the value of 'separatorAdded' when the app has no window
35 | // on the current workspace, to avoid displaying a useless separator in
36 | // that case.
37 | let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
38 |
39 | this._labels = new Array();
40 | this._thumbnailBins = new Array();
41 | this._clones = new Array();
42 | this._windows = windows;
43 |
44 | for (let i = 0; i < windows.length; i++) {
45 | if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
46 | addSep();
47 | separatorAdded = true;
48 | }
49 |
50 | let box = new St.BoxLayout({ style_class: 'thumbnail-box',
51 | vertical: true });
52 |
53 | let bin = new St.Bin({ style_class: 'thumbnail' });
54 |
55 | box.add_actor(bin);
56 | this._thumbnailBins.push(bin);
57 |
58 | let title = windows[i].get_title();
59 | if (title) {
60 | let name = new St.Label({ text: title });
61 | // St.Label doesn't support text-align so use a Bin
62 | let bin = new St.Bin({ x_align: St.Align.MIDDLE });
63 | this._labels.push(bin);
64 | bin.add_actor(name);
65 | box.add_actor(bin);
66 |
67 | this.addItem(box, name);
68 | } else {
69 | this.addItem(box, null);
70 | }
71 | }
72 |
73 | }
74 |
75 | function newGetPreferredHeightAppSwitcher (actor, forWidth, alloc) {
76 | if (isOlderThan("3.12.0")) { //for gnome-shell prior to 3.12
77 | let j = 0;
78 | while(this._items.length > 1 && this._items[j].style_class != 'item-box') {
79 | j++;
80 | }
81 | let themeNode = this._items[j].get_theme_node();
82 | let iconPadding = themeNode.get_horizontal_padding();
83 | let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
84 | let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
85 | let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
86 | let totalSpacing = this._list.spacing * (this._items.length - 1);
87 | if (this._separator)
88 | totalSpacing += this._separator.width + this._list.spacing;
89 |
90 | // We just assume the whole screen here due to weirdness happing with the passed width
91 | let primary = Main.layoutManager.primaryMonitor;
92 | let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
93 | let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding();
94 | let height = 0;
95 |
96 | for(let i = 0; i < AltTab.iconSizes.length; i++) {
97 | this._iconSize = AltTab.iconSizes[i];
98 | height = AltTab.iconSizes[i] + iconSpacing;
99 | let w = height * this._items.length + totalSpacing;
100 | if (w <= availWidth)
101 | break;
102 | }
103 |
104 | if (this._items.length == 1) {
105 | this._iconSize = AltTab.iconSizes[0];
106 | height = AltTab.iconSizes[0] + iconSpacing;
107 | }
108 |
109 | for(let i = 0; i < this.icons.length; i++) {
110 | if (this.icons[i].icon != null)
111 | break;
112 | this.icons[i].set_size(this._iconSize);
113 | }
114 |
115 | alloc.min_size = height;
116 | alloc.natural_size = height;
117 | } else { //for gnome-shell 3.12+
118 | //thanks to Pavel N. Krivitsky
119 | this._setIconSize();
120 |
121 | let maxChildMin = 0;
122 | let maxChildNat = 0;
123 |
124 | for (let i = 0; i < this._items.length; i++) {
125 | let [childMin, childNat] = this._items[i].get_preferred_height(-1);
126 | maxChildMin = Math.max(childMin, maxChildMin);
127 | maxChildNat = Math.max(childNat, maxChildNat);
128 | }
129 |
130 | if (this._squareItems) {
131 | let [childMin, childNat] = this._maxChildWidth(-1);
132 | maxChildMin = Math.max(childMin, maxChildMin);
133 | maxChildNat = maxChildMin;
134 | }
135 |
136 | alloc.min_size = maxChildMin;
137 | alloc.natural_size = maxChildNat;
138 | }
139 | }
140 |
141 | function _newInitAppSwitcher(localApps, otherApps, altTabPopup) {
142 | var parent = Lang.bind(this, SwitcherPopup.SwitcherList.prototype._init);
143 | var addSep = Lang.bind(this, SwitcherPopup.SwitcherList.prototype._addSeparator);
144 |
145 | //from gnome-shell 3.8
146 | parent(true);
147 |
148 | //from gnome-shell 3.6 part
149 |
150 | // Construct the AppIcons, add to the popup
151 | let activeWorkspace = global.screen.get_active_workspace();
152 | let workspaceIcons = [];
153 | let otherIcons = [];
154 |
155 | //re-order localApps in case a window from another
156 | //workspace was raised
157 | let localAppIndex = -1;
158 | for (let i = 0; i < localApps.length; i++) {
159 | let isRaised = true;
160 | let w = localApps[i].get_windows();
161 | for (let j = 0; j < w.length; j++) {
162 | isRaised &= w[j].get_workspace() != activeWorkspace;
163 | }
164 | if (!isRaised) {
165 | localAppIndex = i;
166 | break;
167 | }
168 | }
169 | let localAppToMove = localApps.splice(localAppIndex, 1);
170 | localApps.unshift(localAppToMove[0]);
171 |
172 | for (let i = 0; i < localApps.length; i++) {
173 | let appIcon = new AltTab.AppIcon(localApps[i]);
174 | // Cache the window list now; we don't handle dynamic changes here,
175 | // and we don't want to be continually retrieving it
176 | appIcon.cachedWindows = appIcon.app.get_windows();
177 | workspaceIcons.push(appIcon);
178 | }
179 | for (let i = 0; i < otherApps.length; i++) {
180 | let appIcon = new AltTab.AppIcon(otherApps[i]);
181 | appIcon.cachedWindows = appIcon.app.get_windows();
182 | otherIcons.push(appIcon);
183 | }
184 |
185 | this.icons = [];
186 | this._arrows = [];
187 | for (let i = 0; i < workspaceIcons.length; i++)
188 | this._addIcon(workspaceIcons[i]);
189 | if (workspaceIcons.length > 0 && otherIcons.length > 0)
190 | addSep();
191 | for (let i = 0; i < otherIcons.length; i++)
192 | this._addIcon(otherIcons[i]);
193 |
194 | this._curApp = -1;
195 | this._iconSize = 0;
196 | this._altTabPopup = altTabPopup;
197 | this._mouseTimeOutId = 0;
198 |
199 | //and end with a part from gnome-shell 3.8
200 | this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
201 | }
202 |
203 | function newCreateSwitcherPopup() {
204 |
205 | let tracker = Shell.WindowTracker.get_default();
206 | let windows = global.display.get_tab_list(Meta.TabList.NORMAL, global.screen, global.screen.get_active_workspace());
207 | let allApps = Shell.AppSystem.get_default().get_running ();
208 |
209 | // windows is only the windows on the current workspace. For
210 | // each one, if it corresponds to an app we know, move that
211 | // app from allApps to apps.
212 | let apps = [];
213 | for (let i = 0; i < windows.length && allApps.length != 0; i++) {
214 | let app = tracker.get_window_app(windows[i]);
215 | let index = allApps.indexOf(app);
216 | if (index != -1) {
217 | apps.push(app);
218 | allApps.splice(index, 1);
219 | }
220 | }
221 |
222 | if (apps.length == 0 && allApps.length == 0)
223 | return false;
224 |
225 | this._switcherList = new AltTab.AppSwitcher(apps, allApps, this);
226 | this._items = this._switcherList.icons;
227 |
228 | return true;
229 | }
230 |
231 | //SwitcherList modifications
232 | let oldAddSeparator;
233 | let oldGetPreferredWidth;
234 | let oldAllocate;
235 |
236 | function newAddSeparator() {
237 | let box = new St.Bin({ style_class: 'separator' });
238 | this._separator = box;
239 | this._list.add_actor(box);
240 | }
241 |
242 | function newGetPreferredWidth(actor, forHeight, alloc) {
243 | let [maxChildMin, maxChildNat] = this._maxChildWidth(forHeight);
244 |
245 | let separatorWidth = 0;
246 | if (this._separator) {
247 | let [sepMin, sepNat] = this._separator.get_preferred_width(forHeight);
248 | separatorWidth = sepNat + this._list.spacing;
249 | }
250 |
251 | let totalSpacing = this._list.spacing * (this._items.length - 1);
252 | alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
253 | alloc.natural_size = alloc.min_size;
254 | this._minSize = alloc.min_size;
255 | }
256 |
257 | function newAllocate(actor, box, flags) {
258 | let childHeight = box.y2 - box.y1;
259 |
260 | let [maxChildMin, maxChildNat] = this._maxChildWidth(childHeight);
261 | let totalSpacing = this._list.spacing * (this._items.length - 1);
262 |
263 | let separatorWidth = 0;
264 | if (this._separator) {
265 | let [sepMin, sepNat] = this._separator.get_preferred_width(childHeight);
266 | separatorWidth = sepNat;
267 | totalSpacing += this._list.spacing;
268 | }
269 |
270 | let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing - separatorWidth) / this._items.length);
271 |
272 | let x = 0;
273 | let children = this._list.get_children();
274 | let childBox = new Clutter.ActorBox();
275 |
276 | let primary = Main.layoutManager.primaryMonitor;
277 | let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
278 |
279 | for (let i = 0; i < children.length; i++) {
280 | if (this._items.indexOf(children[i]) != -1) {
281 | let [childMin, childNat] = children[i].get_preferred_height(childWidth);
282 | let vSpacing = (childHeight - childNat) / 2;
283 | childBox.x1 = x;
284 | childBox.y1 = vSpacing;
285 | childBox.x2 = x + childWidth;
286 | childBox.y2 = childBox.y1 + childNat;
287 | children[i].allocate(childBox, flags);
288 |
289 | x += this._list.spacing + childWidth;
290 | } else if (children[i] == this._separator) {
291 | // We want the separator to be more compact than the rest.
292 | childBox.x1 = x;
293 | childBox.y1 = 0;
294 | childBox.x2 = x + separatorWidth;
295 | childBox.y2 = childHeight;
296 | children[i].allocate(childBox, flags);
297 | x += this._list.spacing + separatorWidth;
298 | } else {
299 | // Something else, eg, AppSwitcher's arrows;
300 | // we don't allocate it.
301 | }
302 | }
303 | }
304 |
305 | function init() {
306 | oldCreateSwitcherPopup = AltTab.AppSwitcherPopup.prototype._createSwitcher;
307 | oldInitAppSwitcher = AltTab.AppSwitcher.prototype._init;
308 | oldGetPreferredHeightAppSwitcher = AltTab.AppSwitcher.prototype._getPreferredHeight;
309 | oldInitThumbnailList = AltTab.ThumbnailList.prototype._init;
310 |
311 | //SwitcherList modifications
312 | oldAddSeparator = SwitcherPopup.SwitcherList.prototype._addSeparator; //inexistant as of now
313 | oldGetPreferredWidth = SwitcherPopup.SwitcherList.prototype._getPreferredWidth;
314 | oldAllocate = SwitcherPopup.SwitcherList.prototype._allocate;
315 | }
316 |
317 | function enable() {
318 | AltTab.AppSwitcherPopup.prototype._createSwitcher = newCreateSwitcherPopup;
319 | AltTab.AppSwitcher.prototype._init = _newInitAppSwitcher;
320 | AltTab.AppSwitcher.prototype._getPreferredHeight = newGetPreferredHeightAppSwitcher;
321 | AltTab.ThumbnailList.prototype._init = newInitThumbnailList;
322 |
323 | //SwitcherList modifications
324 | SwitcherPopup.SwitcherList.prototype._addSeparator = newAddSeparator;
325 | SwitcherPopup.SwitcherList.prototype._allocate = newAllocate;
326 | SwitcherPopup.SwitcherList.prototype._getPreferredWidth= newGetPreferredWidth;
327 | }
328 |
329 | function disable() {
330 | AltTab.AppSwitcherPopup.prototype._createSwitcher = oldCreateSwitcherPopup;
331 | AltTab.AppSwitcher.prototype._init = oldInitAppSwitcher;
332 | AltTab.AppSwitcher.prototype._getPreferredHeight = oldGetPreferredHeightAppSwitcher;
333 | AltTab.ThumbnailList.prototype._init = oldInitThumbnailList;
334 |
335 | //SwitcherList modifications
336 | SwitcherPopup.SwitcherList.prototype._addSeparator = oldAddSeparator;
337 | SwitcherPopup.SwitcherList.prototype._allocate = oldAllocate;
338 | SwitcherPopup.SwitcherList.prototype._getPreferredWidth= oldGetPreferredWidth;
339 | }
340 |
--------------------------------------------------------------------------------