summaryrefslogtreecommitdiff
path: root/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js')
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js337
1 files changed, 337 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js
new file mode 100644
index 0000000..9668af2
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js
@@ -0,0 +1,337 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+import {
+ GLib,
+ Meta,
+ Shell,
+} from './dependencies/gi.js';
+
+import {
+ Docking,
+ Utils,
+} from './imports.js';
+
+const {signals: Signals} = imports;
+
+// A good compromise between reactivity and efficiency; to be tuned.
+const INTELLIHIDE_CHECK_INTERVAL = 100;
+
+const OverlapStatus = Object.freeze({
+ UNDEFINED: -1,
+ FALSE: 0,
+ TRUE: 1,
+});
+
+const IntellihideMode = Object.freeze({
+ ALL_WINDOWS: 0,
+ FOCUS_APPLICATION_WINDOWS: 1,
+ MAXIMIZED_WINDOWS: 2,
+ ALWAYS_ON_TOP: 3,
+});
+
+// List of windows type taken into account. Order is important (keep the original
+// enum order).
+const handledWindowTypes = [
+ Meta.WindowType.NORMAL,
+ Meta.WindowType.DOCK,
+ Meta.WindowType.DIALOG,
+ Meta.WindowType.MODAL_DIALOG,
+ Meta.WindowType.TOOLBAR,
+ Meta.WindowType.MENU,
+ Meta.WindowType.UTILITY,
+ Meta.WindowType.SPLASHSCREEN,
+ Meta.WindowType.DROPDOWN_MENU,
+];
+
+// List of applications, ignore windows of these applications in considering intellihide
+const ignoreApps = ['com.rastersoft.ding', 'com.desktop.ding'];
+
+/**
+ * A rough and ugly implementation of the intellihide behaviour.
+ * Intallihide object: emit 'status-changed' signal when the overlap of windows
+ * with the provided targetBoxClutter.ActorBox changes;
+ */
+export class Intellihide {
+ constructor(monitorIndex) {
+ // Load settings
+ this._monitorIndex = monitorIndex;
+
+ this._signalsHandler = new Utils.GlobalSignalsHandler();
+ this._tracker = Shell.WindowTracker.get_default();
+ this._focusApp = null; // The application whose window is focused.
+ this._topApp = null; // The application whose window is on top on the monitor with the dock.
+
+ this._isEnabled = false;
+ this._status = OverlapStatus.UNDEFINED;
+ this._targetBox = null;
+
+ this._checkOverlapTimeoutContinue = false;
+ this._checkOverlapTimeoutId = 0;
+
+ this._trackedWindows = new Map();
+
+ // Connect global signals
+ this._signalsHandler.add([
+ // Add signals on windows created from now on
+ global.display,
+ 'window-created',
+ this._windowCreated.bind(this),
+ ], [
+ // triggered for instance when the window list order changes,
+ // included when the workspace is switched
+ global.display,
+ 'restacked',
+ this._checkOverlap.bind(this),
+ ], [
+ // when windows are alwasy on top, the focus window can change
+ // without the windows being restacked. Thus monitor window focus change.
+ this._tracker,
+ 'notify::focus-app',
+ this._checkOverlap.bind(this),
+ ], [
+ // update wne monitor changes, for instance in multimonitor when monitor are attached
+ Utils.getMonitorManager(),
+ 'monitors-changed',
+ this._checkOverlap.bind(this),
+ ]);
+ }
+
+ destroy() {
+ // Disconnect global signals
+ this._signalsHandler.destroy();
+
+ // Remove residual windows signals
+ this.disable();
+ }
+
+ enable() {
+ this._isEnabled = true;
+ this._status = OverlapStatus.UNDEFINED;
+ global.get_window_actors().forEach(function (wa) {
+ this._addWindowSignals(wa);
+ }, this);
+ this._doCheckOverlap();
+ }
+
+ disable() {
+ this._isEnabled = false;
+
+ for (const wa of this._trackedWindows.keys())
+ this._removeWindowSignals(wa);
+
+ this._trackedWindows.clear();
+
+ if (this._checkOverlapTimeoutId > 0) {
+ GLib.source_remove(this._checkOverlapTimeoutId);
+ this._checkOverlapTimeoutId = 0;
+ }
+ }
+
+ _windowCreated(display, metaWindow) {
+ this._addWindowSignals(metaWindow.get_compositor_private());
+ this._doCheckOverlap();
+ }
+
+ _addWindowSignals(wa) {
+ if (!this._handledWindow(wa))
+ return;
+ const signalId = wa.connect('notify::allocation', this._checkOverlap.bind(this));
+ this._trackedWindows.set(wa, signalId);
+ wa.connect('destroy', this._removeWindowSignals.bind(this));
+ }
+
+ _removeWindowSignals(wa) {
+ if (this._trackedWindows.get(wa)) {
+ wa.disconnect(this._trackedWindows.get(wa));
+ this._trackedWindows.delete(wa);
+ }
+ }
+
+ updateTargetBox(box) {
+ this._targetBox = box;
+ this._checkOverlap();
+ }
+
+ forceUpdate() {
+ this._status = OverlapStatus.UNDEFINED;
+ this._doCheckOverlap();
+ }
+
+ getOverlapStatus() {
+ return this._status === OverlapStatus.TRUE;
+ }
+
+ _checkOverlap() {
+ if (!this._isEnabled || !this._targetBox)
+ return;
+
+ /* Limit the number of calls to the doCheckOverlap function */
+ if (this._checkOverlapTimeoutId) {
+ this._checkOverlapTimeoutContinue = true;
+ return;
+ }
+
+ this._doCheckOverlap();
+
+ this._checkOverlapTimeoutId = GLib.timeout_add(
+ GLib.PRIORITY_DEFAULT, INTELLIHIDE_CHECK_INTERVAL, () => {
+ this._doCheckOverlap();
+ if (this._checkOverlapTimeoutContinue) {
+ this._checkOverlapTimeoutContinue = false;
+ return GLib.SOURCE_CONTINUE;
+ } else {
+ this._checkOverlapTimeoutId = 0;
+ return GLib.SOURCE_REMOVE;
+ }
+ });
+ }
+
+ _doCheckOverlap() {
+ if (!this._isEnabled || !this._targetBox)
+ return;
+
+ let overlaps = OverlapStatus.FALSE;
+ let windows = global.get_window_actors().filter(wa => this._handledWindow(wa));
+
+ if (windows.length > 0) {
+ /*
+ * Get the top window on the monitor where the dock is placed.
+ * The idea is that we dont want to overlap with the windows of the topmost application,
+ * event is it's not the focused app -- for instance because in multimonitor the user
+ * select a window in the secondary monitor.
+ */
+
+ let topWindow = null;
+ for (let i = windows.length - 1; i >= 0; i--) {
+ const metaWin = windows[i].get_meta_window();
+ if (metaWin.get_monitor() === this._monitorIndex) {
+ topWindow = metaWin;
+ break;
+ }
+ }
+
+ if (topWindow) {
+ this._topApp = this._tracker.get_window_app(topWindow);
+ // If there isn't a focused app, use that of the window on top
+ this._focusApp = this._tracker.focus_app || this._topApp;
+
+ windows = windows.filter(this._intellihideFilterInteresting, this);
+
+ for (let i = 0; i < windows.length; i++) {
+ const win = windows[i].get_meta_window();
+
+ if (win) {
+ const rect = win.get_frame_rect();
+
+ const test = (rect.x < this._targetBox.x2) &&
+ (rect.x + rect.width >= this._targetBox.x1) &&
+ (rect.y < this._targetBox.y2) &&
+ (rect.y + rect.height >= this._targetBox.y1);
+
+ if (test) {
+ overlaps = OverlapStatus.TRUE;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (this._status !== overlaps) {
+ this._status = overlaps;
+ this.emit('status-changed', this._status);
+ }
+ }
+
+ // Filter interesting windows to be considered for intellihide.
+ // Consider all windows visible on the current workspace.
+ // Optionally skip windows of other applications
+ _intellihideFilterInteresting(wa) {
+ const metaWin = wa.get_meta_window();
+ const currentWorkspace = global.workspace_manager.get_active_workspace_index();
+ const workspace = metaWin.get_workspace();
+ const workspaceIndex = workspace.index();
+
+ // Depending on the intellihide mode, exclude non-relevent windows
+ switch (Docking.DockManager.settings.intellihideMode) {
+ case IntellihideMode.ALL_WINDOWS:
+ // Do nothing
+ break;
+
+ case IntellihideMode.FOCUS_APPLICATION_WINDOWS:
+ // Skip windows of other apps
+ if (this._focusApp) {
+ // The DropDownTerminal extension is not an application per se
+ // so we match its window by wm class instead
+ if (metaWin.get_wm_class() === 'DropDownTerminalWindow')
+ return true;
+
+ const currentApp = this._tracker.get_window_app(metaWin);
+ const focusWindow = global.display.get_focus_window();
+
+ // Consider half maximized windows side by side
+ // and windows which are alwayson top
+ if (currentApp !== this._focusApp && currentApp !== this._topApp &&
+ !((focusWindow && focusWindow.maximized_vertically &&
+ !focusWindow.maximized_horizontally) &&
+ (metaWin.maximized_vertically && !metaWin.maximized_horizontally) &&
+ metaWin.get_monitor() === focusWindow.get_monitor()) &&
+ !metaWin.is_above())
+ return false;
+ }
+ break;
+
+ case IntellihideMode.MAXIMIZED_WINDOWS:
+ // Skip unmaximized windows
+ if (!metaWin.maximized_vertically && !metaWin.maximized_horizontally && !metaWin.fullscreen)
+ return false;
+ break;
+
+ case IntellihideMode.ALWAYS_ON_TOP:
+ // Always on top, except for fullscreen windows
+ if (this._focusApp) {
+ const {focusWindow} = global.display;
+ if (!focusWindow?.fullscreen)
+ return false;
+ }
+ break;
+ }
+
+ if (workspaceIndex === currentWorkspace && metaWin.showing_on_its_workspace())
+ return true;
+ else
+ return false;
+ }
+
+ // Filter windows by type
+ // inspired by Opacify@gnome-shell.localdomain.pl
+ _handledWindow(wa) {
+ const metaWindow = wa.get_meta_window();
+
+ if (!metaWindow)
+ return false;
+
+ // The DING extension desktop window needs to be excluded
+ // so we match its window by application id and window property.
+ const wmApp = metaWindow.get_gtk_application_id();
+ if (ignoreApps.includes(wmApp) && metaWindow.is_skip_taskbar())
+ return false;
+
+ // The DropDownTerminal extension uses the POPUP_MENU window type hint
+ // so we match its window by wm class instead
+ if (metaWindow.get_wm_class() === 'DropDownTerminalWindow')
+ return true;
+
+ const wtype = metaWindow.get_window_type();
+ for (let i = 0; i < handledWindowTypes.length; i++) {
+ const hwtype = handledWindowTypes[i];
+ if (hwtype === wtype)
+ return true;
+ else if (hwtype > wtype)
+ return false;
+ }
+ return false;
+ }
+}
+
+Signals.addSignalMethods(Intellihide.prototype);