forked from passingthru67/workspaces-to-dock
/
intellihide.js
executable file
·387 lines (337 loc) · 13.2 KB
/
intellihide.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/* ========================================================================================================
* intellihide.js - intellihide functions
* --------------------------------------------------------------------------------------------------------
* CREDITS: This code was copied from the dash-to-dock extension https://github.com/micheleg/dash-to-dock
* and modified to create a workspaces dock. Many thanks to michele_g for a great extension.
* ========================================================================================================
*/
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const handledWindowTypes = [
Meta.WindowType.NORMAL,
// Meta.WindowType.DESKTOP, // skip nautilus dekstop window
// Meta.WindowType.DOCK, // skip other docks
Meta.WindowType.DIALOG,
Meta.WindowType.MODAL_DIALOG,
Meta.WindowType.TOOLBAR,
Meta.WindowType.MENU,
Meta.WindowType.POPUP_MENU,
Meta.WindowType.DROPDOWN_MENU,
Meta.WindowType.UTILITY,
Meta.WindowType.SPLASHSCREEN,
Meta.WindowType.TOOLTIP
];
const IntellihideMode = {
HIDE: 0, // Workspaces is always invisible
SHOW: 1, // Workspaces is always visible
AUTOHIDE: 2, // Basic autohide mode: visible on mouse hover
INTELLIHIDE: 3 // Basic intellihide mode: visible if no window overlap the workspaces
};
const OVERVIEW_MODE = IntellihideMode.SHOW;
/*
* A rough and ugly implementation of the intellihide behaviour.
* Intallihide object: call show()/hide() function based on the overlap with the
* the target actor object;
*
* Target object has to contain a Clutter.ActorBox object named staticBox and
* emit a 'box-changed' signal when this changes.
*
*/
let intellihide = function(show, hide, target, settings) {
this._init(show, hide, target, settings);
}
intellihide.prototype = {
_init: function(show, hide, target, settings) {
// Load settings
this._settings = settings;
this._bindSettingsChanges();
this._signalHandler = new Convenience.globalSignalHandler();
// current intellihide status
this.status;
// manually temporary disable intellihide update
this._disableIntellihide = false;
// Set base functions
this.showFunction = show;
this.hideFunction = hide;
// Target object
this._target = target;
// Keep track of the current overview mode (I mean if it is on/off)
this._inOverview = false;
// Main id of the timeout controlling timeout for updateDockVisibility function
// when windows are dragged around (move and resize)
this._windowChangedTimeout = 0;
// Connect global signals
this._signalHandler.push(
// call updateVisibility when target actor changes
[
this._target,
'box-changed',
Lang.bind(this, this._updateDockVisibility)
],
// Add timeout when window grab-operation begins and remove it when it ends.
// These signals only exist starting from Gnome-Shell 3.4
[
global.display,
'grab-op-begin',
Lang.bind(this, this._grabOpBegin)
],
[
global.display,
'grab-op-end',
Lang.bind(this, this._grabOpEnd)
],
// direct maximize/unmazimize are not included in grab-operations
[
global.window_manager,
'maximize',
Lang.bind(this, this._updateDockVisibility )
],
[
global.window_manager,
'unmaximize',
Lang.bind(this, this._updateDockVisibility )
],
// Probably this is also included in restacked?
[
global.window_manager,
'switch-workspace',
Lang.bind(this, this._switchWorkspace)
],
// trigggered for instance when a window is closed.
[
global.screen,
'restacked',
Lang.bind(this, this._updateDockVisibility)
],
// Set visibility in overview mode
[
Main.overview,
'showing',
Lang.bind(this, this._overviewEnter)
],
[
Main.overview,
'hiding',
Lang.bind(this,this._overviewExit)
],
// update when monitor changes, for instance in multimonitor when monitors are attached
[
global.screen,
'monitors-changed',
Lang.bind(this, this._updateDockVisibility)
],
// TODO: need to detect if the messageTray is actually in its normal position ?
// we are assuming the messageTray is located at its normal position at the right bottom corner
[
Main.messageTray._focusGrabber,
'focus-grabbed',
Lang.bind(this, this._onTrayFocusGrabbed)
],
[
Main.messageTray._focusGrabber,
'focus-ungrabbed',
Lang.bind(this, this._onTrayFocusUngrabbed)
]
);
// Detect viewSelector Tab signals in overview mode
for (let i = 0; i < Main.overview._viewSelector._tabs.length; i++) {
this._signalHandler.push([Main.overview._viewSelector._tabs[i], 'activated', Lang.bind(this, this._overviewChanged)]);
}
// initialize: call show forcing to initialize status variable
this._show(true);
// update visibility
this._updateDockVisibility();
// Start main loop and bind initialize function
Mainloop.idle_add(Lang.bind(this, this._initialize));
},
_initialize: function() {
// Detect gnome panel popup menus. Reason for detection being here instead of during init
// is because it needs to be done after all the panel extensions have loaded
for (let i = 0; i < Main.panel._menus._menus.length; i++) {
this._signalHandler.push([Main.panel._menus._menus[i].menu, 'open-state-changed', Lang.bind(this, this._onPanelMenuStateChange)]);
}
},
destroy: function() {
// Disconnect global signals
this._signalHandler.disconnect();
if (this._windowChangedTimeout > 0)
Mainloop.source_remove(this._windowChangedTimeout); // Just to be sure
},
_bindSettingsChanges: function() {
this._settings.connect('changed::intellihide', Lang.bind(this, function() {
this._updateDockVisibility();
}));
this._settings.connect('changed::dock-fixed', Lang.bind(this, function() {
if (this._settings.get_boolean('dock-fixed')) {
this.status = true; // Since the dock is now shown
} else {
// Wait that windows rearragne after struts change
Mainloop.idle_add(Lang.bind(this, function() {
this._updateDockVisibility();
return false;
}));
}
}));
},
_show: function(force) {
if (this.status == false || force) {
this.status = true;
this.showFunction();
}
},
_hide: function(force) {
if (this.status == true || force) {
this.status = false;
this.hideFunction();
}
},
_overviewExit: function() {
this._inOverview = false;
this._updateDockVisibility();
},
_overviewEnter: function() {
this._inOverview = true;
if (OVERVIEW_MODE == IntellihideMode.SHOW) {
this._show();
} else if (OVERVIEW_MODE == IntellihideMode.AUTOHIDE) {
this._hide();
} else if (OVERVIEW_MODE == IntellihideMode.INTELLIHIDE) {
this._show();
} else if (OVERVIEW_MODE == IntellihideMode.HIDE) {
/*TODO*/
}
},
// This function was added to handle changes in overview mode
// for example, when Applications tab is clicked the workspaces dock is hidden
_overviewChanged: function() {
if (Main.overview._viewSelector._activeTab.id == "windows") {
this._show();
} else {
this._hide();
}
},
_onTrayFocusGrabbed: function(actor, event) {
this._disableIntellihide = true;
this._hide();
},
_onTrayFocusUngrabbed: function(actor, event) {
this._disableIntellihide = false;
if (this._inOverview) {
this._show();
} else {
this._updateDockVisibility();
}
},
_onPanelMenuStateChange: function(menu, open) {
if (open) {
let [rx, ry] = menu.actor.get_transformed_position();
let [rwidth, rheight] = menu.actor.get_size();
let test = (rx < this._target.staticBox.x2) && (rx + rwidth > this._target.staticBox.x1) && (ry < this._target.staticBox.y2) && (ry + rheight > this._target.staticBox.y1);
if (test) {
this._hide();
}
} else {
if (this._inOverview) {
this._show();
} else {
this._updateDockVisibility();
}
}
},
_grabOpBegin: function() {
if (this._settings.get_boolean('intellihide')) {
let INTERVAL = 100; // A good compromise between reactivity and efficiency; to be tuned.
if (this._windowChangedTimeout > 0)
Mainloop.source_remove(this._windowChangedTimeout); // Just to be sure
this._windowChangedTimeout = Mainloop.timeout_add(INTERVAL,
Lang.bind(this, function() {
this._updateDockVisibility();
return true; // to make the loop continue
})
);
}
},
_grabOpEnd: function() {
if (this._settings.get_boolean('intellihide')) {
if (this._windowChangedTimeout > 0)
Mainloop.source_remove(this._windowChangedTimeout);
this._updateDockVisibility();
}
},
_switchWorkspace: function(shellwm, from, to, direction) {
this._updateDockVisibility();
},
_updateDockVisibility: function() {
if (this._disableIntellihide)
return;
// If we are in overview mode and the dock is set to be visible prevent
// it to be hidden by window events(window create, workspace change,
// window close...)
if (this._inOverview) {
if (OVERVIEW_MODE !== IntellihideMode.INTELLIHIDE) {
return;
}
}
//else in normal mode:
else if (!this._settings.get_boolean('dock-fixed')) {
if (this._settings.get_boolean('intellihide')) {
let overlaps = false;
let windows = global.get_window_actors().filter(this._intellihideFilterInteresting, this);
for (let i = 0; i < windows.length; i++) {
let win = windows[i].get_meta_window();
if (win) {
let rect = win.get_outer_rect();
let test = (rect.x < this._target.staticBox.x2) && (rect.x + rect.width > this._target.staticBox.x1) && (rect.y < this._target.staticBox.y2) && (rect.y + rect.height > this._target.staticBox.y1);
if (test) {
overlaps = true;
break;
}
}
}
if (overlaps) {
this._hide();
} else {
this._show();
}
} else {
this._hide();
}
}
},
// Filter interesting windows to be considered for intellihide.
// Consider all windows visible on the current workspace.
_intellihideFilterInteresting: function(wa, edge) {
var currentWorkspace = global.screen.get_active_workspace_index();
var meta_win = wa.get_meta_window();
if (!meta_win) { //TODO michele: why? What does it mean?
return false;
}
if (!this._handledWindowType(meta_win))
return false;
var wksp = meta_win.get_workspace();
var wksp_index = wksp.index();
if (wksp_index == currentWorkspace && meta_win.showing_on_its_workspace()) {
return true;
} else {
return false;
}
},
// Filter windows by type
// inspired by Opacify@gnome-shell.localdomain.pl
_handledWindowType: function(metaWindow) {
var wtype = metaWindow.get_window_type();
for (var i = 0; i < handledWindowTypes.length; i++) {
var hwtype = handledWindowTypes[i];
if (hwtype == wtype) {
return true;
} else if (hwtype > wtype) {
return false;
}
}
return false;
}
};