diff options
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.js | 337 |
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); |