diff options
Diffstat (limited to 'extensions/44/vertical-workspaces/lib')
29 files changed, 6083 insertions, 3181 deletions
diff --git a/extensions/44/vertical-workspaces/lib/appDisplay.js b/extensions/44/vertical-workspaces/lib/appDisplay.js index 2ac70b1..aeb2808 100644 --- a/extensions/44/vertical-workspaces/lib/appDisplay.js +++ b/extensions/44/vertical-workspaces/lib/appDisplay.js @@ -10,246 +10,501 @@ 'use strict'; -const { Clutter, GLib, GObject, Meta, Shell, St, Graphene, Pango } = imports.gi; +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Graphene = imports.gi.Graphene; +const Meta = imports.gi.Meta; +const Pango = imports.gi.Pango; +const Shell = imports.gi.Shell; +const St = imports.gi.St; -const DND = imports.ui.dnd; -const Main = imports.ui.main; const AppDisplay = imports.ui.appDisplay; +const DND = imports.ui.dnd; const IconGrid = imports.ui.iconGrid; +const Main = imports.ui.main; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const IconGridOverride = Me.imports.lib.iconGrid; -const _Util = Me.imports.lib.util; +let Me; +let opt; -const DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x00000022); -const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000000); +let _timeouts; -// gettext -const _ = Me.imports.lib.settings._; +// DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x00000022); +// DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000000); -let _overrides; +var AppDisplayModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -let _appGridLayoutSettings; -let _appDisplayScrollConId; -let _appSystemStateConId; -let _appGridLayoutConId; -let _origAppViewItemAcceptDrop; -let _updateFolderIcons; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; -let opt; -let shellVersion = _Util.shellVersion; -let _firstRun = true; + this._appGridLayoutSettings = null; + this._appDisplayScrollConId = 0; + this._appSystemStateConId = 0; + this._appGridLayoutConId = 0; + this._origAppViewItemAcceptDrop = null; + this._updateFolderIcons = 0; + } -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('appDisplayModule', true); - reset = reset || !moduleEnabled; + cleanGlobals() { + Me = null; + opt = null; + } - // don't even touch this module if disabled - if (_firstRun && reset) - return; + update(reset) { + this._removeTimeouts(); + this.moduleEnabled = opt.get('appDisplayModule'); + const conflict = false; - _firstRun = false; + reset = reset || !this.moduleEnabled || conflict; - if (_overrides) - _overrides.removeAll(); + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + this.moduleEnabled = false; + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) { + this.moduleEnabled = false; + console.debug(' AppDisplayModule - Keeping untouched'); + } + } - if (reset) { - _setAppDisplayOrientation(false); - _updateAppGridProperties(reset); - _updateAppGridDND(reset); - _restoreOverviewGroup(); - _overrides = null; - opt = null; - return; + _activateModule() { + Me.Modules.iconGridModule.update(); + + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + _timeouts = {}; + + // Common + this._overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView); + this._overrides.addOverride('FolderIcon', AppDisplay.FolderIcon.prototype, FolderIcon); + if (opt.APP_GRID_ACTIVE_PREVIEW) + this._overrides.addOverride('ActiveFolderIcon', AppDisplay.FolderIcon, ActiveFolderIcon); + this._overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); + this._overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); + this._overrides.addOverride('AppViewItem', AppDisplay.AppViewItem.prototype, AppViewItemCommon); + this._overrides.addOverride('BaseAppViewCommon', AppDisplay.BaseAppView.prototype, BaseAppViewCommon); + + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { + this._overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical); + this._overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical); + } + + // Custom App Grid + this._overrides.addOverride('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialog); + if (Me.shellVersion >= 43) { + // const defined class needs to be touched before real access + this._dummy = AppDisplay.AppGrid; + delete this._dummy; + // BaseAppViewGridLayout is not exported, we can only access current instance + this._overrides.addOverride('BaseAppViewGridLayout', Main.overview._overview.controls._appDisplay._appGridLayout, BaseAppViewGridLayout); + this._overrides.addOverride('FolderGrid', AppDisplay.FolderGrid.prototype, FolderGrid); + } else { + this._overrides.addOverride('FolderGrid', AppDisplay.FolderGrid.prototype, FolderGridLegacy); + } + + this._setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL); + this._updateDND(); + if (!Main.sessionMode.isGreeter) + this._updateAppDisplayProperties(); + + console.debug(' AppDisplayModule - Activated'); } - _overrides = new _Util.Overrides(); + _disableModule() { + Me.Modules.iconGridModule.update(true); + + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; - if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { - _overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical); - _overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical); + const reset = true; + this._setAppDisplayOrientation(false); + this._updateAppDisplayProperties(reset); + this._updateDND(reset); + this._restoreOverviewGroup(); + this._removeStatusMessage(); + + console.debug(' AppDisplayModule - Disabled'); } - // Custom App Grid - _overrides.addOverride('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialog); - if (shellVersion >= 43) { - // const defined class needs to be touched before real access - AppDisplay.BaseAppViewGridLayout; - _overrides.addOverride('BaseAppViewGridLayout', AppDisplay.BaseAppViewGridLayout.prototype, BaseAppViewGridLayout); + _removeTimeouts() { + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + _timeouts = null; + } } - _overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView); - _overrides.addOverride('FolderIcon', AppDisplay.FolderIcon.prototype, FolderIcon); - _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); - _overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); - _overrides.addOverride('AppViewItem', AppDisplay.AppViewItem.prototype, AppViewItemCommon); - _overrides.addOverride('BaseAppViewCommon', AppDisplay.BaseAppView.prototype, BaseAppViewCommon); - - _setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL); - _updateAppGridProperties(); - _updateAppGridDND(); - opt._appGridNeedsRedisplay = true; -} - -function _setAppDisplayOrientation(vertical = false) { - const CLUTTER_ORIENTATION = vertical ? Clutter.Orientation.VERTICAL : Clutter.Orientation.HORIZONTAL; - const scroll = vertical ? 'vscroll' : 'hscroll'; - // app display to vertical has issues - page indicator not working - // global appDisplay orientation switch is not built-in - let appDisplay = Main.overview._overview._controls._appDisplay; - // following line itself only changes in which axis will operate overshoot detection which switches appDisplay pages while dragging app icon to vertical - appDisplay._orientation = CLUTTER_ORIENTATION; - appDisplay._grid.layoutManager._orientation = CLUTTER_ORIENTATION; - appDisplay._swipeTracker.orientation = CLUTTER_ORIENTATION; - appDisplay._swipeTracker._reset(); - if (vertical) { - appDisplay._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL); - - // move and change orientation of page indicators - // needs corrections in appgrid page calculations, e.g. appDisplay.adaptToSize() fnc, - // which complicates use of super call inside the function - const pageIndicators = appDisplay._pageIndicators; - pageIndicators.vertical = true; - appDisplay._box.vertical = false; - pageIndicators.x_expand = false; - pageIndicators.y_align = Clutter.ActorAlign.CENTER; - pageIndicators.x_align = Clutter.ActorAlign.START; - - const scrollContainer = appDisplay._scrollView.get_parent(); - if (shellVersion < 43) { - // remove touch friendly side navigation bars / arrows - if (appDisplay._hintContainer && appDisplay._hintContainer.get_parent()) - scrollContainer.remove_child(appDisplay._hintContainer); + + _setAppDisplayOrientation(vertical = false) { + const CLUTTER_ORIENTATION = vertical ? Clutter.Orientation.VERTICAL : Clutter.Orientation.HORIZONTAL; + const scroll = vertical ? 'vscroll' : 'hscroll'; + // app display to vertical has issues - page indicator not working + // global appDisplay orientation switch is not built-in + let appDisplay = Main.overview._overview._controls._appDisplay; + // following line itself only changes in which axis will operate overshoot detection which switches appDisplay pages while dragging app icon to vertical + appDisplay._orientation = CLUTTER_ORIENTATION; + appDisplay._grid.layoutManager._orientation = CLUTTER_ORIENTATION; + appDisplay._swipeTracker.orientation = CLUTTER_ORIENTATION; + appDisplay._swipeTracker._reset(); + if (vertical) { + appDisplay._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL); + + // move and change orientation of page indicators + const pageIndicators = appDisplay._pageIndicators; + pageIndicators.vertical = true; + appDisplay._box.vertical = false; + pageIndicators.x_expand = false; + pageIndicators.y_align = Clutter.ActorAlign.CENTER; + pageIndicators.x_align = Clutter.ActorAlign.START; + + const scrollContainer = appDisplay._scrollView.get_parent(); + if (Me.shellVersion < 43) { + // remove touch friendly side navigation bars / arrows + if (appDisplay._hintContainer && appDisplay._hintContainer.get_parent()) + scrollContainer.remove_child(appDisplay._hintContainer); + } else { + // moving these bars needs more patching of the appDisplay's code + // for now we just change bars style to be more like vertically oriented arrows indicating direction to prev/next page + appDisplay._nextPageIndicator.add_style_class_name('nextPageIndicator'); + appDisplay._prevPageIndicator.add_style_class_name('prevPageIndicator'); + } + + // setting their x_scale to 0 removes the arrows and avoid allocation issues compared to .hide() them + appDisplay._nextPageArrow.scale_x = 0; + appDisplay._prevPageArrow.scale_x = 0; } else { - // moving these bars needs more patching of the appDisplay's code - // for now we just change bars style to be more like vertically oriented arrows indicating direction to prev/next page - appDisplay._nextPageIndicator.add_style_class_name('nextPageIndicator'); - appDisplay._prevPageIndicator.add_style_class_name('prevPageIndicator'); - } + appDisplay._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); + if (this._appDisplayScrollConId) { + appDisplay._adjustment.disconnect(this._appDisplayScrollConId); + this._appDisplayScrollConId = 0; + } - // setting their x_scale to 0 removes the arrows and avoid allocation issues compared to .hide() them - appDisplay._nextPageArrow.scale_x = 0; - appDisplay._prevPageArrow.scale_x = 0; - } else { - appDisplay._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER); - if (_appDisplayScrollConId) { - appDisplay._adjustment.disconnect(_appDisplayScrollConId); - _appDisplayScrollConId = 0; + // restore original page indicators + const pageIndicators = appDisplay._pageIndicators; + pageIndicators.vertical = false; + appDisplay._box.vertical = true; + pageIndicators.x_expand = true; + pageIndicators.y_align = Clutter.ActorAlign.END; + pageIndicators.x_align = Clutter.ActorAlign.CENTER; + + // put back touch friendly navigation bars/buttons + const scrollContainer = appDisplay._scrollView.get_parent(); + if (appDisplay._hintContainer && !appDisplay._hintContainer.get_parent()) { + scrollContainer.add_child(appDisplay._hintContainer); + // the hit container covers the entire app grid and added at the top of the stack blocks DND drops + // so it needs to be pushed below + scrollContainer.set_child_below_sibling(appDisplay._hintContainer, null); + } + + appDisplay._nextPageArrow.scale_x = 1; + appDisplay._prevPageArrow.scale_x = 1; + + appDisplay._nextPageIndicator.remove_style_class_name('nextPageIndicator'); + appDisplay._prevPageIndicator.remove_style_class_name('prevPageIndicator'); } - // restore original page indicators - const pageIndicators = appDisplay._pageIndicators; - pageIndicators.vertical = false; - appDisplay._box.vertical = true; - pageIndicators.x_expand = true; - pageIndicators.y_align = Clutter.ActorAlign.END; - pageIndicators.x_align = Clutter.ActorAlign.CENTER; - - // put back touch friendly navigation bars/buttons - const scrollContainer = appDisplay._scrollView.get_parent(); - if (appDisplay._hintContainer && !appDisplay._hintContainer.get_parent()) { - scrollContainer.add_child(appDisplay._hintContainer); - // the hit container covers the entire app grid and added at the top of the stack blocks DND drops - // so it needs to be pushed below - scrollContainer.set_child_below_sibling(appDisplay._hintContainer, null); + // value for page indicator is calculated from scroll adjustment, horizontal needs to be replaced by vertical + appDisplay._adjustment = appDisplay._scrollView[scroll].adjustment; + + // no need to connect already connected signal (wasn't removed the original one before) + if (!vertical) { + // reset used appDisplay properties + Main.overview._overview._controls._appDisplay.scale_y = 1; + Main.overview._overview._controls._appDisplay.scale_x = 1; + Main.overview._overview._controls._appDisplay.opacity = 255; + return; } - appDisplay._nextPageArrow.scale_x = 1; - appDisplay._prevPageArrow.scale_x = 1; + // update appGrid dot pages indicators + this._appDisplayScrollConId = appDisplay._adjustment.connect('notify::value', adj => { + const value = adj.value / adj.page_size; + appDisplay._pageIndicators.setCurrentPosition(value); + }); + } + + // Set App Grid columns, rows, icon size, incomplete pages + _updateAppDisplayProperties(reset = false) { + opt._appGridNeedsRedisplay = false; + // columns, rows, icon size + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay.visible = true; + if (reset) { + appDisplay._grid.layoutManager.fixedIconSize = -1; + appDisplay._grid.layoutManager.allow_incomplete_pages = true; + appDisplay._grid._currentMode = -1; + appDisplay._grid.setGridModes(); + if (this._appGridLayoutSettings) { + this._appGridLayoutSettings.disconnect(this._appGridLayoutConId); + this._appGridLayoutConId = 0; + this._appGridLayoutSettings = null; + } + appDisplay._redisplay(); - appDisplay._nextPageIndicator.remove_style_class_name('nextPageIndicator'); - appDisplay._prevPageIndicator.remove_style_class_name('prevPageIndicator'); + appDisplay._grid.set_style(''); + this._updateAppGrid(reset); + } else { + // update grid on layout reset + if (!this._appGridLayoutSettings) { + this._appGridLayoutSettings = new Gio.Settings({ schema_id: 'org.gnome.shell' }); + this._appGridLayoutConId = this._appGridLayoutSettings.connect('changed::app-picker-layout', this._updateLayout); + } + + appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; + // appDisplay._grid.set_style(`column-spacing: ${opt.APP_GRID_SPACING}px; row-spacing: ${opt.APP_GRID_SPACING}px;`); + // APP_GRID_SPACING constant is used for grid dimensions calculation + // but sometimes the actual grid spacing properties affect/change the calculated size, therefore we set it lower to avoid this problem + // main app grid always use available space and the spacing is optimized for the grid dimensions + appDisplay._grid.set_style('column-spacing: 5px; row-spacing: 5px;'); + + // force redisplay + appDisplay._grid._currentMode = -1; + appDisplay._grid.setGridModes(); + appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE; + // avoid resetting appDisplay before startup animation + // x11 shell restart skips startup animation + if (!Main.layoutManager._startingUp) { + this._updateAppGrid(); + } else if (Main.layoutManager._startingUp && (Meta.is_restart() || Me.Util.dashIsDashToDock())) { + _timeouts.three = GLib.idle_add(GLib.PRIORITY_LOW, () => { + this._updateAppGrid(); + _timeouts.three = 0; + return GLib.SOURCE_REMOVE; + }); + } + } } - // value for page indicator is calculated from scroll adjustment, horizontal needs to be replaced by vertical - appDisplay._adjustment = appDisplay._scrollView[scroll].adjustment; + _updateDND(reset) { + if (!reset) { + if (!this._appSystemStateConId && opt.APP_GRID_INCLUDE_DASH >= 3) { + this._appSystemStateConId = Shell.AppSystem.get_default().connect( + 'app-state-changed', + () => { + this._updateFolderIcons = true; + Main.overview._overview.controls._appDisplay._redisplay(); + } + ); + } + } else if (this._appSystemStateConId) { + Shell.AppSystem.get_default().disconnect(this._appSystemStateConId); + this._appSystemStateConId = 0; + } + } - // no need to connect already connected signal (wasn't removed the original one before) - if (!vertical) { - // reset used appDisplay properties - Main.overview._overview._controls._appDisplay.scale_y = 1; - Main.overview._overview._controls._appDisplay.scale_x = 1; + _restoreOverviewGroup() { + Main.overview.dash.showAppsButton.checked = false; + Main.layoutManager.overviewGroup.opacity = 255; + Main.layoutManager.overviewGroup.scale_x = 1; + Main.layoutManager.overviewGroup.scale_y = 1; + Main.layoutManager.overviewGroup.hide(); + Main.overview._overview._controls._appDisplay.translation_x = 0; + Main.overview._overview._controls._appDisplay.translation_y = 0; + Main.overview._overview._controls._appDisplay.visible = true; Main.overview._overview._controls._appDisplay.opacity = 255; - return; } - // update appGrid dot pages indicators - _appDisplayScrollConId = appDisplay._adjustment.connect('notify::value', adj => { - const value = adj.value / adj.page_size; - appDisplay._pageIndicators.setCurrentPosition(value); - }); -} - -// Set App Grid columns, rows, icon size, incomplete pages -function _updateAppGridProperties(reset = false) { - opt._appGridNeedsRedisplay = false; - // columns, rows, icon size - const appDisplay = Main.overview._overview._controls._appDisplay; - appDisplay.visible = true; - - if (reset) { - appDisplay._grid.layoutManager.fixedIconSize = -1; - appDisplay._grid.layoutManager.allow_incomplete_pages = true; - appDisplay._grid.setGridModes(); - if (_appGridLayoutSettings) { - _appGridLayoutSettings.disconnect(_appGridLayoutConId); - _appGridLayoutConId = 0; - _appGridLayoutSettings = null; + // update all invalid positions that may be result of grid/icon size change + _updateIconPositions() { + const appDisplay = Main.overview._overview._controls._appDisplay; + const layout = JSON.stringify(global.settings.get_value('app-picker-layout').recursiveUnpack()); + // if app grid layout is empty, sort source alphabetically to avoid misplacing + if (layout === JSON.stringify([]) && appDisplay._sortOrderedItemsAlphabetically) + appDisplay._sortOrderedItemsAlphabetically(); + const icons = [...appDisplay._orderedItems]; + for (let i = 0; i < icons.length; i++) + appDisplay._moveItem(icons[i], -1, -1); + } + + _removeIcons() { + const appDisplay = Main.overview._overview._controls._appDisplay; + const icons = [...appDisplay._orderedItems]; + for (let i = 0; i < icons.length; i++) { + const icon = icons[i]; + if (icon._dialog) + Main.layoutManager.overviewGroup.remove_child(icon._dialog); + appDisplay._removeItem(icon); + icon.destroy(); } - appDisplay._redisplay(); + appDisplay._folderIcons = []; + } + + _removeStatusMessage() { + if (Me._vShellStatusMessage) { + if (Me._vShellMessageTimeoutId) { + GLib.source_remove(Me._vShellMessageTimeoutId); + Me._vShellMessageTimeoutId = 0; + } + Me._vShellStatusMessage.destroy(); + Me._vShellStatusMessage = null; + } + } - appDisplay._grid.set_style(''); - _resetAppGrid(); - } else { - // update grid on layout reset - if (!_appGridLayoutSettings) { - _appGridLayoutSettings = ExtensionUtils.getSettings('org.gnome.shell'); - _appGridLayoutConId = _appGridLayoutSettings.connect('changed::app-picker-layout', _resetAppGrid); + _updateLayout(settings, key) { + const currentValue = JSON.stringify(settings.get_value(key).deep_unpack()); + const emptyValue = JSON.stringify([]); + const customLayout = currentValue !== emptyValue; + if (!customLayout) { + this._updateAppGrid(); + } + } + + _updateAppGrid(reset = false, callback) { + const appDisplay = Main.overview._overview._controls._appDisplay; + // reset the grid only if called directly without args or if all folders where removed by using reset button in Settings window + // otherwise this function is called every time a user moves icon to another position as a settings callback + + // force update icon size using adaptToSize(), the page size cannot be the same as the current one + appDisplay._grid.layoutManager._pageWidth += 1; + appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); + + // don't delay the first screen lock on GS < 44, removing icons takes a time and with other 15 enabled extensions it can be multiplied by 15 + if (!Main.sessionMode.isLocked) + this._removeIcons(); + + appDisplay._redisplay(); + // don't realize appDisplay on disable, or at startup if disabled + // always realize appDisplay otherwise to avoid errors while opening folders (that I was unable to trace) + if (reset || (!opt.APP_GRID_PERFORMANCE && callback)) { + this._removeStatusMessage(); + if (callback) + callback(); + return; } - appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; - appDisplay._grid.set_style(`column-spacing: ${opt.APP_GRID_SPACING}px; row-spacing: ${opt.APP_GRID_SPACING}px;`); + // workaround - silently realize appDisplay + // appDisplay and its content must be "visible" (opacity > 0) on the screen (within monitor geometry) + // to realize its objects + // this action takes some time and affects animations during the first use + // if we do it invisibly before user needs it, it can improve the user's experience + + this._exposeAppGrid(); + + // let the main loop process our changes before continuing + _timeouts.one = GLib.idle_add(GLib.PRIORITY_LOW, () => { + this._updateIconPositions(); + if (appDisplay._sortOrderedItemsAlphabetically) { + appDisplay._sortOrderedItemsAlphabetically(); + appDisplay._grid.layoutManager._pageWidth += 1; + appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); + appDisplay._setLinearPositions(appDisplay._orderedItems); + } - // force redisplay - appDisplay._grid._currentMode = -1; - appDisplay._grid.setGridModes(); - appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE; - // appDisplay._folderIcons.forEach(folder => folder._dialog?._updateFolderSize()); - _resetAppGrid(); + appDisplay._redisplay(); + // realize also all app folders (by opening them) so the first popup is as smooth as the second one + // let the main loop process our changes before continuing + _timeouts.two = GLib.idle_add(GLib.PRIORITY_LOW, () => { + this._restoreAppGrid(); + Me._resetInProgress = false; + this._removeStatusMessage(); + + if (callback) + callback(); + + _timeouts.two = 0; + return GLib.SOURCE_REMOVE; + }); + _timeouts.one = 0; + return GLib.SOURCE_REMOVE; + }); } -} -function _updateAppGridDND(reset) { - if (!reset) { - if (!_appSystemStateConId && opt.APP_GRID_INCLUDE_DASH >= 3) { - _appSystemStateConId = Shell.AppSystem.get_default().connect( - 'app-state-changed', - () => { - _updateFolderIcons = true; - Main.overview._overview._controls._appDisplay._redisplay(); - } - ); + _exposeAppGrid() { + const overviewGroup = Main.layoutManager.overviewGroup; + if (!overviewGroup.visible) { + // scale down the overviewGroup so it don't cover uiGroup + overviewGroup.scale_y = 0.001; + // make it invisible to the eye, but visible for the renderer + overviewGroup.opacity = 1; + // if overview is hidden, show it + overviewGroup.visible = true; } - } else if (_appSystemStateConId) { - Shell.AppSystem.get_default().disconnect(_appSystemStateConId); - _appSystemStateConId = 0; + + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay.opacity = 1; + + // find usable value, sometimes it's one, sometime the other... + let [x, y] = appDisplay.get_position(); + let { x1, y1 } = appDisplay.allocation; + x = x === Infinity ? 0 : x; + y = y === Infinity ? 0 : y; + x1 = x1 === Infinity ? 0 : x1; + y1 = y1 === Infinity ? 0 : y1; + appDisplay.translation_x = -(x ? x : x1); + appDisplay.translation_y = -(y ? y : y1); + this._exposeAppFolders(); + } + + _exposeAppFolders() { + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay._folderIcons.forEach(d => { + d._ensureFolderDialog(); + d._dialog._updateFolderSize(); + d._dialog.scale_y = 0.0001; + d._dialog.show(); + }); + } + + _restoreAppGrid() { + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay.translation_x = 0; + appDisplay.translation_y = 0; + // appDisplay.opacity = 0; + this._hideAppFolders(); + + const overviewGroup = Main.layoutManager.overviewGroup; + if (!Main.overview._shown) + overviewGroup.hide(); + overviewGroup.scale_y = 1; + overviewGroup.opacity = 255; + + this._removeStatusMessage(); + } + + _hideAppFolders() { + const appDisplay = Main.overview._overview._controls._appDisplay; + appDisplay._folderIcons.forEach(d => { + if (d._dialog) { + d._dialog._updateFolderSize(); + d._dialog.hide(); + d._dialog.scale_y = 1; + } + }); + } + + _getWindowApp(metaWin) { + const tracker = Shell.WindowTracker.get_default(); + return tracker.get_window_app(metaWin); } - if (opt.APP_GRID_ORDER && !reset) { - if (!_origAppViewItemAcceptDrop) - _origAppViewItemAcceptDrop = AppDisplay.AppViewItem.prototype.acceptDrop; - AppDisplay.AppViewItem.prototype.acceptDrop = () => false; - } else if (_origAppViewItemAcceptDrop) { - AppDisplay.AppViewItem.prototype.acceptDrop = _origAppViewItemAcceptDrop; + + _getAppLastUsedWindow(app) { + let recentWin; + global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => { + const winApp = this._getWindowApp(metaWin); + if (!recentWin && winApp === app) + recentWin = metaWin; + }); + return recentWin; } -} -function _restoreOverviewGroup() { - Main.overview.dash.showAppsButton.checked = false; - Main.layoutManager.overviewGroup.opacity = 255; - Main.layoutManager.overviewGroup.scale_x = 1; - Main.layoutManager.overviewGroup.hide(); -} + _getAppRecentWorkspace(app) { + const recentWin = this._getAppLastUsedWindow(app); + if (recentWin) + return recentWin.get_workspace(); + + return null; + } +}; const AppDisplayVertical = { // correction of the appGrid size when page indicators were moved from the bottom to the right @@ -302,7 +557,7 @@ const AppDisplayCommon = { const appsInsideFolders = new Set(); this._folderIcons = []; - if (!opt.APP_GRID_ORDER) { + if (!opt.APP_GRID_USAGE) { let folders = this._folderSettings.get_strv('folder-children'); folders.forEach(id => { let path = `${this._folderSettings.path}folders/${id}/`; @@ -317,8 +572,8 @@ const AppDisplayCommon = { if (icon.pressed) this.updateDragFocus(icon); }); - } else if (_updateFolderIcons && opt.APP_GRID_EXCLUDE_RUNNING) { - // if any app changed its running state, update folder icon + } else if (this._updateFolderIcons && opt.APP_GRID_EXCLUDE_RUNNING) { + // if any app changed its running state, update folder icon icon.icon.update(); } @@ -334,8 +589,9 @@ const AppDisplayCommon = { icon.getAppIds().forEach(appId => appsInsideFolders.add(appId)); }); } + // reset request to update active icon - _updateFolderIcons = false; + this._updateFolderIcons = false; // Allow dragging of the icon only if the Dash would accept a drop to // change favorite-apps. There are no other possible drop targets from @@ -348,7 +604,7 @@ const AppDisplayCommon = { global.settings.is_writable('app-picker-layout'); apps.forEach(appId => { - if (!opt.APP_GRID_ORDER && appsInsideFolders.has(appId)) + if (!opt.APP_GRID_USAGE && appsInsideFolders.has(appId)) return; let icon = this._items.get(appId); @@ -380,7 +636,7 @@ const AppDisplayCommon = { dragMotion: this._onDragMotion.bind(this), }; DND.addDragMonitor(this._dragMonitor); - if (shellVersion < 43) + if (Me.shellVersion < 43) this._slideSidePages(AppDisplay.SidePages.PREVIOUS | AppDisplay.SidePages.NEXT | AppDisplay.SidePages.DND); else this._appGridLayout.showPageIndicators(); @@ -419,50 +675,16 @@ const AppDisplayCommon = { this._redisplay(); }, - // accept source from active preview + // accept source from active folder preview acceptDrop(source) { - if (opt.APP_GRID_ORDER) + if (opt.APP_GRID_USAGE) return false; if (source._sourceItem) source = source._sourceItem; - let dropTarget = null; - if (shellVersion >= 43) { - dropTarget = this._dropTarget; - delete this._dropTarget; - } - - if (!this._canAccept(source)) + if (!BaseAppViewCommon.acceptDrop.bind(this)(source)) return false; - if ((shellVersion < 43 && this._dropPage) || - (shellVersion >= 43 && (dropTarget === this._prevPageIndicator || - dropTarget === this._nextPageIndicator))) { - let increment; - - if (shellVersion < 43) - increment = this._dropPage === AppDisplay.SidePages.NEXT ? 1 : -1; - else - increment = dropTarget === this._prevPageIndicator ? -1 : 1; - - const { currentPage, nPages } = this._grid; - const page = Math.min(currentPage + increment, nPages); - const position = page < nPages ? -1 : 0; - - this._moveItem(source, page, position); - this.goToPage(page); - } else if (this._delayedMoveData) { - // Dropped before the icon was moved - const { page, position } = this._delayedMoveData; - - try { - this._moveItem(source, page, position); - } catch (e) { - log(`Warning:${e}`); - } - this._removeDelayedMove(); - } - this._savePages(); let view = AppDisplay._getViewFromIcon(source); @@ -493,7 +715,7 @@ const BaseAppViewVertical = { this._pageIndicators.x_align = Clutter.ActorAlign.START; this._pageIndicators.set_style('margin-right: 10px;'); const scrollContainer = this._scrollView.get_parent(); - if (shellVersion < 43) { + if (Me.shellVersion < 43) { // remove touch friendly side navigation bars / arrows if (this._hintContainer && this._hintContainer.get_parent()) scrollContainer.remove_child(this._hintContainer); @@ -536,7 +758,7 @@ const BaseAppViewCommon = { try { this._moveItem(icon, page, position); } catch (e) { - log(`Warning:${e}`); + console.warn(`Warning:${e}`); } }); }, @@ -570,14 +792,19 @@ const BaseAppViewCommon = { } }); - // sort all alphabetically - if (opt.APP_GRID_ORDER > 0) { + // different options for root app grid and app folders + const thisIsFolder = this instanceof AppDisplay.FolderView; + const thisIsAppDisplay = !thisIsFolder; + if ((opt.APP_GRID_ORDER && thisIsAppDisplay) || + (opt.APP_FOLDER_ORDER && thisIsFolder)) { // const { itemsPerPage } = this._grid; let appIcons = this._orderedItems; + // sort all alphabetically this._sortOrderedItemsAlphabetically(appIcons); // appIcons.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); // then sort used apps by usage - if (opt.APP_GRID_ORDER === 2) + if ((opt.APP_GRID_USAGE && thisIsAppDisplay) || + (opt.APP_FOLDER_USAGE && thisIsFolder)) appIcons.sort((a, b) => Shell.AppUsage.get_default().compare(a.app.id, b.app.id)); // sort favorites first @@ -595,9 +822,14 @@ const BaseAppViewCommon = { } // sort running first - if (opt.APP_GRID_DASH_FIRST) + if (opt.APP_GRID_DASH_FIRST && thisIsAppDisplay) appIcons.sort((a, b) => a.app.get_state() !== Shell.AppState.RUNNING && b.app.get_state() === Shell.AppState.RUNNING); + if (opt.APP_GRID_FOLDERS_FIRST) + appIcons.sort((a, b) => b._folder && !a._folder); + else if (opt.APP_GRID_FOLDERS_LAST) + appIcons.sort((a, b) => a._folder && !b._folder); + this._setLinearPositions(appIcons); this._orderedItems = appIcons; @@ -611,7 +843,7 @@ const BaseAppViewCommon = { }, _canAccept(source) { - return opt.APP_GRID_ORDER ? false : source instanceof AppDisplay.AppViewItem; + return source instanceof AppDisplay.AppViewItem; }, // support active preview icons @@ -619,12 +851,25 @@ const BaseAppViewCommon = { if (!this._canAccept(source)) return false; - if (source._sourceItem) - source = source._sourceItem; + let dropTarget = null; + if (Me.shellVersion >= 43) { + dropTarget = this._dropTarget; + delete this._dropTarget; + } + + if (!this._canAccept(source)) + return false; + if ((Me.shellVersion < 43 && this._dropPage) || + (Me.shellVersion >= 43 && (dropTarget === this._prevPageIndicator || + dropTarget === this._nextPageIndicator))) { + let increment; + + if (Me.shellVersion < 43) + increment = this._dropPage === AppDisplay.SidePages.NEXT ? 1 : -1; + else + increment = dropTarget === this._prevPageIndicator ? -1 : 1; - if (this._dropPage) { - const increment = this._dropPage === AppDisplay.SidePages.NEXT ? 1 : -1; const { currentPage, nPages } = this._grid; const page = Math.min(currentPage + increment, nPages); const position = page < nPages ? -1 : 0; @@ -635,7 +880,11 @@ const BaseAppViewCommon = { // Dropped before the icon was moved const { page, position } = this._delayedMoveData; - this._moveItem(source, page, position); + try { + this._moveItem(source, page, position); + } catch (e) { + console.warn(`Warning:${e}`); + } this._removeDelayedMove(); } @@ -652,7 +901,7 @@ const BaseAppViewCommon = { const appIcon = dragEvent.source; - if (shellVersion < 43) { + if (Me.shellVersion < 43) { this._dropPage = this._pageForCoords(dragEvent.x, dragEvent.y); if (this._dropPage && this._dropPage === AppDisplay.SidePages.PREVIOUS && @@ -663,7 +912,7 @@ const BaseAppViewCommon = { } if (appIcon instanceof AppDisplay.AppViewItem) { - if (shellVersion < 44) { + if (Me.shellVersion < 44) { // Handle the drag overshoot. When dragging to above the // icon grid, move to the page above; when dragging below, // move to the page below. @@ -685,7 +934,10 @@ const BaseAppViewCommon = { } } - this._maybeMoveItem(dragEvent); + const thisIsFolder = this instanceof AppDisplay.FolderView; + const thisIsAppDisplay = !thisIsFolder; + if ((!opt.APP_GRID_ORDER && thisIsAppDisplay) || (!opt.APP_FOLDER_ORDER && thisIsFolder)) + this._maybeMoveItem(dragEvent); return DND.DragMotionResult.CONTINUE; }, @@ -771,16 +1023,12 @@ const FolderIcon = { : St.ButtonMask.ONE | St.ButtonMask.TWO; this.button_mask = buttonMask;*/ this.button_mask = St.ButtonMask.ONE | St.ButtonMask.TWO; - - // build the folders now to avoid node errors when dragging active folder preview icons - if (this.visible && opt.APP_GRID_ACTIVE_PREVIEW) - this._ensureFolderDialog(); }, open() { this._ensureFolderDialog(); - if (this._dialog._designCapacity !== this.view._orderedItems.length) - this._dialog._updateFolderSize(); + // if (this._dialog._designCapacity !== this.view._orderedItems.length) + this._dialog._updateFolderSize(); this.view._scrollView.vscroll.adjustment.value = 0; this._dialog.popup(); @@ -789,15 +1037,7 @@ const FolderIcon = { const FolderView = { _createGrid() { - let grid; - if (shellVersion < 43) - grid = new FolderGrid(); - else - grid = new FolderGrid43(); - - // IconGrid algorithm for adaptive icon size - // counts with different default(max) size for folders - grid.layoutManager._isFolder = true; + let grid = new AppDisplay.FolderGrid(); return grid; }, @@ -832,11 +1072,15 @@ const FolderView = { bin.child = this._orderedItems[i].app.create_icon_texture(subSize); } else { const app = this._orderedItems[i].app; - const child = new ActiveFolderIcon(app); + const child = new AppDisplay.AppIcon(app, { + setSizeManually: true, + showLabel: false, + }); child._sourceItem = this._orderedItems[i]; child._sourceFolder = this; child.icon.style_class = ''; child.icon.set_style('margin: 0; padding: 0;'); + child._dot.set_style('margin-bottom: 1px;'); child.icon.setIconSize(subSize); bin.child = child; @@ -863,9 +1107,9 @@ const FolderView = { layout.attach(bin, rtl ? (i + 1) % gridSize : i % gridSize, Math.floor(i / gridSize), 1, 1); } - // if folder content changed, update folder size - if (this._dialog && this._dialog._designCapacity !== this._orderedItems.length) - this._dialog._updateFolderSize(); + // if folder content changed, update folder size, but not if it's empty + /* if (this._dialog && this._dialog._designCapacity !== this._orderedItems.length && this._orderedItems.length) + this._dialog._updateFolderSize();*/ return icon; }, @@ -927,6 +1171,13 @@ const FolderView = { items.push(icon); }); + + if (opt.APP_FOLDER_ORDER) + Main.overview._overview.controls._appDisplay._sortOrderedItemsAlphabetically(items); + + if (opt.APP_FOLDER_USAGE) + items.sort((a, b) => Shell.AppUsage.get_default().compare(a.app.id, b.app.id)); + this._appIds = this._apps.map(app => app.get_id()); return items; }, @@ -942,10 +1193,9 @@ const FolderView = { }; // folder columns and rows -const FolderGrid = GObject.registerClass( -class FolderGrid extends IconGrid.IconGrid { +const FolderGridLegacy = { _init() { - super._init({ + IconGrid.IconGrid.prototype._init.bind(this)({ allow_incomplete_pages: false, // For adaptive size (0), set the numbers high enough to fit all the icons // to avoid splitting the icons to pages @@ -954,56 +1204,52 @@ class FolderGrid extends IconGrid.IconGrid { page_halign: Clutter.ActorAlign.CENTER, page_valign: Clutter.ActorAlign.CENTER, }); - + this.layout_manager._isFolder = true; // if (!opt.APP_GRID_FOLDER_DEFAULT) const spacing = opt.APP_GRID_SPACING; this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); - this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; - } + this.layoutManager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + }, adaptToSize(width, height) { this.layout_manager.adaptToSize(width, height); - } -}); - - -let FolderGrid43; -// first reference to constant defined using const in other module returns undefined, the AppGrid const will remain empty and unused -const AppGrid = AppDisplay.AppGrid; -if (AppDisplay.AppGrid) { - FolderGrid43 = GObject.registerClass( - class FolderGrid43 extends AppDisplay.AppGrid { - _init() { - super._init({ - allow_incomplete_pages: false, - columns_per_page: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 20, - rows_per_page: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 20, - page_halign: Clutter.ActorAlign.CENTER, - page_valign: Clutter.ActorAlign.CENTER, - }); + }, +}; + +const FolderGrid = { + _init() { + AppDisplay.AppGrid.prototype._init.bind(this)({ + allow_incomplete_pages: false, + columns_per_page: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 20, + rows_per_page: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 20, + page_halign: Clutter.ActorAlign.CENTER, + page_valign: Clutter.ActorAlign.CENTER, + }); + this.layout_manager._isFolder = true; + const spacing = opt.APP_GRID_SPACING; + this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); + this.layoutManager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; - const spacing = opt.APP_GRID_SPACING; - this.set_style(`column-spacing: ${spacing}px; row-spacing: ${spacing}px;`); - this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; + this.setGridModes([ + { + columns: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 3, + rows: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 3, + }, + ]); + }, - this.setGridModes([ - { - columns: opt.APP_GRID_FOLDER_COLUMNS ? opt.APP_GRID_FOLDER_COLUMNS : 3, - rows: opt.APP_GRID_FOLDER_ROWS ? opt.APP_GRID_FOLDER_ROWS : 3, - }, - ]); - } + adaptToSize(width, height) { + this.layout_manager.adaptToSize(width, height); + }, +}; - adaptToSize(width, height) { - this.layout_manager.adaptToSize(width, height); - } - }); -} const FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME const AppFolderDialog = { // injection to _init() after__init() { + this._viewBox.add_style_class_name('app-folder-dialog-vshell'); + // delegate this dialog to the FolderIcon._view // so its _createFolderIcon function can update the dialog if folder content changed this._view._dialog = this; @@ -1023,18 +1269,65 @@ const AppFolderDialog = { }); this.child.add_action(clickAction); + + // Adjust empty actor to center the title + this._entryBox.get_first_child().width = 82; + }, + + after__addFolderNameEntry() { + // Edit button + this._removeButton = new St.Button({ + style_class: 'edit-folder-button', + button_mask: St.ButtonMask.ONE, + toggle_mode: false, + reactive: true, + can_focus: true, + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.CENTER, + child: new St.Icon({ + icon_name: 'user-trash-symbolic', + icon_size: 16, + }), + }); + + this._removeButton.connect('clicked', () => { + if (Date.now() - this._removeButton._lastClick < Clutter.Settings.get_default().double_click_time) { + this._grabHelper.ungrab({ actor: this }); + // without hiding the dialog, Shell crashes (at least on X11) + this.hide(); + this._view._deletingFolder = true; + + // Resetting all keys deletes the relocatable schema + let keys = this._folder.settings_schema.list_keys(); + for (const key of keys) + this._folder.reset(key); + + let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' }); + let folders = settings.get_strv('folder-children'); + folders.splice(folders.indexOf(this._view._id), 1); + + // remove all abandoned folders (usually my own garbage and unwanted default folders...) + /* const appFolders = this._appDisplay._folderIcons.map(icon => icon._id); + folders.forEach(folder => { + if (!appFolders.includes(folder)) { + folders.splice(folders.indexOf(folder._id), 1); + } + });*/ + settings.set_strv('folder-children', folders); + + this._view._deletingFolder = false; + return; + } + this._removeButton._lastClick = Date.now(); + }); + + this._entryBox.add_child(this._removeButton); }, popup() { if (this._isOpen) return; - /* if (!this._correctSize) { - // update folder with the precise app item size when the dialog is realized - GLib.idle_add(0, () => this._updateFolderSize(true)); - this._correctSize = true; - }*/ - this._isOpen = this._grabHelper.grab({ actor: this, onUngrab: () => this.popdown(), @@ -1046,26 +1339,48 @@ const AppFolderDialog = { this.get_parent().set_child_above_sibling(this, null); this._needsZoomAndFade = true; - this.show(); + // the first folder dialog realization needs size correction + // so set the folder size, let it realize and then update the folder content + if (!this.realized) { + this._updateFolderSize(); + GLib.idle_add( + GLib.PRIORITY_DEFAULT, + () => { + this._updateFolderSize(); + } + ); + } + + this.show(); this.emit('open-state-changed', true); }, _updateFolderSize() { - // adapt folder size according to the settings and number of icons const view = this._view; + const [firstItem] = view._grid.layoutManager._container; + if (!firstItem) + return; + // adapt folder size according to the settings and number of icons + const appDisplay = this._source._parentView; + if (!appDisplay.width || appDisplay.allocation.x2 === Infinity || appDisplay.allocation.x2 === -Infinity) { + return; + } + view._grid.layoutManager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; view._grid.set_style(`column-spacing: ${opt.APP_GRID_SPACING}px; row-spacing: ${opt.APP_GRID_SPACING}px;`); const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const dialogMargin = 30; + const itemPadding = 55; // default icon item padding on Fedora 44 + // const dialogMargin = 30; const nItems = view._orderedItems.length; let columns = opt.APP_GRID_FOLDER_COLUMNS; let rows = opt.APP_GRID_FOLDER_ROWS; + const fullAdaptiveGrid = !columns && !rows; let spacing = opt.APP_GRID_SPACING; - const monitor = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + const minItemSize = 48 + itemPadding; - if (!columns && !rows) { + if (fullAdaptiveGrid) { columns = Math.ceil(Math.sqrt(nItems)); rows = columns; if (columns * (columns - 1) >= nItems) { @@ -1081,38 +1396,67 @@ const AppFolderDialog = { } const iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT : opt.APP_GRID_FOLDER_ICON_SIZE; - let itemSize = iconSize + 53; // icon padding + view._grid.layoutManager.fixedIconSize = iconSize; + + let itemSize = iconSize + 55; // icon padding // first run sets the grid before we can read the real icon size // so we estimate the size from default properties // and correct it in the second run - if (this._notFirstRun) { - const [firstItem] = view._grid.layoutManager._container; + if (this.realized) { firstItem.icon.setIconSize(iconSize); const [firstItemWidth] = firstItem.get_preferred_size(); const realSize = firstItemWidth / scaleFactor; - if (realSize > iconSize) + // if the preferred item size is smaller than icon plus some padding, ignore it + // (icons that are not yet realized are returning sizes like 45 or 53) + if (realSize > (iconSize + 24)) itemSize = realSize; - } else { - this._needsUpdateSize = true; - this._notFirstRun = true; } - let width = columns * (itemSize + spacing) + /* padding for nav arrows*/64; - width = Math.round(width + (opt.ORIENTATION || !opt.APP_GRID_FOLDER_COLUMNS ? 100 : 160/* space for navigation arrows*/)); - let height = rows * (itemSize + spacing) + /* header*/75 + /* padding*/100; + width = Math.round(width + (opt.ORIENTATION ? 100 : 160/* space for navigation arrows*/)); + let height = rows * (itemSize + spacing) + /* header*/75 + /* padding*/ 2 * 30 + /* padding + ?page indicator*/(!opt.ORIENTATION || !opt.APP_GRID_FOLDER_COLUMNS ? 100 : 70); + + // allocation is more reliable than appDisplay width/height properties + const appDisplayWidth = appDisplay.allocation.x2 - appDisplay.allocation.x1; + const appDisplayHeight = appDisplay.allocation.y2 - appDisplay.allocation.y1 + (opt.SHOW_SEARCH_ENTRY ? Main.overview._overview.controls._searchEntryBin.height : 0); - // folder must fit the primary monitor + // folder must fit the appDisplay area // reduce columns/rows if needed and count with the scaled values - while (width * scaleFactor > monitor.width - 2 * dialogMargin) { - width -= itemSize + spacing; - columns -= 1; + if (!opt.APP_GRID_FOLDER_ROWS) { + while ((height * scaleFactor) > appDisplayHeight) { + height -= itemSize + spacing; + rows -= 1; + } + } + + if (!opt.APP_GRID_FOLDER_COLUMNS) { + while ((width * scaleFactor) > appDisplayWidth) { + width -= itemSize + spacing; + columns -= 1; + } } - while (height * scaleFactor > monitor.height - 2 * dialogMargin) { - height -= itemSize + spacing; - rows -= 1; + // try to compensate for the previous reduction if there is a space + if (!opt.APP_GRID_FOLDER_COLUMNS) { + while ((nItems > columns * rows) && ((width * scaleFactor + itemSize + spacing) <= appDisplayWidth)) { + width += itemSize + spacing; + columns += 1; + } + // remove columns that cannot be displayed + if ((columns * minItemSize + (columns - 1) * spacing) > appDisplayWidth) + columns = Math.floor(appDisplayWidth / (minItemSize + spacing)); } - width = Math.max(540, width); + if (!opt.APP_GRID_FOLDER_ROWS) { + while ((nItems > columns * rows) && ((height * scaleFactor + itemSize + spacing) <= appDisplayHeight)) { + height += itemSize + spacing; + rows += 1; + } + // remove rows that cannot be displayed + if ((rows * minItemSize + (rows - 1) * spacing) > appDisplayHeight) + rows = Math.floor(appDisplayWidth / (minItemSize + spacing)); + } + + width = Math.clamp(width, 640, appDisplayWidth); + height = Math.min(height, appDisplayHeight); const layoutManager = view._grid.layoutManager; layoutManager.rows_per_page = rows; @@ -1127,6 +1471,8 @@ const AppFolderDialog = { padding: 30px; `); + view._grid.layoutManager._pageWidth += 1; + view._grid.layoutManager.adaptToSize(view._grid.layoutManager._pageWidth - 1, view._grid.layoutManager._pageHeight); view._redisplay(); // store original item count @@ -1145,40 +1491,34 @@ const AppFolderDialog = { // this. covers the whole screen let dialogTargetX = dialogX; let dialogTargetY = dialogY; - if (!opt.APP_GRID_FOLDER_CENTER) { - const appDisplay = this._source._parentView; - dialogTargetX = Math.round(sourceCenterX - this.child.width / 2); - dialogTargetY = Math.round(sourceCenterY - this.child.height / 2); + const appDisplay = this._source._parentView; + const [appDisplayX, appDisplayY] = this._source._parentView.get_transformed_position(); - // keep the dialog in appDisplay area if possible - dialogTargetX = Math.clamp( - dialogTargetX, - this.x + appDisplay.x, - this.x + appDisplay.x + appDisplay.width - this.child.width - ); + if (!opt.APP_GRID_FOLDER_CENTER) { + dialogTargetX = sourceCenterX - this.child.width / 2; + dialogTargetY = sourceCenterY - this.child.height / 2; - dialogTargetY = Math.clamp( - dialogTargetY, - this.y + appDisplay.y, - this.y + appDisplay.y + appDisplay.height - this.child.height - ); - // or at least in the monitor area - const monitor = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + // keep the dialog in appDisplay area if possible dialogTargetX = Math.clamp( dialogTargetX, - this.x + monitor.x, - this.x + monitor.x + monitor.width - this.child.width + appDisplayX, + appDisplayX + appDisplay.width - this.child.width ); dialogTargetY = Math.clamp( dialogTargetY, - this.y + monitor.y, - this.y + monitor.y + monitor.height - this.child.height + appDisplayY, + appDisplayY + appDisplay.height - this.child.height ); + } else { + const searchEntryHeight = opt.SHOW_SEARCH_ENTRY ? Main.overview._overview.controls._searchEntryBin.height : 0; + dialogTargetX = appDisplayX + appDisplay.width / 2 - this.child.width / 2; + dialogTargetY = appDisplayY - searchEntryHeight + ((appDisplay.height + searchEntryHeight) / 2 - this.child.height / 2) / 2; } - const dialogOffsetX = -dialogX + dialogTargetX; - const dialogOffsetY = -dialogY + dialogTargetY; + + const dialogOffsetX = Math.round(dialogTargetX - dialogX); + const dialogOffsetY = Math.round(dialogTargetY - dialogY); this.child.set({ translation_x: sourceX - dialogX, @@ -1188,12 +1528,6 @@ const AppFolderDialog = { opacity: 0, }); - this.ease({ - background_color: DIALOG_SHADE_NORMAL, - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - this.child.ease({ translation_x: dialogOffsetX, translation_y: dialogOffsetY, @@ -1202,17 +1536,22 @@ const AppFolderDialog = { opacity: 255, duration: FOLDER_DIALOG_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => { - // if the folder grid was build with the estimated icon item size because the real size wasn't available - // rebuild it with the real size now, after the folder was realized - if (this._needsUpdateSize) { - this._updateFolderSize(); - this._view._redisplay(); - this._needsUpdateSize = false; - } - }, }); + appDisplay.ease({ + opacity: 0, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + if (opt.SHOW_SEARCH_ENTRY) { + Main.overview.searchEntry.ease({ + opacity: 0, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + this._needsZoomAndFade = false; if (this._sourceMappedId === 0) { @@ -1230,17 +1569,20 @@ const AppFolderDialog = { return; } + // if the dialog was shown silently, skip animation + if (this.scale_y < 1) { + this._needsZoomAndFade = false; + this.hide(); + this._popdownCallbacks.forEach(func => func()); + this._popdownCallbacks = []; + return; + } + let [sourceX, sourceY] = this._source.get_transformed_position(); let [dialogX, dialogY] = this.child.get_transformed_position(); - this.ease({ - background_color: Clutter.Color.from_pixel(0x00000000), - duration: FOLDER_DIALOG_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - this.child.ease({ translation_x: sourceX - dialogX + this.child.translation_x, translation_y: sourceY - dialogY + this.child.translation_y, @@ -1264,11 +1606,35 @@ const AppFolderDialog = { }, }); + const appDisplay = this._source._parentView; + appDisplay.ease({ + opacity: 255, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + if (opt.SHOW_SEARCH_ENTRY) { + Main.overview.searchEntry.ease({ + opacity: 255, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + this._needsZoomAndFade = false; }, _setLighterBackground(lighter) { - const backgroundColor = lighter + if (this._isOpen) { + const appDisplay = this._source._parentView; + appDisplay.ease({ + opacity: lighter ? 20 : 0, + duration: FOLDER_DIALOG_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + + /* const backgroundColor = lighter ? DIALOG_SHADE_HIGHLIGHT : DIALOG_SHADE_NORMAL; @@ -1276,91 +1642,10 @@ const AppFolderDialog = { backgroundColor, duration: FOLDER_DIALOG_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); + }); */ }, }; -// just make app grid to update all invalid positions that may be result of grid/icon size change -function _updateIconPositions() { - const appDisplay = Main.overview._overview._controls._appDisplay; - const icons = [...appDisplay._orderedItems]; - for (let i = 0; i < icons.length; i++) - appDisplay._moveItem(icons[i], -1, -1); -} - -function _removeIcons() { - const appDisplay = Main.overview._overview._controls._appDisplay; - const icons = [...appDisplay._orderedItems]; - for (let i = 0; i < icons.length; i++) { - const icon = icons[i]; - if (icon._dialog) - Main.layoutManager.overviewGroup.remove_child(icon._dialog); - appDisplay._removeItem(icon); - icon.destroy(); - } - appDisplay._folderIcons = []; -} - -function _resetAppGrid(settings) { - const appDisplay = Main.overview._overview._controls._appDisplay; - // reset the grid only if called directly without args or if all folders where removed by using reset button in Settings window - // otherwise this function is called every time a user moves icon to another position as a settings callback - if (settings) { - const currentValue = JSON.stringify(global.settings.get_value('app-picker-layout').deep_unpack()); - const emptyValue = JSON.stringify([]); - const customLayout = currentValue !== emptyValue; - // appDisplay._customLayout = customLayout; - if (customLayout) - return; - else - opt._appGridNeedsRedisplay = true; - } - - // force update icon size using adaptToSize(). the page size cannot be the same as the current one - appDisplay._grid.layoutManager._pageWidth += 1; - appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); - _removeIcons(); - appDisplay._redisplay(); - // force appDisplay to move all icons to proper positions and update all properties - GLib.idle_add(0, () => { - _updateIconPositions(); - if (appDisplay._sortOrderedItemsAlphabetically) { - appDisplay._sortOrderedItemsAlphabetically(); - appDisplay._grid.layoutManager._pageWidth += 1; - appDisplay._grid.layoutManager.adaptToSize(appDisplay._grid.layoutManager._pageWidth - 1, appDisplay._grid.layoutManager._pageHeight); - appDisplay._setLinearPositions(appDisplay._orderedItems); - } else { - appDisplay._removeItem(appDisplay._orderedItems[0]); - appDisplay._redisplay(); - } - - appDisplay._redisplay(); - }); -} - -function _getWindowApp(metaWin) { - const tracker = Shell.WindowTracker.get_default(); - return tracker.get_window_app(metaWin); -} - -function _getAppLastUsedWindow(app) { - let recentWin; - global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => { - const winApp = _getWindowApp(metaWin); - if (!recentWin && winApp === app) - recentWin = metaWin; - }); - return recentWin; -} - -function _getAppRecentWorkspace(app) { - const recentWin = _getAppLastUsedWindow(app); - if (recentWin) - return recentWin.get_workspace(); - - return null; -} - const AppIcon = { after__init() { // update the app label behavior @@ -1377,7 +1662,7 @@ const AppIcon = { return source !== this && (source instanceof this.constructor) && (view instanceof AppDisplay.AppDisplay && - !opt.APP_GRID_ORDER); + !opt.APP_GRID_USAGE); }, }; @@ -1427,7 +1712,7 @@ const AppViewItemCommon = { // support active preview icons acceptDrop(source, _actor, x) { - if (opt.APP_GRID_ORDER) + if (opt.APP_GRID_USAGE) return DND.DragMotionResult.NO_DROP; this._setHoveringByDnd(false); @@ -1449,26 +1734,18 @@ const AppViewItemCommon = { }; -const ActiveFolderIcon = GObject.registerClass( -class ActiveFolderIcon extends AppDisplay.AppIcon { - _init(app) { - super._init(app, { - setSizeManually: true, - showLabel: false, - }); - } - +const ActiveFolderIcon = { handleDragOver() { return DND.DragMotionResult.CONTINUE; - } + }, acceptDrop() { return false; - } + }, _onDragEnd() { this._dragging = false; this.undoScaleAndFade(); Main.overview.endItemDrag(this._sourceItem.icon); - } -}); + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/appFavorites.js b/extensions/44/vertical-workspaces/lib/appFavorites.js index 50ebce9..3efb68c 100644 --- a/extensions/44/vertical-workspaces/lib/appFavorites.js +++ b/extensions/44/vertical-workspaces/lib/appFavorites.js @@ -10,45 +10,63 @@ 'use strict'; -const { Shell } = imports.gi; const AppFavorites = imports.ui.appFavorites; -const Main = imports.ui.main; - -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; +let Me; let opt; -let _overrides; -let _firstRun = true; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('appFavoritesModule', true); - reset = reset || !moduleEnabled; +var AppFavoritesModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - // don't even touch this module if disabled - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - _firstRun = false; + cleanGlobals() { + Me = null; + opt = null; + } - if (_overrides) - _overrides.removeAll(); + update(reset) { + this.moduleEnabled = opt.get('appFavoritesModule'); + // if notifications are enabled no override is needed + reset = reset || !this.moduleEnabled || opt.SHOW_FAV_NOTIFICATION; - // if notifications are enabled no override is needed - if (reset || opt.SHOW_FAV_NOTIFICATION) { - _overrides = null; - opt = null; - return; + // don't touch original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) { + this.moduleEnabled = false; + console.debug(' AppFavoritesModule - Keeping untouched'); + } } - _overrides = new _Util.Overrides(); + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + // use actual instance instead of prototype + this._overrides.addOverride('AppFavorites', AppFavorites.getAppFavorites(), AppFavoritesCommon); - // AppFavorites.AppFavorites is const, first access returns undefined - const dummy = AppFavorites.AppFavorites; - _overrides.addOverride('AppFavorites', AppFavorites.AppFavorites.prototype, AppFavoritesCommon); -} + console.debug(' AppFavoritesModule - Activated'); + } + + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + console.debug(' AppFavoritesModule - Deactivated'); + } +}; const AppFavoritesCommon = { addFavoriteAtPos(appId, pos) { diff --git a/extensions/44/vertical-workspaces/lib/dash.js b/extensions/44/vertical-workspaces/lib/dash.js index bf832bd..17d43ea 100644 --- a/extensions/44/vertical-workspaces/lib/dash.js +++ b/extensions/44/vertical-workspaces/lib/dash.js @@ -8,309 +8,523 @@ * modified dash module of https://github.com/RensAlthuis/vertical-overview extension */ -const { Clutter, GObject, St, Shell, Meta } = imports.gi; -const AppDisplay = imports.ui.appDisplay; +'use strict'; + +const Clutter = imports.gi.Clutter; +const GLib = imports.gi.GLib; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const AltTab = imports.ui.altTab; const AppFavorites = imports.ui.appFavorites; +const AppDisplay = imports.ui.appDisplay; +const AppMenu = imports.ui.appMenu; +const BoxPointer = imports.ui.boxpointer; +const Dash = imports.ui.dash; const DND = imports.ui.dnd; const IconGrid = imports.ui.iconGrid; const Main = imports.ui.main; -const Overview = imports.ui.overview; -const Dash = imports.ui.dash; const PopupMenu = imports.ui.popupMenu; -const { AppMenu } = imports.ui.appMenu; -const BoxPointer = imports.ui.boxpointer; -const AltTab = imports.ui.altTab; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const Util = Me.imports.lib.util; -const _ = Me.imports.lib.settings._; +let Me; +let opt; +// gettext +let _; -const shellVersion = Util.shellVersion; -let _origWorkId; -let _newWorkId; -let _showAppsIconBtnPressId; +let _moduleEnabled; +let _timeouts; // added values to achieve a better ability to scale down according to available space var BaseIconSizes = [16, 24, 32, 40, 44, 48, 56, 64, 72, 80, 96, 112, 128]; -const RecentFilesSearchProviderPrefix = Me.imports.lib.recentFilesSearchProvider.prefix; -const WindowSearchProviderPrefix = Me.imports.lib.windowSearchProvider.prefix; +const DASH_ITEM_LABEL_SHOW_TIME = 150; -let _overrides; -let opt; -let _firstRun = true; +var DashModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + this._originalWorkId = null; + this._customWorkId = null; + this._showAppsIconBtnPressId = 0; + } -const DASH_ITEM_LABEL_SHOW_TIME = 150; + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } + + update(reset) { + this._removeTimeouts(); + + this.moduleEnabled = opt.get('dashModule'); + const conflict = !!(Me.Util.getEnabledExtensions('dash-to-dock').length || + Me.Util.getEnabledExtensions('ubuntu-dock').length || + Me.Util.getEnabledExtensions('dash-to-panel').length); -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('dashModule', true); - reset = reset || !moduleEnabled; + if (conflict && !reset) + console.warn(`[${Me.metadata.name}] Warning: "Dash" module disabled due to potential conflict with another extension`); - // don't even touch this module if disabled - if (_firstRun && reset) - return; + reset = reset || !this.moduleEnabled || conflict; + this._conflict = conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' DashModule - Keeping untouched'); + } - _firstRun = false; + updateStyle(dash) { + if (opt.DASH_BG_LIGHT) + dash._background.add_style_class_name('dash-background-light'); + else + dash._background.remove_style_class_name('dash-background-light'); + + dash._background.opacity = opt.DASH_BG_OPACITY; + let radius = opt.DASH_BG_RADIUS; + if (radius) { + let style; + switch (opt.DASH_POSITION) { + case 1: + style = opt.DASH_BG_GS3_STYLE ? `border-radius: ${radius}px 0 0 ${radius}px;` : `border-radius: ${radius}px;`; + break; + case 3: + style = opt.DASH_BG_GS3_STYLE ? `border-radius: 0 ${radius}px ${radius}px 0;` : `border-radius: ${radius}px;`; + break; + default: + style = `border-radius: ${radius}px;`; + } + dash._background.set_style(style); + } else { + dash._background.set_style(''); + } + } - if (_overrides) - _overrides.removeAll(); + _activateModule() { + _moduleEnabled = true; + _timeouts = {}; + const dash = Main.overview._overview._controls.layoutManager._dash; + if (!this._originalWorkId) + this._originalWorkId = dash._workId; - const dash = Main.overview._overview._controls.layoutManager._dash; + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - setToHorizontal(); + this._resetStyle(dash); + this.updateStyle(dash); - dash.remove_style_class_name('vertical'); - dash.remove_style_class_name('vertical-left'); - dash.remove_style_class_name('vertical-right'); + this._overrides.addOverride('DashItemContainer', Dash.DashItemContainer.prototype, DashItemContainerCommon); + this._overrides.addOverride('DashCommon', Dash.Dash.prototype, DashCommon); + this._overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIconCommon); + this._overrides.addOverride('DashIcon', Dash.DashIcon.prototype, DashIconCommon); + this._overrides.addOverride('AppMenu', AppMenu.AppMenu.prototype, AppMenuCommon); - if (reset) { - _moveDashAppGridIcon(reset); - _connectShowAppsIcon(reset); - _updateSearchWindowsIcon(false); - _updateRecentFilesIcon(false); - dash.visible = true; - dash._background.opacity = 255; - dash._background.remove_style_class_name('v-shell-dash-background'); - _overrides = null; - opt = null; - return; + if (opt.DASH_VERTICAL) { + dash.add_style_class_name('vertical'); + this._setOrientation(Clutter.Orientation.VERTICAL); + } else { + this._setOrientation(Clutter.Orientation.HORIZONTAL); + } + + if (!this._customWorkId) + this._customWorkId = Main.initializeDeferredWork(dash._box, dash._redisplay.bind(dash)); + dash._workId = this._customWorkId; + + this._updateSearchWindowsIcon(); + this._updateRecentFilesIcon(); + this._updateExtensionsIcon(); + this._moveDashAppGridIcon(); + this._connectShowAppsIcon(); + + dash.visible = opt.DASH_VISIBLE; + dash._background.add_style_class_name('dash-background-reduced'); + dash._queueRedisplay(); + + if (opt.DASH_ISOLATE_WS && !this._wmSwitchWsConId) { + this._wmSwitchWsConId = global.windowManager.connect('switch-workspace', () => dash._queueRedisplay()); + this._newWindowConId = global.display.connect_after('window-created', () => dash._queueRedisplay()); + } + + console.debug(' DashModule - Activated'); } - _overrides = new Util.Overrides(); + _disableModule() { + const dash = Main.overview._overview._controls.layoutManager._dash; + this._resetStyle(dash); - _overrides.addOverride('DashItemContainer', Dash.DashItemContainer.prototype, DashItemContainerCommon); - _overrides.addOverride('DashCommon', Dash.Dash.prototype, DashCommon); - _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIconCommon); - _overrides.addOverride('DashIcon', Dash.DashIcon.prototype, DashIconCommon); + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; - if (opt.DASH_VERTICAL) { - _overrides.addOverride('Dash', Dash.Dash.prototype, DashOverride); - setToVertical(); - dash.add_style_class_name('vertical'); + dash._workId = this._originalWorkId; - if (!_newWorkId) { - _origWorkId = dash._workId; - dash._workId = Main.initializeDeferredWork(dash._box, dash._redisplay.bind(dash)); - _newWorkId = dash._workId; - } else { - dash._workId = _newWorkId; + if (this._wmSwitchWsConId) { + global.windowManager.disconnect(this._wmSwitchWsConId); + this._wmSwitchWsConId = 0; + } + if (this._newWindowConId) { + global.windowManager.disconnect(this._newWindowConId); + this._newWindowConId = 0; } - } else { - setToHorizontal(); - if (_origWorkId) - dash._workId = _origWorkId; + + const reset = true; + this._setOrientation(Clutter.Orientation.HORIZONTAL); + this._moveDashAppGridIcon(reset); + this._connectShowAppsIcon(reset); + this._updateSearchWindowsIcon(false); + this._updateRecentFilesIcon(false); + this._updateExtensionsIcon(false); + dash.visible = !this._conflict; + dash._background.opacity = 255; + + _moduleEnabled = false; + console.debug(' DashModule - Disabled'); } - _updateSearchWindowsIcon(); - _updateRecentFilesIcon(); - _moveDashAppGridIcon(); - _connectShowAppsIcon(); - - if (dash._showWindowsIcon && !dash._showWindowsIconClickedId) - dash._showWindowsIconClickedId = dash._showWindowsIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(WindowSearchProviderPrefix)); - - if (dash._recentFilesIcon && !dash._recentFilesIconClickedId) - dash._recentFilesIconClickedId = dash._recentFilesIcon.toggleButton.connect('clicked', (a, c) => c && Util.activateSearchProvider(RecentFilesSearchProviderPrefix)); - - dash.visible = opt.DASH_VISIBLE; - dash._background.add_style_class_name('v-shell-dash-background'); - dash._redisplay(); -} - -function setToVertical() { - let dash = Main.overview._overview._controls.layoutManager._dash; - - dash._box.layout_manager.orientation = Clutter.Orientation.VERTICAL; - dash._dashContainer.layout_manager.orientation = Clutter.Orientation.VERTICAL; - dash._dashContainer.y_expand = false; - dash._dashContainer.x_expand = true; - dash.x_align = Clutter.ActorAlign.START; - dash.y_align = Clutter.ActorAlign.CENTER; - - let sizerBox = dash._background.get_children()[0]; - sizerBox.clear_constraints(); - sizerBox.add_constraint(new Clutter.BindConstraint({ - source: dash._showAppsIcon.icon, - coordinate: Clutter.BindCoordinate.WIDTH, - })); - sizerBox.add_constraint(new Clutter.BindConstraint({ - source: dash._dashContainer, - coordinate: Clutter.BindCoordinate.HEIGHT, - })); - dash._box.remove_all_children(); - dash._separator = null; - dash._queueRedisplay(); - dash._adjustIconSize(); - - dash.add_style_class_name(opt.DASH_LEFT ? 'vertical-left' : 'vertical-right'); -} - -function setToHorizontal() { - let dash = Main.overview._overview._controls.layoutManager._dash; - if (_origWorkId) - dash._workId = _origWorkId; // pretty sure this is a leak, but there no provided way to disconnect these... - dash._box.layout_manager.orientation = Clutter.Orientation.HORIZONTAL; - dash._dashContainer.layout_manager.orientation = Clutter.Orientation.HORIZONTAL; - dash._dashContainer.y_expand = true; - dash._dashContainer.x_expand = false; - dash.x_align = Clutter.ActorAlign.CENTER; - dash.y_align = 0; - - let sizerBox = dash._background.get_children()[0]; - sizerBox.clear_constraints(); - sizerBox.add_constraint(new Clutter.BindConstraint({ - source: dash._showAppsIcon.icon, - coordinate: Clutter.BindCoordinate.HEIGHT, - })); - sizerBox.add_constraint(new Clutter.BindConstraint({ - source: dash._dashContainer, - coordinate: Clutter.BindCoordinate.WIDTH, - })); - - dash._box.remove_all_children(); - dash._separator = null; - dash._queueRedisplay(); - dash._adjustIconSize(); -} - -function _moveDashAppGridIcon(reset = false) { - // move dash app grid icon to the front - const dash = Main.overview._overview._controls.layoutManager._dash; - - const appIconPosition = opt.get('showAppsIconPosition', true); - dash._showAppsIcon.remove_style_class_name('show-apps-icon-vertical-hide'); - dash._showAppsIcon.remove_style_class_name('show-apps-icon-horizontal-hide'); - dash._showAppsIcon.opacity = 255; - if (!reset && appIconPosition === 0) // 0 - start - dash._dashContainer.set_child_at_index(dash._showAppsIcon, 0); - if (reset || appIconPosition === 1) { // 1 - end - const index = dash._dashContainer.get_children().length - 1; - dash._dashContainer.set_child_at_index(dash._showAppsIcon, index); + _resetStyle(dash) { + dash.remove_style_class_name('vertical'); + dash.remove_style_class_name('vertical-gs3-left'); + dash.remove_style_class_name('vertical-gs3-right'); + dash.remove_style_class_name('vertical-left'); + dash.remove_style_class_name('vertical-right'); + dash._background.remove_style_class_name('dash-background-light'); + dash._background.remove_style_class_name('dash-background-reduced'); } - if (!reset && appIconPosition === 2) { // 2 - hide - const style = opt.DASH_VERTICAL ? 'show-apps-icon-vertical-hide' : 'show-apps-icon-horizontal-hide'; - dash._showAppsIcon.add_style_class_name(style); - // for some reason even if the icon height in vertical mode should be set to 0 by the style, it stays visible in full size returning height 1px - dash._showAppsIcon.opacity = 0; + + _removeTimeouts() { + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + _timeouts = null; + } } -} -function _connectShowAppsIcon(reset = false) { - if (!reset) { - if (_showAppsIconBtnPressId || Util.dashIsDashToDock()) { - // button is already connected || dash is Dash to Dock - return; + _setOrientation(orientation, dash) { + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + + dash._box.layout_manager.orientation = orientation; + dash._dashContainer.layout_manager.orientation = orientation; + dash._dashContainer.y_expand = !orientation; + dash._dashContainer.x_expand = !!orientation; + dash.x_align = orientation ? Clutter.ActorAlign.START : Clutter.ActorAlign.CENTER; + dash.y_align = orientation ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.FILL; + + let sizerBox = dash._background.get_children()[0]; + sizerBox.clear_constraints(); + sizerBox.add_constraint(new Clutter.BindConstraint({ + source: dash._showAppsIcon.icon, + coordinate: orientation ? Clutter.BindCoordinate.WIDTH : Clutter.BindCoordinate.HEIGHT, + })); + sizerBox.add_constraint(new Clutter.BindConstraint({ + source: dash._dashContainer, + coordinate: orientation ? Clutter.BindCoordinate.HEIGHT : Clutter.BindCoordinate.WIDTH, + })); + dash._box.remove_all_children(); + dash._separator = null; + dash._queueRedisplay(); + dash._adjustIconSize(); + + if (orientation && opt.DASH_BG_GS3_STYLE) { + if (opt.DASH_LEFT) + dash.add_style_class_name('vertical-gs3-left'); + else if (opt.DASH_RIGHT) + dash.add_style_class_name('vertical-gs3-right'); + } else { + dash.remove_style_class_name('vertical-gs3-left'); + dash.remove_style_class_name('vertical-gs3-right'); } + } - Main.overview.dash._showAppsIcon.reactive = true; - _showAppsIconBtnPressId = Main.overview.dash._showAppsIcon.connect('button-press-event', (actor, event) => { - const button = event.get_button(); - if (button === Clutter.BUTTON_MIDDLE) - Util.openPreferences(); - else if (button === Clutter.BUTTON_SECONDARY) - Util.activateSearchProvider(WindowSearchProviderPrefix); - else - return Clutter.EVENT_PROPAGATE; - return Clutter.EVENT_STOP; - }); - } else if (_showAppsIconBtnPressId) { - Main.overview.dash._showAppsIcon.disconnect(_showAppsIconBtnPressId); - _showAppsIconBtnPressId = 0; - Main.overview.dash._showAppsIcon.reactive = false; + _moveDashAppGridIcon(reset = false, dash) { + // move dash app grid icon to the front + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + + const appIconPosition = opt.get('showAppsIconPosition'); + dash._showAppsIcon.remove_style_class_name('show-apps-icon-vertical-hide'); + dash._showAppsIcon.remove_style_class_name('show-apps-icon-horizontal-hide'); + dash._showAppsIcon.opacity = 255; + if (!reset && appIconPosition === 0) // 0 - start + dash._dashContainer.set_child_at_index(dash._showAppsIcon, 0); + if (reset || appIconPosition === 1) { // 1 - end + const index = dash._dashContainer.get_children().length - 1; + dash._dashContainer.set_child_at_index(dash._showAppsIcon, index); + } + if (!reset && appIconPosition === 2) { // 2 - hide + const style = opt.DASH_VERTICAL ? 'show-apps-icon-vertical-hide' : 'show-apps-icon-horizontal-hide'; + dash._showAppsIcon.add_style_class_name(style); + // for some reason even if the icon height in vertical mode should be set to 0 by the style, it stays visible in full size returning height 1px + dash._showAppsIcon.opacity = 0; + } } -} -const DashOverride = { - handleDragOver(source, actor, _x, y, _time) { - let app = Dash.getAppFromSource(source); + _connectShowAppsIcon(reset = false, dash) { + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + if (!reset) { + if (this._showAppsIconBtnPressId || Me.Util.dashIsDashToDock()) { + // button is already connected || dash is Dash to Dock + return; + } + dash._showAppsIcon.reactive = true; + this._showAppsIconBtnPressId = dash._showAppsIcon.connect('button-press-event', (actor, event) => { + const button = event.get_button(); + if (button === Clutter.BUTTON_MIDDLE) + Me.Util.openPreferences(); + else if (button === Clutter.BUTTON_SECONDARY) + Me.Util.activateSearchProvider(Me.WSP_PREFIX); + else + return Clutter.EVENT_PROPAGATE; + return Clutter.EVENT_STOP; + }); + } else if (this._showAppsIconBtnPressId) { + dash._showAppsIcon.disconnect(this._showAppsIconBtnPressId); + this._showAppsIconBtnPressId = 0; + dash._showAppsIcon.reactive = false; + } + } - // Don't allow favoriting of transient apps - if (app === null || app.is_window_backed()) - return DND.DragMotionResult.NO_DROP; + _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON, dash) { + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + const dashContainer = dash._dashContainer; - if (!global.settings.is_writable('favorite-apps')) - return DND.DragMotionResult.NO_DROP; + if (dash._showWindowsIcon) { + dashContainer.remove_child(dash._showWindowsIcon); + if (dash._showWindowsIconClickedId) { + dash._showWindowsIcon.toggleButton.disconnect(dash._showWindowsIconClickedId); + dash._showWindowsIconClickedId = 0; + } + delete dash._showWindowsIconClickedId; + if (dash._showWindowsIcon) + dash._showWindowsIcon.destroy(); + delete dash._showWindowsIcon; + } - let favorites = AppFavorites.getAppFavorites().getFavorites(); - let numFavorites = favorites.length; + if (!show || !opt.get('windowSearchProviderModule')) + return; - let favPos = favorites.indexOf(app); + if (!dash._showWindowsIcon) { + dash._showWindowsIcon = new Dash.DashItemContainer(); + new Me.Util.Overrides().addOverride('showWindowsIcon', dash._showWindowsIcon, ShowWindowsIcon); + dash._showWindowsIcon._afterInit(); + dash._showWindowsIcon.show(false); + dashContainer.add_child(dash._showWindowsIcon); + dash._hookUpLabel(dash._showWindowsIcon); + } - let children = this._box.get_children(); - let numChildren = children.length; - let boxHeight = this._box.height; + dash._showWindowsIcon.icon.setIconSize(dash.iconSize); + if (opt.SHOW_WINDOWS_ICON === 1) { + dashContainer.set_child_at_index(dash._showWindowsIcon, 0); + } else if (opt.SHOW_WINDOWS_ICON === 2) { + const index = dashContainer.get_children().length - 1; + dashContainer.set_child_at_index(dash._showWindowsIcon, index); + } - // Keep the placeholder out of the index calculation; assuming that - // the remove target has the same size as "normal" items, we don't - // need to do the same adjustment there. - if (this._dragPlaceholder) { - boxHeight -= this._dragPlaceholder.height; - numChildren--; + Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); + + if (dash._showWindowsIcon && !dash._showWindowsIconClickedId) { + dash._showWindowsIconClickedId = dash._showWindowsIcon.toggleButton.connect('clicked', () => { + Me.Util.activateSearchProvider(Me.WSP_PREFIX); + }); } + } - // Same with the separator - if (this._separator) { - boxHeight -= this._separator.height; - numChildren--; + _updateRecentFilesIcon(show = opt.SHOW_RECENT_FILES_ICON, dash) { + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + const dashContainer = dash._dashContainer; + + if (dash._recentFilesIcon) { + dashContainer.remove_child(dash._recentFilesIcon); + if (dash._recentFilesIconClickedId) { + dash._recentFilesIcon.toggleButton.disconnect(dash._recentFilesIconClickedId); + dash._recentFilesIconClickedId = 0; + } + delete dash._recentFilesIconClickedId; + if (dash._recentFilesIcon) + dash._recentFilesIcon.destroy(); + delete dash._recentFilesIcon; } - let pos; - if (!this._emptyDropTarget) - pos = Math.floor(y * numChildren / boxHeight); - else - pos = 0; // always insert at the top when dash is empty + if (!show || !opt.get('recentFilesSearchProviderModule')) + return; - // Put the placeholder after the last favorite if we are not - // in the favorites zone - if (pos > numFavorites) - pos = numFavorites; + if (!dash._recentFilesIcon) { + dash._recentFilesIcon = new Dash.DashItemContainer(); + new Me.Util.Overrides().addOverride('recentFilesIcon', dash._recentFilesIcon, ShowRecentFilesIcon); + dash._recentFilesIcon._afterInit(); + dash._recentFilesIcon.show(false); + dashContainer.add_child(dash._recentFilesIcon); + dash._hookUpLabel(dash._recentFilesIcon); + } - if (pos !== this._dragPlaceholderPos && this._animatingPlaceholdersCount === 0) { - this._dragPlaceholderPos = pos; + dash._recentFilesIcon.icon.setIconSize(dash.iconSize); + if (opt.SHOW_RECENT_FILES_ICON === 1) { + dashContainer.set_child_at_index(dash._recentFilesIcon, 0); + } else if (opt.SHOW_RECENT_FILES_ICON === 2) { + const index = dashContainer.get_children().length - 1; + dashContainer.set_child_at_index(dash._recentFilesIcon, index); + } - // Don't allow positioning before or after self - if (favPos !== -1 && (pos === favPos || pos === favPos + 1)) { - this._clearDragPlaceholder(); - return DND.DragMotionResult.CONTINUE; - } + Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); - // If the placeholder already exists, we just move - // it, but if we are adding it, expand its size in - // an animation - let fadeIn; - if (this._dragPlaceholder) { - this._dragPlaceholder.destroy(); - fadeIn = false; - } else { - fadeIn = true; + if (dash._recentFilesIcon && !dash._recentFilesIconClickedId) { + dash._recentFilesIconClickedId = dash._recentFilesIcon.toggleButton.connect('clicked', () => { + Me.Util.activateSearchProvider(Me.RFSP_PREFIX); + }); + } + } + + _updateExtensionsIcon(show = opt.SHOW_EXTENSIONS_ICON, dash) { + dash = dash ?? Main.overview._overview._controls.layoutManager._dash; + const dashContainer = dash._dashContainer; + + if (dash._extensionsIcon) { + dashContainer.remove_child(dash._extensionsIcon); + if (dash._extensionsIconClickedId) { + dash._extensionsIcon.toggleButton.disconnect(dash._extensionsIconClickedId); + dash._extensionsIconClickedId = 0; } + delete dash._extensionsIconClickedId; + if (dash._extensionsIcon) + dash._extensionsIcon.destroy(); + delete dash._extensionsIcon; + } - this._dragPlaceholder = new Dash.DragPlaceholderItem(); - this._dragPlaceholder.child.set_width(this.iconSize / 2); - this._dragPlaceholder.child.set_height(this.iconSize); - this._box.insert_child_at_index(this._dragPlaceholder, - this._dragPlaceholderPos); - this._dragPlaceholder.show(fadeIn); + if (!show || !opt.get('extensionsSearchProviderModule')) + return; + + if (!dash._extensionsIcon) { + dash._extensionsIcon = new Dash.DashItemContainer(); + new Me.Util.Overrides().addOverride('extensionsIcon', dash._extensionsIcon, ShowExtensionsIcon); + dash._extensionsIcon._afterInit(); + dash._extensionsIcon.show(false); + dashContainer.add_child(dash._extensionsIcon); + dash._hookUpLabel(dash._extensionsIcon); } - if (!this._dragPlaceholder) - return DND.DragMotionResult.NO_DROP; + dash._extensionsIcon.icon.setIconSize(dash.iconSize); + if (opt.SHOW_EXTENSIONS_ICON === 1) { + dashContainer.set_child_at_index(dash._extensionsIcon, 0); + } else if (opt.SHOW_EXTENSIONS_ICON === 2) { + const index = dashContainer.get_children().length - 1; + dashContainer.set_child_at_index(dash._extensionsIcon, index); + } - let srcIsFavorite = favPos !== -1; + Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); - if (srcIsFavorite) - return DND.DragMotionResult.MOVE_DROP; + if (dash._extensionsIcon && !dash._extensionsIconClickedId) { + dash._extensionsIconClickedId = dash._extensionsIcon.toggleButton.connect('clicked', () => { + Me.Util.activateSearchProvider(Me.ESP_PREFIX); + }); + } + } +}; - return DND.DragMotionResult.COPY_DROP; +const DashItemContainerCommon = { + // move labels according dash position + showLabel() { + if (!this._labelText) + return; + + const windows = this.child.app?.get_windows(); + const recentWindowTitle = windows && windows.length ? windows[0].get_title() : ''; + const windowCount = this.child.app?.get_windows().length; + let labelSuffix = ''; + if (windowCount > 1) + labelSuffix = ` (${windowCount})`; + if (recentWindowTitle && recentWindowTitle !== this._labelText) + labelSuffix += `\n ${recentWindowTitle}`; + + + this.label.set_text(this._labelText + labelSuffix); + + this.label.opacity = 0; + this.label.show(); + + let [stageX, stageY] = this.get_transformed_position(); + + const itemWidth = this.allocation.get_width(); + const itemHeight = this.allocation.get_height(); + + const labelWidth = this.label.get_width(); + const labelHeight = this.label.get_height(); + let xOffset = Math.floor((itemWidth - labelWidth) / 2); + let x = Math.clamp(stageX + xOffset, 0, global.stage.width - labelWidth); + const primaryMonitor = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + x = Math.clamp(x, primaryMonitor.x, primaryMonitor.x + primaryMonitor.width - labelWidth); + + let node = this.label.get_theme_node(); + let y; + + if (opt.DASH_TOP) { + const yOffset = 0.75 * itemHeight + 3 * node.get_length('-y-offset'); + y = stageY + yOffset; + } else if (opt.DASH_BOTTOM) { + const yOffset = node.get_length('-y-offset'); + y = stageY - this.label.height - yOffset; + } else if (opt.DASH_RIGHT) { + const yOffset = Math.floor((itemHeight - labelHeight) / 2); + xOffset = 4; + + x = stageX - xOffset - this.label.width; + y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); + } else if (opt.DASH_LEFT) { + const yOffset = Math.floor((itemHeight - labelHeight) / 2); + xOffset = 4; + + x = stageX + this.width + xOffset; + y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); + } + + this.label.set_position(x, y); + this.label.ease({ + opacity: 255, + duration: DASH_ITEM_LABEL_SHOW_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + + this.label.set_position(x, y); + this.label.ease({ + opacity: 255, + duration: DASH_ITEM_LABEL_SHOW_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); }, +}; +const DashCommon = { _redisplay() { + // After disabling V-Shell queueRedisplay() may call this function + // In that case redirect the call to the current _redisplay() + if (!_moduleEnabled) { + this._redisplay(); + return; + } + let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); let running = this._appSystem.get_running(); + if (opt.DASH_ISOLATE_WS) { + const currentWs = global.workspace_manager.get_active_workspace(); + running = running.filter(app => { + return app.get_windows().filter(w => w.get_workspace() === currentWs).length; + }); + this._box.get_children().forEach(a => a.child?._updateRunningStyle()); + } + let children = this._box.get_children().filter(actor => { return actor.child && actor.child._delegate && @@ -405,7 +619,8 @@ const DashOverride = { } for (let i = 0; i < addedItems.length; i++) { - this._box.insert_child_at_index(addedItems[i].item, + this._box.insert_child_at_index( + addedItems[i].item, addedItems[i].pos); } @@ -450,14 +665,14 @@ const DashOverride = { style_class: 'dash-separator', x_align: Clutter.ActorAlign.CENTER, y_align: Clutter.ActorAlign.CENTER, - width: this.iconSize, - height: 1, + width: opt.DASH_VERTICAL ? this.iconSize : 1, + height: opt.DASH_VERTICAL ? 1 : this.iconSize, }); this._box.add_child(this._separator); } // FIXME: separator placement is broken (also in original dash) - let pos = nFavorites; + let pos = nFavorites + this._animatingPlaceholdersCount; if (this._dragPlaceholder) pos++; this._box.set_child_at_index(this._separator, pos); @@ -474,8 +689,13 @@ const DashOverride = { let appIcon = new Dash.DashIcon(app); let indicator = appIcon._dot; - indicator.x_align = opt.DASH_LEFT ? Clutter.ActorAlign.START : Clutter.ActorAlign.END; - indicator.y_align = Clutter.ActorAlign.CENTER; + if (opt.DASH_VERTICAL) { + indicator.x_align = opt.DASH_LEFT ? Clutter.ActorAlign.START : Clutter.ActorAlign.END; + indicator.y_align = Clutter.ActorAlign.CENTER; + } else { + indicator.x_align = Clutter.ActorAlign.CENTER; + indicator.y_align = Clutter.ActorAlign.END; + } appIcon.connect('menu-state-changed', (o, opened) => { @@ -495,68 +715,7 @@ const DashOverride = { return item; }, -}; - -const DashItemContainerCommon = { - // move labels according dash position - showLabel() { - if (!this._labelText) - return; - - this.label.set_text(this._labelText); - this.label.opacity = 0; - this.label.show(); - - let [stageX, stageY] = this.get_transformed_position(); - - const itemWidth = this.allocation.get_width(); - const itemHeight = this.allocation.get_height(); - - const labelWidth = this.label.get_width(); - const labelHeight = this.label.get_height(); - let xOffset = Math.floor((itemWidth - labelWidth) / 2); - let x = Math.clamp(stageX + xOffset, 0, global.stage.width - labelWidth); - - let node = this.label.get_theme_node(); - let y; - - if (opt.DASH_TOP) { - const yOffset = itemHeight - labelHeight + 3 * node.get_length('-y-offset'); - y = stageY + yOffset; - } else if (opt.DASH_BOTTOM) { - const yOffset = node.get_length('-y-offset'); - y = stageY - this.label.height - yOffset; - } else if (opt.DASH_RIGHT) { - const yOffset = Math.floor((itemHeight - labelHeight) / 2); - xOffset = 4; - - x = stageX - xOffset - this.label.width; - y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); - } else if (opt.DASH_LEFT) { - const yOffset = Math.floor((itemHeight - labelHeight) / 2); - xOffset = 4; - - x = stageX + this.width + xOffset; - y = Math.clamp(stageY + yOffset, 0, global.stage.height - labelHeight); - } - - this.label.set_position(x, y); - this.label.ease({ - opacity: 255, - duration: DASH_ITEM_LABEL_SHOW_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - - this.label.set_position(x, y); - this.label.ease({ - opacity: 255, - duration: DASH_ITEM_LABEL_SHOW_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - }, -}; -const DashCommon = { // use custom BaseIconSizes and add support for custom icons _adjustIconSize() { // if a user launches multiple apps at once, this function may be called again before the previous call has finished @@ -585,6 +744,8 @@ const DashCommon = { if (this._recentFilesIcon) iconChildren.push(this._recentFilesIcon); + if (this._extensionsIcon) + iconChildren.push(this._extensionsIcon); if (!iconChildren.length) return; @@ -706,168 +867,313 @@ const DashCommon = { this._adjustingInProgress = false; }, + + handleDragOver(source, actor, x, y, _time) { + let app = Dash.getAppFromSource(source); + + // Don't allow favoriting of transient apps + if (app === null || app.is_window_backed()) + return DND.DragMotionResult.NO_DROP; + if (!global.settings.is_writable('favorite-apps')) + return DND.DragMotionResult.NO_DROP; + let favorites = AppFavorites.getAppFavorites().getFavorites(); + let numFavorites = favorites.length; + + let favPos = favorites.indexOf(app); + + let children = this._box.get_children(); + let numChildren = children.length; + let boxSize = opt.DASH_VERTICAL ? this._box.height : this._box.width; + + // Keep the placeholder out of the index calculation; assuming that + // the remove target has the same size as "normal" items, we don't + // need to do the same adjustment there. + if (this._dragPlaceholder) { + boxSize -= opt.DASH_VERTICAL ? this._dragPlaceholder.height : this._dragPlaceholder.width; + numChildren--; + } + + // Same with the separator + if (this._separator) { + boxSize -= opt.DASH_VERTICAL ? this._separator.height : this._separator.width; + numChildren--; + } + + let pos; + if (this._emptyDropTarget) + pos = 0; // always insert at the start when dash is empty + else if (this.text_direction === Clutter.TextDirection.RTL) + pos = numChildren - Math.floor((opt.DASH_VERTICAL ? y : x) * numChildren / boxSize); + else + pos = Math.floor((opt.DASH_VERTICAL ? y : x) * numChildren / boxSize); + + // Put the placeholder after the last favorite if we are not + // in the favorites zone + if (pos > numFavorites) + pos = numFavorites; + + if (pos !== this._dragPlaceholderPos && this._animatingPlaceholdersCount === 0) { + this._dragPlaceholderPos = pos; + + // Don't allow positioning before or after self + if (favPos !== -1 && (pos === favPos || pos === favPos + 1)) { + this._clearDragPlaceholder(); + return DND.DragMotionResult.CONTINUE; + } + + // If the placeholder already exists, we just move + // it, but if we are adding it, expand its size in + // an animation + let fadeIn; + if (this._dragPlaceholder) { + this._dragPlaceholder.destroy(); + fadeIn = false; + } else { + fadeIn = true; + } + + this._dragPlaceholder = new Dash.DragPlaceholderItem(); + this._dragPlaceholder.child.set_width(this.iconSize / (opt.DASH_VERTICAL ? 2 : 1)); + this._dragPlaceholder.child.set_height(this.iconSize / (opt.DASH_VERTICAL ? 1 : 2)); + this._box.insert_child_at_index( + this._dragPlaceholder, + this._dragPlaceholderPos); + this._dragPlaceholder.show(fadeIn); + } + + if (!this._dragPlaceholder) + return DND.DragMotionResult.NO_DROP; + + let srcIsFavorite = favPos !== -1; + + if (srcIsFavorite) + return DND.DragMotionResult.MOVE_DROP; + + return DND.DragMotionResult.COPY_DROP; + }, }; const DashIconCommon = { after__init() { - if (opt.DASH_ICON_SCROLL) { - this._scrollConId = this.connect('scroll-event', _onScrollEvent.bind(this)); - this._leaveConId = this.connect('leave-event', _onLeaveEvent.bind(this)); + if (opt.DASH_ICON_SCROLL && !Me.Util.dashNotDefault()) { + this._scrollConId = this.connect('scroll-event', DashExtensions.onScrollEvent.bind(this)); + this._leaveConId = this.connect('leave-event', DashExtensions.onLeaveEvent.bind(this)); } }, -}; -function _onScrollEvent(source, event) { - if ((this.app && !opt.DASH_ICON_SCROLL) || (this._isSearchWindowsIcon && !opt.SEARCH_WINDOWS_ICON_SCROLL)) { - if (this._scrollConId) - this.disconnect(this._scrollConId); - if (this._leaveConId) - this.disconnect(this._leaveConId); - return Clutter.EVENT_PROPAGATE; - } + popupMenu() { + const side = opt.DASH_VERTICAL ? St.Side.LEFT : St.Side.BOTTOM; + AppIconCommon.popupMenu.bind(this)(side); + }, - let direction = Util.getScrollDirection(event); - if (direction === Clutter.ScrollDirection.UP) - direction = 1; - else if (direction === Clutter.ScrollDirection.DOWN) - direction = -1; - else - return Clutter.EVENT_STOP; + _updateRunningStyle() { + const currentWs = global.workspace_manager.get_active_workspace(); + const show = opt.DASH_ISOLATE_WS + ? this.app.get_windows().filter(w => w.get_workspace() === currentWs).length + : this.app.state !== Shell.AppState.STOPPED; - // avoid uncontrollable switching if smooth scroll wheel or trackpad is used - if (this._lastScroll && Date.now() - this._lastScroll < 160) - return Clutter.EVENT_STOP; + if (show) + this._dot.show(); + else + this._dot.hide(); + }, +}; - this._lastScroll = Date.now(); - - _switchWindow.bind(this)(direction); - return Clutter.EVENT_STOP; -} - -function _onLeaveEvent() { - if (!this._selectedMetaWin || this.has_pointer || this.toggleButton?.has_pointer) - return; - - this._selectedPreview._activateSelected = false; - this._selectedMetaWin = null; - this._scrolledWindows = null; - _showWindowPreview.bind(this)(null); -} - -function _switchWindow(direction) { - if (!this._scrolledWindows) { - // source is app icon - if (this.app) { - this._scrolledWindows = this.app.get_windows(); - const wsList = []; - this._scrolledWindows.forEach(w => { - const ws = w.get_workspace(); - if (!wsList.includes(ws)) - wsList.push(ws); - }); - // sort windows by workspaces in MRU order - this._scrolledWindows.sort((a, b) => wsList.indexOf(a.get_workspace()) > wsList.indexOf(b.get_workspace())); - // source is Search Windows icon - } else if (this._isSearchWindowsIcon) { - if (opt.SEARCH_WINDOWS_ICON_SCROLL === 1) // all windows - this._scrolledWindows = AltTab.getWindows(null); - else - this._scrolledWindows = AltTab.getWindows(global.workspace_manager.get_active_workspace()); +const DashExtensions = { + onScrollEvent(source, event) { + if ((this.app && !opt.DASH_ICON_SCROLL) || (this._isSearchWindowsIcon && !opt.SEARCH_WINDOWS_ICON_SCROLL)) { + if (this._scrollConId) { + this.disconnect(this._scrollConId); + this._scrollConId = 0; + } + if (this._leaveConId) { + this.disconnect(this._leaveConId); + this._leaveConId = 0; + } + return Clutter.EVENT_PROPAGATE; } - } - let windows = this._scrolledWindows; + if (Main.overview._overview.controls._stateAdjustment.value > 1) + return Clutter.EVENT_PROPAGATE; + + let direction = Me.Util.getScrollDirection(event); + if (direction === Clutter.ScrollDirection.UP) + direction = 1; + else if (direction === Clutter.ScrollDirection.DOWN) + direction = -1; + else + return Clutter.EVENT_STOP; + + // avoid uncontrollable switching if smooth scroll wheel or trackpad is used + if (this._lastScroll && Date.now() - this._lastScroll < 160) + return Clutter.EVENT_STOP; - if (!windows.length) - return; + this._lastScroll = Date.now(); - // if window selection is in the process, the previewed window must be the current one - let currentWin = this._selectedMetaWin ? this._selectedMetaWin : windows[0]; + DashExtensions.switchWindow.bind(this)(direction); + return Clutter.EVENT_STOP; + }, - const currentIdx = windows.indexOf(currentWin); - let targetIdx = currentIdx + direction; + onLeaveEvent() { + if (!this._selectedMetaWin || this.has_pointer || this.toggleButton?.has_pointer) + return; - if (targetIdx > windows.length - 1) - targetIdx = 0; - else if (targetIdx < 0) - targetIdx = windows.length - 1; + this._selectedPreview._activateSelected = false; + this._selectedMetaWin = null; + this._scrolledWindows = null; + DashExtensions.showWindowPreview.bind(this)(null); + }, - const metaWin = windows[targetIdx]; - _showWindowPreview.bind(this)(metaWin); - this._selectedMetaWin = metaWin; -} -function _showWindowPreview(metaWin) { - const views = Main.overview._overview.controls._workspacesDisplay._workspacesViews; - const viewsIter = [views[0]]; - // secondary monitors use different structure - views.forEach(v => { - if (v._workspacesView) - viewsIter.push(v._workspacesView); - }); + switchWindow(direction) { + if (!this._scrolledWindows) { + this._initialSelection = true; + // source is app icon + if (this.app) { + this._scrolledWindows = this.app.get_windows(); + if (opt.DASH_ISOLATE_WS) { + const currentWs = global.workspaceManager.get_active_workspace(); + this._scrolledWindows = this._scrolledWindows.filter(w => w.get_workspace() === currentWs); + } - viewsIter.forEach(view => { - // if workspaces are on primary monitor only - if (!view || !view._workspaces) + const wsList = []; + this._scrolledWindows.forEach(w => { + const ws = w.get_workspace(); + if (!wsList.includes(ws)) + wsList.push(ws); + }); + + // sort windows by workspaces in MRU order + this._scrolledWindows.sort((a, b) => wsList.indexOf(a.get_workspace()) > wsList.indexOf(b.get_workspace())); + // source is Search Windows icon + } else if (this._isSearchWindowsIcon) { + if (opt.SEARCH_WINDOWS_ICON_SCROLL === 1) // all windows + this._scrolledWindows = AltTab.getWindows(null); + else + this._scrolledWindows = AltTab.getWindows(global.workspace_manager.get_active_workspace()); + } + } + + let windows = this._scrolledWindows; + + if (!windows.length) return; - view._workspaces.forEach(ws => { - ws._windows.forEach(windowPreview => { - // metaWin === null resets opacity - let opacity = metaWin ? 50 : 255; - windowPreview._activateSelected = false; + // if window selection is in the process, the previewed window must be the current one + let currentWin = this._selectedMetaWin ? this._selectedMetaWin : windows[0]; + + const currentIdx = windows.indexOf(currentWin); + let targetIdx = currentIdx; + const focusWindow = AltTab.getWindows(null)[0]; + const appFocused = this._scrolledWindows[0] === focusWindow && this._scrolledWindows[0].get_workspace() === global.workspace_manager.get_active_workspace(); + // only if the app has focus, immediately switch to the previous window + // otherwise just set the current window above others + if (!this._initialSelection || appFocused) + targetIdx += direction; + else + this._initialSelection = false; - // minimized windows are invisible if windows are not exposed (WORKSPACE_MODE === 0) - if (!windowPreview.opacity) - windowPreview.opacity = 255; + if (targetIdx > windows.length - 1) + targetIdx = 0; + else if (targetIdx < 0) + targetIdx = windows.length - 1; - // app windows set to lower opacity, so they can be recognized - if (this._scrolledWindows && this._scrolledWindows.includes(windowPreview.metaWindow)) { - if (opt.DASH_ICON_SCROLL === 2) - opacity = 254; - } - if (windowPreview.metaWindow === metaWin) { - if (metaWin && metaWin.get_workspace() !== global.workspace_manager.get_active_workspace()) - Main.wm.actionMoveWorkspace(metaWin.get_workspace()); + const metaWin = windows[targetIdx]; + DashExtensions.showWindowPreview.bind(this)(metaWin); + this._selectedMetaWin = metaWin; + }, - windowPreview.get_parent().set_child_above_sibling(windowPreview, null); + showWindowPreview(metaWin) { + const views = Main.overview._overview.controls._workspacesDisplay._workspacesViews; + const viewsIter = [views[0]]; + // secondary monitors use different structure + views.forEach(v => { + if (v._workspacesView) + viewsIter.push(v._workspacesView); + }); - opacity = 255; - this._selectedPreview = windowPreview; - windowPreview._activateSelected = true; - } + viewsIter.forEach(view => { + // if workspaces are on primary monitor only + if (!view || !view._workspaces) + return; - // if windows are exposed, highlight selected using opacity - if ((opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) || !opt.OVERVIEW_MODE) { - if (metaWin && opacity === 255) - windowPreview.showOverlay(true); - else - windowPreview.hideOverlay(true); - windowPreview.ease({ - duration: 200, - opacity, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - }); - } + view._workspaces.forEach(ws => { + ws._windows.forEach(windowPreview => { + // metaWin === null resets opacity + let opacity = metaWin ? 50 : 255; + windowPreview._activateSelected = false; + + // minimized windows are invisible if windows are not exposed (WORKSPACE_MODE === 0) + if (!windowPreview.opacity) + windowPreview.opacity = 255; + + // app windows set to lower opacity, so they can be recognized + if (this._scrolledWindows && this._scrolledWindows.includes(windowPreview.metaWindow)) { + if (opt.DASH_ICON_SCROLL === 2) + opacity = 254; + } + if (windowPreview.metaWindow === metaWin) { + if (metaWin && metaWin.get_workspace() !== global.workspace_manager.get_active_workspace()) { + Main.wm.actionMoveWorkspace(metaWin.get_workspace()); + if (_timeouts.wsSwitcherAnimation) + GLib.source_remove(_timeouts.wsSwitcherAnimation); + // setting window preview above siblings before workspace switcher animation has no effect + // we need to set the window above after the ws preview become visible on the screen + // the default switcher animation time is 250, 200 ms delay should be enough + _timeouts.wsSwitcherAnimation = GLib.timeout_add(0, 200 * St.Settings.get().slow_down_factor, () => { + windowPreview.get_parent().set_child_above_sibling(windowPreview, null); + _timeouts.wsSwitcherAnimation = 0; + return GLib.SOURCE_REMOVE; + }); + } else { + windowPreview.get_parent().set_child_above_sibling(windowPreview, null); + } + + opacity = 255; + this._selectedPreview = windowPreview; + windowPreview._activateSelected = true; + } + + // if windows are exposed, highlight selected using opacity + if ((opt.OVERVIEW_MODE && opt.WORKSPACE_MODE) || !opt.OVERVIEW_MODE) { + if (metaWin && opacity === 255) + windowPreview.showOverlay(true); + else + windowPreview.hideOverlay(true); + windowPreview.ease({ + duration: 200, + opacity, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + }); }); }); - }); -} + }, +}; const AppIconCommon = { + after__init() { + if (this._updateRunningDotStyle) + this._updateRunningDotStyle(); + }, + + _updateRunningDotStyle() { + if (opt.RUNNING_DOT_STYLE) + this._dot.add_style_class_name('app-well-app-running-dot-custom'); + else + this._dot.remove_style_class_name('app-well-app-running-dot-custom'); + }, + activate(button) { const event = Clutter.get_current_event(); const state = event ? event.get_state() : 0; const isMiddleButton = button && button === Clutter.BUTTON_MIDDLE; - const isCtrlPressed = Util.isCtrlPressed(state); - const isShiftPressed = Util.isShiftPressed(state); - const openNewWindow = (this.app.can_open_new_window() && - this.app.state === Shell.AppState.RUNNING && - (isCtrlPressed || isMiddleButton) && !opt.DASH_CLICK_ACTION === 2) || - (opt.DASH_CLICK_ACTION === 2 && !this._selectedMetaWin && !isMiddleButton); + const isCtrlPressed = Me.Util.isCtrlPressed(state); + const isShiftPressed = Me.Util.isShiftPressed(state); const currentWS = global.workspace_manager.get_active_workspace(); - const appRecentWorkspace = _getAppRecentWorkspace(this.app); + const appRecentWorkspace = this._getAppRecentWorkspace(this.app); // this feature shouldn't affect search results, dash icons don't have labels, so we use them as a condition const showWidowsBeforeActivation = opt.DASH_CLICK_ACTION === 1 && !this.icon.label; @@ -882,6 +1188,12 @@ const AppIconCommon = { ); } + const openNewWindow = this.app.can_open_new_window() && + this.app.state === Shell.AppState.RUNNING && + (((isCtrlPressed || isMiddleButton) && !opt.DASH_CLICK_OPEN_NEW_WIN) || + (opt.DASH_CLICK_OPEN_NEW_WIN && !this._selectedMetaWin && !isMiddleButton) || + ((opt.DASH_CLICK_PREFER_WORKSPACE || opt.DASH_ISOLATE_WS) && !targetWindowOnCurrentWs)); + if ((this.app.state === Shell.AppState.STOPPED || openNewWindow) && !isShiftPressed) this.animateLaunch(); @@ -890,19 +1202,23 @@ const AppIconCommon = { // if DASH_CLICK_ACTION == "SHOW_WINS_BEFORE", the app has more than one window and has no window on the current workspace, // don't activate the app immediately, only move the overview to the workspace with the app's recent window } else if (showWidowsBeforeActivation && !isShiftPressed && this.app.get_n_windows() > 1 && !targetWindowOnCurrentWs/* && !(opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE)*/) { - // this._scroll = true; - // this._scrollTime = Date.now(); + Main.wm.actionMoveWorkspace(appRecentWorkspace); Main.overview.dash.showAppsButton.checked = false; return; } else if (this._selectedMetaWin) { this._selectedMetaWin.activate(global.get_current_time()); - } else if (showWidowsBeforeActivation && opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE && !isShiftPressed && this.app.get_n_windows() > 1) { + } else if (showWidowsBeforeActivation && opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && !isShiftPressed && this.app.get_n_windows() > 1) { // expose windows Main.overview._overview._controls._thumbnailsBox._activateThumbnailAtPoint(0, 0, global.get_current_time(), true); return; - } else if (opt.DASH_SHIFT_CLICK_MV && isShiftPressed && this.app.get_windows().length) { + } else if (((opt.DASH_SHIFT_CLICK_MV && isShiftPressed) || ((opt.DASH_CLICK_PREFER_WORKSPACE || opt.DASH_ISOLATE_WS) && !openNewWindow)) && this.app.get_windows().length) { this._moveAppToCurrentWorkspace(); + if (opt.DASH_ISOLATE_WS) { + this.app.activate(); + // hide the overview after the window is re-created + GLib.idle_add(GLib.PRIORITY_LOW, () => Main.overview.hide()); + } return; } else if (isShiftPressed) { return; @@ -918,8 +1234,7 @@ const AppIconCommon = { }, popupMenu(side = St.Side.LEFT) { - if (shellVersion >= 42) - this.setForcedHighlight(true); + this.setForcedHighlight(true); this._removeMenuTimeout(); this.fake_release(); @@ -939,7 +1254,7 @@ const AppIconCommon = { } if (!this._menu) { - this._menu = new AppMenu(this, side, { + this._menu = new AppMenu.AppMenu(this, side, { favoritesSection: true, showSingleWindows: true, }); @@ -986,8 +1301,12 @@ const AppIconCommon = { }]); } - if (/* opt.APP_MENU_MV_TO_WS && */this._windowsOnOtherWs()) - popupItems.push([_('Move App to Current Workspace ( Shift + Click )'), this._moveAppToCurrentWorkspace]); + popupItems.push([_('Move App to Current Workspace ( Shift + Click )'), this._moveAppToCurrentWorkspace]); + if (opt.WINDOW_THUMBNAIL_ENABLED) { + popupItems.push([_('Create Window Thumbnail - PIP'), () => { + Me.Modules.winTmbModule.createThumbnail(this.app.get_windows()[0]); + }]); + } } this._addedMenuItems = []; @@ -996,6 +1315,8 @@ const AppIconCommon = { let item = new PopupMenu.PopupMenuItem(i[0]); this._menu.addMenuItem(item); item.connect('activate', i[1].bind(this)); + if (i[1] === this._moveAppToCurrentWorkspace && !this._windowsOnOtherWs()) + item.setSensitive(false); this._addedMenuItems.push(item); }); @@ -1007,71 +1328,33 @@ const AppIconCommon = { return false; }, -}; -function _getWindowApp(metaWin) { - const tracker = Shell.WindowTracker.get_default(); - return tracker.get_window_app(metaWin); -} - -function _getAppLastUsedWindow(app) { - let recentWin; - global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => { - const winApp = _getWindowApp(metaWin); - if (!recentWin && winApp === app) - recentWin = metaWin; - }); - return recentWin; -} - -function _getAppRecentWorkspace(app) { - const recentWin = _getAppLastUsedWindow(app); - if (recentWin) - return recentWin.get_workspace(); - - return null; -} - -function _updateSearchWindowsIcon(show = opt.SHOW_WINDOWS_ICON) { - const dash = Main.overview._overview._controls.layoutManager._dash; - const dashContainer = dash._dashContainer; - - if (dash._showWindowsIcon) { - dashContainer.remove_child(dash._showWindowsIcon); - if (dash._showWindowsIconClickedId) - dash._showWindowsIcon.toggleButton.disconnect(dash._showWindowsIconClickedId); - dash._showWindowsIconClickedId = undefined; - if (dash._showWindowsIcon) - dash._showWindowsIcon.destroy(); - dash._showWindowsIcon = undefined; - } - - if (!show || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) - return; - - if (!dash._showWindowsIcon) { - dash._showWindowsIcon = new ShowWindowsIcon(); - dash._showWindowsIcon.show(false); - dashContainer.add_child(dash._showWindowsIcon); - dash._hookUpLabel(dash._showWindowsIcon); - } + _getWindowApp(metaWin) { + const tracker = Shell.WindowTracker.get_default(); + return tracker.get_window_app(metaWin); + }, - dash._showWindowsIcon.icon.setIconSize(dash.iconSize); - if (opt.SHOW_WINDOWS_ICON === 1) { - dashContainer.set_child_at_index(dash._showWindowsIcon, 0); - } else if (opt.SHOW_WINDOWS_ICON === 2) { - const index = dashContainer.get_children().length - 1; - dashContainer.set_child_at_index(dash._showWindowsIcon, index); - } + _getAppLastUsedWindow(app) { + let recentWin; + global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null).forEach(metaWin => { + const winApp = this._getWindowApp(metaWin); + if (!recentWin && winApp === app) + recentWin = metaWin; + }); + return recentWin; + }, - Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); -} + _getAppRecentWorkspace(app) { + const recentWin = this._getAppLastUsedWindow(app); + if (recentWin) + return recentWin.get_workspace(); -const ShowWindowsIcon = GObject.registerClass( -class ShowWindowsIcon extends Dash.DashItemContainer { - _init() { - super._init(); + return null; + }, +}; +const ShowWindowsIcon = { + _afterInit() { this._isSearchWindowsIcon = true; this._labelText = _('Search Open Windows (Hotkey: Space)'); this.toggleButton = new St.Button({ @@ -1096,10 +1379,10 @@ class ShowWindowsIcon extends Dash.DashItemContainer { if (opt.SEARCH_WINDOWS_ICON_SCROLL) { this.reactive = true; - this._scrollConId = this.connect('scroll-event', _onScrollEvent.bind(this)); - this._leaveConId = this.connect('leave-event', _onLeaveEvent.bind(this)); + this._scrollConId = this.connect('scroll-event', DashExtensions.onScrollEvent.bind(this)); + this._leaveConId = this.connect('leave-event', DashExtensions.onLeaveEvent.bind(this)); } - } + }, _createIcon(size) { this._iconActor = new St.Icon({ @@ -1109,50 +1392,47 @@ class ShowWindowsIcon extends Dash.DashItemContainer { track_hover: true, }); return this._iconActor; - } -}); - -function _updateRecentFilesIcon(show = opt.SHOW_RECENT_FILES_ICON) { - const dash = Main.overview._overview._controls.layoutManager._dash; - const dashContainer = dash._dashContainer; - - if (dash._recentFilesIcon) { - dashContainer.remove_child(dash._recentFilesIcon); - if (dash._recentFilesIconClickedId) - dash._recentFilesIcon.toggleButton.disconnect(dash._recentFilesIconClickedId); - dash._recentFilesIconClickedId = undefined; - if (dash._recentFilesIcon) - dash._recentFilesIcon.destroy(); - dash._recentFilesIcon = undefined; - } + }, +}; - if (!show || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) - return; +const ShowRecentFilesIcon = { + _afterInit() { + this._labelText = _('Search Recent Files (Hotkey: Ctrl + Space)'); + this.toggleButton = new St.Button({ + style_class: 'show-apps', + track_hover: true, + can_focus: true, + toggle_mode: false, + }); - if (!dash._recentFilesIcon) { - dash._recentFilesIcon = new ShowRecentFilesIcon(); - dash._recentFilesIcon.show(false); - dashContainer.add_child(dash._recentFilesIcon); - dash._hookUpLabel(dash._recentFilesIcon); - } + this._iconActor = null; + this.icon = new IconGrid.BaseIcon(this.labelText, { + setSizeManually: true, + showLabel: false, + createIcon: this._createIcon.bind(this), + }); + this.icon.y_align = Clutter.ActorAlign.CENTER; - dash._recentFilesIcon.icon.setIconSize(dash.iconSize); - if (opt.SHOW_RECENT_FILES_ICON === 1) { - dashContainer.set_child_at_index(dash._recentFilesIcon, 0); - } else if (opt.SHOW_RECENT_FILES_ICON === 2) { - const index = dashContainer.get_children().length - 1; - dashContainer.set_child_at_index(dash._recentFilesIcon, index); - } + this.toggleButton.add_actor(this.icon); + this.toggleButton._delegate = this; - Main.overview._overview._controls.layoutManager._dash._adjustIconSize(); -} + this.setChild(this.toggleButton); + }, -const ShowRecentFilesIcon = GObject.registerClass( -class ShowRecentFilesIcon extends Dash.DashItemContainer { - _init() { - super._init(); + _createIcon(size) { + this._iconActor = new St.Icon({ + icon_name: 'document-open-recent-symbolic', + icon_size: size, + style_class: 'show-apps-icon', + track_hover: true, + }); + return this._iconActor; + }, +}; - this._labelText = _('Search Recent Files (Hotkey: Ctrl + Space)'); +const ShowExtensionsIcon = { + _afterInit() { + this._labelText = _('Search Extensions (Hotkey: Ctrl + Shift + Space)'); this.toggleButton = new St.Button({ style_class: 'show-apps', track_hover: true, @@ -1172,15 +1452,55 @@ class ShowRecentFilesIcon extends Dash.DashItemContainer { this.toggleButton._delegate = this; this.setChild(this.toggleButton); - } + }, _createIcon(size) { this._iconActor = new St.Icon({ - icon_name: 'document-open-recent-symbolic', + icon_name: 'application-x-addon-symbolic', icon_size: size, style_class: 'show-apps-icon', track_hover: true, }); return this._iconActor; - } -}); + }, +}; + +const AppMenuCommon = { + _updateWindowsSection() { + if (global.compositor) { + if (this._updateWindowsLaterId) { + const laters = global.compositor.get_laters(); + laters.remove(this._updateWindowsLaterId); + } + } else if (this._updateWindowsLaterId) { + Meta.later_remove(this._updateWindowsLaterId); + } + + this._updateWindowsLaterId = 0; + + this._windowSection.removeAll(); + this._openWindowsHeader.hide(); + + if (!this._app) + return; + + const minWindows = this._showSingleWindows ? 1 : 2; + const currentWs = global.workspaceManager.get_active_workspace(); + const isolateWs = opt.DASH_ISOLATE_WS && !Main.overview.dash.showAppsButton.checked; + const windows = this._app.get_windows().filter(w => !w.skip_taskbar && (isolateWs ? w.get_workspace() === currentWs : true)); + if (windows.length < minWindows) + return; + + this._openWindowsHeader.show(); + + windows.forEach(window => { + const title = window.title || this._app.get_name(); + const item = this._windowSection.addAction(title, event => { + Main.activateWindow(window, event.get_time()); + }); + window.connectObject('notify::title', () => { + item.label.text = window.title || this._app.get_name(); + }, item); + }); + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js b/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js new file mode 100644 index 0000000..5d0f28a --- /dev/null +++ b/extensions/44/vertical-workspaces/lib/extensionsSearchProvider.js @@ -0,0 +1,423 @@ +/** +* V-Shell (Vertical Workspaces) + * extensionsSearchProvider.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + */ + +'use strict'; + +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const St = imports.gi.St; +const Shell = imports.gi.Shell; +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; + +const Main = imports.ui.main; + +const ExtensionState = { + 1: 'ENABLED', + 2: 'DISABLED', + 3: 'ERROR', + 4: 'INCOMPATIBLE', + 5: 'DOWNLOADING', + 6: 'INITIALIZED', + 7: 'DISABLING', + 8: 'ENABLING', +}; + +let Me; +let opt; +// gettext +let _; +let _toggleTimeout; + +// prefix helps to eliminate results from other search providers +// so it needs to be something less common +// needs to be accessible from vw module +const PREFIX = 'eq//'; + +var ExtensionsSearchProviderModule = class { + // export for other modules + static _PREFIX = PREFIX; + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + this._extensionsSearchProvider = null; + this._enableTimeoutId = 0; + } + + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } + + update(reset) { + if (_toggleTimeout) + GLib.source_remove(_toggleTimeout); + + this.moduleEnabled = opt.get('extensionsSearchProviderModule'); + + reset = reset || !this.moduleEnabled; + + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' ExtensionsSearchProviderModule - Keeping untouched'); + } + + _activateModule() { + // delay because Fedora had problem to register a new provider soon after Shell restarts + this._enableTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + 2000, + () => { + if (!this._extensionsSearchProvider) { + this._extensionsSearchProvider = new extensionsSearchProvider(opt); + this._getOverviewSearchResult()._registerProvider(this._extensionsSearchProvider); + } + this._enableTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + ); + console.debug(' ExtensionsSearchProviderModule - Activated'); + } + + _disableModule() { + if (this._enableTimeoutId) { + GLib.source_remove(this._enableTimeoutId); + this._enableTimeoutId = 0; + } + + if (this._extensionsSearchProvider) { + this._getOverviewSearchResult()._unregisterProvider(this._extensionsSearchProvider); + this._extensionsSearchProvider = null; + } + + console.debug(' ExtensionsSearchProviderModule - Disabled'); + } + + _getOverviewSearchResult() { + return Main.overview._overview.controls._searchController._searchResults; + } +}; + +class extensionsSearchProvider { + constructor() { + this.id = 'extensions'; + const appSystem = Shell.AppSystem.get_default(); + let appInfo = appSystem.lookup_app('com.matjakeman.ExtensionManager.desktop')?.get_app_info(); + if (!appInfo) + appInfo = appSystem.lookup_app('org.gnome.Extensions.desktop')?.get_app_info(); + if (!appInfo) + appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/gnome-extensions-app', 'Extensions', null); + appInfo.get_description = () => _('Search extensions'); + appInfo.get_name = () => _('Extensions'); + appInfo.get_id = () => 'org.gnome.Extensions.desktop'; + appInfo.get_icon = () => Gio.icon_new_for_string('application-x-addon'); + appInfo.should_show = () => true; + + this.appInfo = appInfo; + this.canLaunchSearch = true; + this.isRemoteProvider = false; + } + + getInitialResultSet(terms, callback /* , cancellable = null*/) { + // In GS 43 callback arg has been removed + /* if (Me.shellVersion >= 43) + cancellable = callback; */ + + const extensions = {}; + Main.extensionManager._extensions.forEach( + e => { + extensions[e.uuid] = e; + } + ); + this.extensions = extensions; + + if (Me.shellVersion >= 43) + return new Promise(resolve => resolve(this._getResultSet(terms))); + else + callback(this._getResultSet(terms)); + + return null; + } + + _getResultSet(terms) { + // do not modify original terms + let termsCopy = [...terms]; + // search for terms without prefix + termsCopy[0] = termsCopy[0].replace(PREFIX, ''); + + const candidates = this.extensions; + const _terms = [].concat(termsCopy); + + const term = _terms.join(' '); + + const results = []; + let m; + for (let id in candidates) { + const extension = this.extensions[id]; + const text = extension.metadata.name + (extension.state === 1 ? 'enabled' : '') + ([6, 2].includes(extension.state) ? 'disabled' : ''); + if (opt.SEARCH_FUZZY) + m = Me.Util.fuzzyMatch(term, text); + else + m = Me.Util.strictMatch(term, text); + + if (m !== -1) + results.push({ weight: m, id }); + } + + // sort alphabetically + results.sort((a, b) => this.extensions[a.id].metadata.name.localeCompare(this.extensions[b.id].metadata.name)); + // enabled first + // results.sort((a, b) => this.extensions[a.id].state !== 1 && this.extensions[b.id].state === 1); + // incompatible last + results.sort((a, b) => this.extensions[a.id].state === 4 && this.extensions[b.id].state !== 4); + + this.resultIds = results.map(item => item.id); + return this.resultIds; + } + + getResultMetas(resultIds, callback = null) { + const metas = resultIds.map(id => this.getResultMeta(id)); + if (Me.shellVersion >= 43) + return new Promise(resolve => resolve(metas)); + else if (callback) + callback(metas); + return null; + } + + getResultMeta(resultId) { + const result = this.extensions[resultId]; + + const versionName = result.metadata['version-name'] ?? ''; + let version = result.metadata['version'] ?? ''; + version = versionName && version ? `/${version}` : version; + const versionStr = `${versionName}${version}`; + + return { + 'id': resultId, + 'name': `${result.metadata.name}`, + 'version': versionStr, + 'description': versionStr, // description will be updated in result object + 'createIcon': size => { + let icon = this.getIcon(result, size); + return icon; + }, + }; + } + + getIcon(extension, size) { + let opacity = 0; + let iconName = 'process-stop-symbolic'; + + switch (extension.state) { + case 1: + if (extension.hasUpdate) + iconName = 'software-update-available'; // 'software-update-available-symbolic'; + else + iconName = 'object-select';// 'object-select-symbolic'; + + opacity = 255; + break; + case 3: + if (Main.extensionManager._enabledExtensions.includes(extension.uuid)) + iconName = 'emblem-ok-symbolic'; + else + iconName = 'dialog-error'; + opacity = 100; + break; + case 4: + iconName = 'software-update-urgent'; // 'software-update-urgent-symbolic'; + opacity = 100; + break; + } + + if (extension.hasUpdate) { + iconName = 'software-update-available'; // 'software-update-available-symbolic'; + opacity = 100; + } + + const icon = new St.Icon({ icon_name: iconName, icon_size: size }); + icon.set({ + reactive: true, + opacity, + }); + + return icon; + } + + createResultObject(meta) { + return new ListSearchResult(this, meta, this.extensions[meta.id]); + } + + launchSearch(terms, timeStamp) { + this.appInfo.launch([], global.create_app_launch_context(timeStamp, -1), null); + } + + activateResult(resultId/* terms, timeStamp*/) { + const extension = this.extensions[resultId]; + if (Me.Util.isShiftPressed()) + this._toggleExtension(extension); + else if (extension.hasPrefs) + Me.Util.openPreferences(extension.metadata); + } + + filterResults(results /* , maxResults*/) { + // return results.slice(0, maxResults); + return results; + } + + getSubsearchResultSet(previousResults, terms, callback) { + if (Me.shellVersion < 43) { + this.getSubsearchResultSet42(terms, callback); + return null; + } + return this.getInitialResultSet(terms); + } + + getSubsearchResultSet42(terms, callback) { + callback(this._getResultSet(terms)); + } +} + +const ListSearchResult = GObject.registerClass( +class ListSearchResult extends St.Button { + _init(provider, metaInfo, extension) { + this.provider = provider; + this.metaInfo = metaInfo; + this.extension = extension; + + super._init({ + reactive: true, + can_focus: true, + track_hover: true, + }); + + this.style_class = 'list-search-result'; + + let content = new St.BoxLayout({ + style_class: 'list-search-result-content', + vertical: false, + x_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + }); + this.set_child(content); + + let titleBox = new St.BoxLayout({ + style_class: 'list-search-result-title', + y_align: Clutter.ActorAlign.CENTER, + }); + + content.add_child(titleBox); + + // An icon for, or thumbnail of, content + let icon = this.metaInfo['createIcon'](this.ICON_SIZE); + let iconBox = new St.Button(); + iconBox.set_child(icon); + titleBox.add(iconBox); + iconBox.set_style('border: 1px solid rgba(200,200,200,0.2); padding: 2px; border-radius: 8px;'); + this._iconBox = iconBox; + this.icon = icon; + + iconBox.connect('clicked', () => { + this._toggleExtension(); + return Clutter.EVENT_STOP; + }); + + let title = new St.Label({ + text: this.metaInfo['name'], + y_align: Clutter.ActorAlign.CENTER, + opacity: extension.hasPrefs ? 255 : 150, + }); + titleBox.add_child(title); + + this.label_actor = title; + + this._descriptionLabel = new St.Label({ + style_class: 'list-search-result-description', + y_align: Clutter.ActorAlign.CENTER, + }); + content.add_child(this._descriptionLabel); + + this._highlightTerms(); + + this.connect('destroy', () => { + if (_toggleTimeout) { + GLib.source_remove(_toggleTimeout); + _toggleTimeout = 0; + } + }); + } + + _toggleExtension() { + const state = this.extension.state; + if (![1, 2, 6, 3].includes(state) || this.extension.metadata.uuid.includes('vertical-workspaces')) + return; + + if ([2, 6].includes(state)) + Main.extensionManager.enableExtension(this.extension.uuid); + else if ([1, 3].includes(state)) + Main.extensionManager.disableExtension(this.extension.uuid); + + if (_toggleTimeout) + GLib.source_remove(_toggleTimeout); + + _toggleTimeout = GLib.timeout_add(GLib.PRIORITY_LOW, 200, + () => { + if ([7, 8].includes(this.extension.state)) + return GLib.SOURCE_CONTINUE; + + this.icon?.destroy(); + this.icon = this.metaInfo['createIcon'](this.ICON_SIZE); + this._iconBox.set_child(this.icon); + this._highlightTerms(); + + _toggleTimeout = 0; + return GLib.SOURCE_REMOVE; + } + ); + } + + get ICON_SIZE() { + return 24; + } + + _highlightTerms() { + const extension = this.extension; + const state = extension.state === 4 ? ExtensionState[this.extension.state] : ''; + const error = extension.state === 3 ? ` ERROR: ${this.extension.error}` : ''; + const update = extension.hasUpdate ? ' | UPDATE PENDING' : ''; + const text = `${this.metaInfo.version} ${state}${error}${update}`; + let markup = text;// this.metaInfo['description'].split('\n')[0]; + this._descriptionLabel.clutter_text.set_markup(markup); + } + + vfunc_clicked() { + this.activate(); + } + + activate() { + this.provider.activateResult(this.metaInfo.id); + + if (this.metaInfo.clipboardText) { + St.Clipboard.get_default().set_text( + St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText); + } + Main.overview.toggle(); + } +}); diff --git a/extensions/44/vertical-workspaces/lib/iconGrid.js b/extensions/44/vertical-workspaces/lib/iconGrid.js index 1aa980e..1f7516b 100644 --- a/extensions/44/vertical-workspaces/lib/iconGrid.js +++ b/extensions/44/vertical-workspaces/lib/iconGrid.js @@ -9,11 +9,13 @@ */ 'use strict'; -const { GLib, St, Meta } = imports.gi; + +const St = imports.gi.St; + const IconGrid = imports.ui.iconGrid; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; -const shellVersion = _Util.shellVersion; + +let Me; +let opt; // added sizes for better scaling const IconSize = { @@ -29,47 +31,58 @@ const IconSize = { LARGE: 96, 80: 80, 64: 64, - 48: 48, - TINY: 32, + TINY: 48, }; const PAGE_WIDTH_CORRECTION = 100; -let opt; -let _overrides; -let _firstRun = true; - -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('appDisplayModule', true); - reset = reset || !moduleEnabled; +var IconGridModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - // don't even touch this module if disabled - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - _firstRun = false; + cleanGlobals() { + Me = null; + opt = null; + } - if (_overrides) - _overrides.removeAll(); + update(reset) { + this.moduleEnabled = opt.get('appDisplayModule'); + // if notifications are enabled no override is needed + reset = reset || !this.moduleEnabled; + + // don't touch original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + } + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - if (reset) { - _overrides = null; - opt = null; - return; + if (Me.shellVersion < 43 && IconGridCommon._findBestModeForSize) { + IconGridCommon['findBestModeForSize'] = IconGridCommon._findBestModeForSize; + delete IconGridCommon['_findBestModeForSize']; + } + this._overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon); + this._overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon); } - _overrides = new _Util.Overrides(); - - if (shellVersion < 43 && IconGridCommon._findBestModeForSize) { - IconGridCommon['findBestModeForSize'] = IconGridCommon._findBestModeForSize; - IconGridCommon['_findBestModeForSize'] = undefined; + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; } - _overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon); - _overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon); -} -// workaround - silence page -2 error on gnome 43 while cleaning app grid +}; const IconGridCommon = { getItemsAtPage(page) { @@ -87,7 +100,7 @@ const IconGridCommon = { return; const { pagePadding } = this.layout_manager; const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const iconPadding = 53 * scaleFactor; + const iconPadding = 51 * scaleFactor; // provided width is usually about 100px wider in horizontal orientation with prev/next page indicators const pageIndicatorCompensation = opt.ORIENTATION ? 0 : PAGE_WIDTH_CORRECTION; @@ -99,8 +112,13 @@ const IconGridCommon = { this.layoutManager._gridWidth = width; this.layoutManager._gridHeight = height; + width -= 80; // compensation for default padding + height -= 80; + const spacing = opt.APP_GRID_SPACING; - const iconSize = (opt.APP_GRID_ICON_SIZE > 0 ? opt.APP_GRID_ICON_SIZE : opt.APP_GRID_ICON_SIZE_DEFAULT) * scaleFactor; + // set the icon size as fixed to avoid changes in size later + const iconSize = opt.APP_GRID_ICON_SIZE > 0 ? opt.APP_GRID_ICON_SIZE : opt.APP_GRID_ICON_SIZE_DEFAULT; + const itemSize = iconSize * scaleFactor + iconPadding; // if this._gridModes.length === 1, custom grid should be used // if (iconSize > 0 && this._gridModes.length > 1) { let columns = opt.APP_GRID_COLUMNS; @@ -109,17 +127,20 @@ const IconGridCommon = { let unusedSpaceH = -1; let unusedSpaceV = -1; if (!columns) { - columns = Math.floor(width / (iconSize + iconPadding)) + 1; + // calculate #columns + 1 without spacing + columns = Math.floor(width / itemSize) + 1; + // check if columns with spacing fits the available width + // and reduce the number until it fits while (unusedSpaceH < 0) { columns -= 1; - unusedSpaceH = width - columns * (iconSize + iconPadding) - (columns - 1) * spacing; + unusedSpaceH = width - columns * itemSize - (columns - 1) * spacing; } } if (!rows) { - rows = Math.floor(height / (iconSize + iconPadding)) + 1; + rows = Math.floor(height / itemSize) + 1; while (unusedSpaceV < 0) { rows -= 1; - unusedSpaceV = height - rows * (iconSize + iconPadding) - (rows - 1) * spacing; + unusedSpaceV = height - rows * itemSize - ((rows - 1) * spacing); } } @@ -135,9 +156,16 @@ const IconGridLayoutCommon = { const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); const nColumns = this.columnsPerPage; const nRows = this.rowsPerPage; + + // if grid is not defined return default icon size + if (nColumns < 1 && nRows < 1) { + return this._isFolder + ? opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT : opt.APP_GRID_ICON_SIZE_DEFAULT; + } + const columnSpacingPerPage = opt.APP_GRID_SPACING * (nColumns - 1); const rowSpacingPerPage = opt.APP_GRID_SPACING * (nRows - 1); - const iconPadding = 53 * scaleFactor; + const iconPadding = 55 * scaleFactor; const paddingH = this._isFolder ? this.pagePadding.left + this.pagePadding.right : 0; const paddingV = this._isFolder ? this.pagePadding.top + this.pagePadding.bottom : 0; @@ -156,10 +184,14 @@ const IconGridLayoutCommon = { return opt.APP_GRID_ICON_SIZE_DEFAULT;*/ let iconSizes = Object.values(IconSize).sort((a, b) => b - a); - - // limit max icon size for folders, the whole range is for the main grid with active folders - if (this._isFolder) + // limit max icon size for folders and fully adaptive folder grids, the whole range is for the main grid with active folders + if (this._isFolder && opt.APP_GRID_FOLDER_ADAPTIVE && opt.APP_GRID_FOLDER_ICON_SIZE < 0) + iconSizes = iconSizes.slice(iconSizes.indexOf(opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT), -1); + else if (this._isFolder) iconSizes = iconSizes.slice(iconSizes.indexOf(IconSize.LARGE), -1); + else if (opt.APP_GRID_ADAPTIVE && opt.APP_GRID_ICON_SIZE < 0) + iconSizes = iconSizes.slice(iconSizes.indexOf(opt.APP_GRID_ICON_SIZE_DEFAULT), -1); + let sizeInvalid = false; for (const size of iconSizes) { @@ -167,10 +199,9 @@ const IconGridLayoutCommon = { if (firstItem) { firstItem.icon.setIconSize(size); - const [firstItemWidth, firstItemHeight] = - firstItem.get_preferred_size(); + const [firstItemWidth] = firstItem.get_preferred_size(); - const itemSize = Math.max(firstItemWidth, firstItemHeight); + const itemSize = firstItemWidth; if (itemSize < size) sizeInvalid = true; @@ -199,7 +230,7 @@ const IconGridLayoutCommon = { removeItem(item) { if (!this._items.has(item)) { - log(`Item ${item} is not part of the IconGridLayout`); + console.error(`Item ${item} is not part of the IconGridLayout`); return; // throw new Error(`Item ${item} is not part of the IconGridLayout`); } @@ -215,13 +246,13 @@ const IconGridLayoutCommon = { addItem(item, page = -1, index = -1) { if (this._items.has(item)) { - log(`iconGrid: Item ${item} already added to IconGridLayout`); + console.error(`iconGrid: Item ${item} already added to IconGridLayout`); return; // throw new Error(`Item ${item} already added to IconGridLayout`); } if (page > this._pages.length) { - log(`iconGrid: Cannot add ${item} to page ${page}`); + console.error(`iconGrid: Cannot add ${item} to page ${page}`); page = -1; index = -1; // throw new Error(`Cannot add ${item} to page ${page}`); @@ -240,7 +271,7 @@ const IconGridLayoutCommon = { moveItem(item, newPage, newPosition) { if (!this._items.has(item)) { - log(`iconGrid: Item ${item} is not part of the IconGridLayout`); + console.error(`iconGrid: Item ${item} is not part of the IconGridLayout`); return; // throw new Error(`Item ${item} is not part of the IconGridLayout`); } diff --git a/extensions/44/vertical-workspaces/lib/layout.js b/extensions/44/vertical-workspaces/lib/layout.js index f6562fd..6e72645 100644 --- a/extensions/44/vertical-workspaces/lib/layout.js +++ b/extensions/44/vertical-workspaces/lib/layout.js @@ -10,67 +10,107 @@ 'use strict'; -const { Meta, GLib, Shell, Clutter, GObject } = imports.gi; +const GLib = imports.gi.GLib; +const Meta = imports.gi.Meta; +const Gio = imports.gi.Gio; -const Main = imports.ui.main; const Layout = imports.ui.layout; -const Ripples = imports.ui.ripples; -const DND = imports.ui.dnd; +const Main = imports.ui.main; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; +let Me; +let opt; -let _overrides; let _timeouts; -let opt; -let _firstRun = true; -let _originalUpdateHotCorners; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('layoutModule', true); - const conflict = _Util.getEnabledExtensions('custom-hot-corners').length || - _Util.getEnabledExtensions('dash-to-panel').length; - reset = reset || !moduleEnabled; +var LayoutModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + _timeouts = {}; - // don't even touch this module if disabled or in conflict - if (_firstRun && (reset || conflict)) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + this._originalUpdateHotCorners = null; + } - _firstRun = false; + cleanGlobals() { + Me = null; + opt = null; + } + + update(reset) { + this._removeTimeouts(); + + this.moduleEnabled = opt.get('layoutModule'); + const conflict = Me.Util.getEnabledExtensions('custom-hot-corners').length || + Me.Util.getEnabledExtensions('dash-to-panel').length; - if (!_originalUpdateHotCorners) - _originalUpdateHotCorners = Layout.LayoutManager.prototype._updateHotCorners; + if (conflict && !reset) + console.warn(`[${Me.metadata.name}] Warning: "Layout" module disabled due to potential conflict with another extension`); - if (_overrides) - _overrides.removeAll(); + reset = reset || !this.moduleEnabled || conflict; - if (_timeouts) { - Object.values(_timeouts).forEach(t => { - if (t) - GLib.source_remove(t); - }); + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' LayoutModule - Keeping untouched'); } - if (reset) { - _overrides = null; - opt = null; - _timeouts = null; - Main.layoutManager._updateHotCorners = _originalUpdateHotCorners; + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + _timeouts = {}; + + this._overrides.addOverride('LayoutManager', Main.layoutManager, LayoutManagerCommon); + this._overrides.addOverride('HotCorner', Layout.HotCorner.prototype, HotCornerCommon); + + Main.layoutManager._updatePanelBarrier(); Main.layoutManager._updateHotCorners(); - return; + + if (!this._hotCornersEnabledConId) { + this._interfaceSettings = new Gio.Settings({ + schema_id: 'org.gnome.desktop.interface', + }); + this._hotCornersEnabledConId = this._interfaceSettings.connect('changed::enable-hot-corners', + () => Main.layoutManager._updateHotCorners()); + } + + console.debug(' LayoutModule - Activated'); } - _timeouts = {}; + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; - _overrides = new _Util.Overrides(); - _overrides.addOverride('LayoutManager', Layout.LayoutManager.prototype, LayoutManagerCommon); + Main.layoutManager._updateHotCorners(); - Main.layoutManager._updateHotCorners = LayoutManagerCommon._updateHotCorners.bind(Main.layoutManager); + if (this._hotCornersEnabledConId) { + this._interfaceSettings.disconnect(this._hotCornersEnabledConId); + this._hotCornersEnabledConId = 0; + this._interfaceSettings = null; + } - Main.layoutManager._updatePanelBarrier(); - Main.layoutManager._updateHotCorners(); -} + console.debug(' LayoutModule - Disabled'); + } + + _removeTimeouts() { + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + _timeouts = null; + } + } +}; const LayoutManagerCommon = { _updatePanelBarrier() { @@ -84,7 +124,7 @@ const LayoutManagerCommon = { this._leftPanelBarrier = null; } - if (!this.primaryMonitor || !opt) + if (!this.primaryMonitor || !opt || Me.Util.getEnabledExtensions('hidetopbar')) return; if (this.panelBox.height) { @@ -113,6 +153,7 @@ const LayoutManagerCommon = { // avoid errors if called from foreign override if (!opt) return; + // destroy old hot corners this.hotCorners.forEach(corner => corner?.destroy()); this.hotCorners = []; @@ -122,7 +163,7 @@ const LayoutManagerCommon = { return; } - let size = this.panelBox.height; + let size = this.panelBox.height ? this.panelBox.height : 27; // position 0 - default, 1-TL, 2-TR, 3-BL, 4-BR const position = opt.HOT_CORNER_POSITION; @@ -181,8 +222,8 @@ const LayoutManagerCommon = { } if (haveCorner) { - let corner = new HotCorner(this, monitor, cornerX, cornerY); - corner.setBarrierSize(size); + let corner = new Layout.HotCorner(this, monitor, cornerX, cornerY); + corner.setBarrierSize(size, false); this.hotCorners.push(corner); } else { this.hotCorners.push(null); @@ -193,11 +234,8 @@ const LayoutManagerCommon = { }, }; -var HotCorner = GObject.registerClass( -class HotCorner extends Layout.HotCorner { - _init(layoutManager, monitor, x, y) { - super._init(layoutManager, monitor, x, y); - +const HotCornerCommon = { + after__init() { let angle = 0; switch (opt.HOT_CORNER_POSITION) { case 2: @@ -214,9 +252,14 @@ class HotCorner extends Layout.HotCorner { this._ripples._ripple1.rotation_angle_z = angle; this._ripples._ripple2.rotation_angle_z = angle; this._ripples._ripple3.rotation_angle_z = angle; - } + }, + + setBarrierSize(size, notMyCall = true) { + // ignore calls from the original _updateHotCorners() callback to avoid building barriers outside screen + if (notMyCall && size > 0) { + return; + } - setBarrierSize(size) { if (this._verticalBarrier) { this._pressureBarrier.removeBarrier(this._verticalBarrier); this._verticalBarrier.destroy(); @@ -232,8 +275,8 @@ class HotCorner extends Layout.HotCorner { if (size > 0) { const primaryMonitor = global.display.get_primary_monitor(); const monitor = this._monitor; - const extendV = opt && opt.HOT_CORNER_EDGE && opt.DASH_VERTICAL && monitor.index === primaryMonitor; - const extendH = opt && opt.HOT_CORNER_EDGE && !opt.DASH_VERTICAL && monitor.index === primaryMonitor; + const extendV = opt && opt.HOT_CORNER_ACTION && opt.HOT_CORNER_EDGE && opt.DASH_VERTICAL && monitor.index === primaryMonitor; + const extendH = opt && opt.HOT_CORNER_ACTION && opt.HOT_CORNER_EDGE && !opt.DASH_VERTICAL && monitor.index === primaryMonitor; if (opt.HOT_CORNER_POSITION <= 1) { this._verticalBarrier = new Meta.Barrier({ @@ -284,25 +327,52 @@ class HotCorner extends Layout.HotCorner { this._pressureBarrier.addBarrier(this._verticalBarrier); this._pressureBarrier.addBarrier(this._horizontalBarrier); } - } + }, _toggleOverview() { if (!opt.HOT_CORNER_ACTION || (!opt.HOT_CORNER_FULLSCREEN && this._monitor.inFullscreen && !Main.overview.visible)) return; if (Main.overview.shouldToggleByCornerOrButton()) { - if ((opt.HOT_CORNER_ACTION === 1 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 2 && _Util.isCtrlPressed())) + if (Main.overview._shown) { this._toggleWindowPicker(true); - else if ((opt.HOT_CORNER_ACTION === 2 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 1 && _Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 3 && _Util.isCtrlPressed())) + } else if ((opt.HOT_CORNER_ACTION === 2 && !Me.Util.isCtrlPressed()) || ([3, 4, 5, 6].includes(opt.HOT_CORNER_ACTION) && Me.Util.isCtrlPressed())) { + // Default overview + opt.OVERVIEW_MODE = 0; + opt.OVERVIEW_MODE2 = false; + opt.WORKSPACE_MODE = 1; + this._toggleWindowPicker(true, true); + } else if (opt.HOT_CORNER_ACTION === 1) { + Main.overview.resetOverviewMode(); + this._toggleWindowPicker(true, true); + } else if ((opt.HOT_CORNER_ACTION === 3 && !Me.Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 2 && Me.Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 6 && Me.Util.isCtrlPressed())) { + // Applications this._toggleApplications(true); - else if (opt.HOT_CORNER_ACTION === 3 && !_Util.isCtrlPressed()) + } else if (opt.HOT_CORNER_ACTION === 4 && !Me.Util.isCtrlPressed()) { + // Overview - static ws preview + opt.OVERVIEW_MODE = 1; + opt.OVERVIEW_MODE2 = false; + opt.WORKSPACE_MODE = 0; + this._toggleWindowPicker(true, true); + } else if (opt.HOT_CORNER_ACTION === 5 && !Me.Util.isCtrlPressed()) { + // Overview - static ws + opt.OVERVIEW_MODE = 2; + opt.OVERVIEW_MODE2 = true; + opt.WORKSPACE_MODE = 0; + this._toggleWindowPicker(true, true); + } else if (opt.HOT_CORNER_ACTION === 6 && !Me.Util.isCtrlPressed()) { + // Window search provider + opt.OVERVIEW_MODE = 2; + opt.OVERVIEW_MODE2 = true; + opt.WORKSPACE_MODE = 0; this._toggleWindowSearchProvider(); + } if (opt.HOT_CORNER_RIPPLES && Main.overview.animationInProgress) this._ripples.playAnimation(this._x, this._y); } - } + }, - _toggleWindowPicker(leaveOverview = false) { + _toggleWindowPicker(leaveOverview = false, customOverviewMode = false) { if (Main.overview._shown && (leaveOverview || !Main.overview.dash.showAppsButton.checked)) { Main.overview.hide(); } else if (Main.overview.dash.showAppsButton.checked) { @@ -320,17 +390,17 @@ class HotCorner extends Layout.HotCorner { // delay cannot be too short 200, () => { - Main.overview.show(); + Main.overview.show(1, customOverviewMode); _timeouts.releaseKeyboardTimeoutId = 0; return GLib.SOURCE_REMOVE; } ); } else { - Main.overview.show(); + Main.overview.show(1, customOverviewMode); } } - } + }, _toggleApplications(leaveOverview = false) { if ((leaveOverview && Main.overview._shown) || Main.overview.dash.showAppsButton.checked) { @@ -360,12 +430,15 @@ class HotCorner extends Layout.HotCorner { Main.overview.show(2); // 2 for App Grid } } - } + }, _toggleWindowSearchProvider() { if (!Main.overview._overview._controls._searchController._searchActive) { - this._toggleWindowPicker(); - const prefix = 'wq// '; + opt.OVERVIEW_MODE = 2; + opt.OVERVIEW_MODE2 = true; + opt.WORKSPACE_MODE = 0; + this._toggleWindowPicker(false, true); + const prefix = Me.WSP_PREFIX; const position = prefix.length; const searchEntry = Main.overview.searchEntry; searchEntry.set_text(prefix); @@ -376,5 +449,5 @@ class HotCorner extends Layout.HotCorner { // Main.overview.searchEntry.text = ''; Main.overview.hide(); } - } -}); + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/messageTray.js b/extensions/44/vertical-workspaces/lib/messageTray.js index b35541a..ef7a51b 100644 --- a/extensions/44/vertical-workspaces/lib/messageTray.js +++ b/extensions/44/vertical-workspaces/lib/messageTray.js @@ -10,58 +10,82 @@ 'use strict'; -const { Clutter } = imports.gi; -const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Clutter = imports.gi.Clutter; + const Main = imports.ui.main; +let Me; let opt; -let _firstRun = true; - -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('messageTrayModule', true); - reset = reset || !moduleEnabled; - // don't even touch this module if disabled - if (_firstRun && reset) - return; +var MessageTrayModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - _firstRun = false; + this._firstActivation = true; + this.moduleEnabled = false; + } - if (reset) { + cleanGlobals() { + Me = null; opt = null; - setNotificationPosition(1); - return; } - setNotificationPosition(opt.NOTIFICATION_POSITION); -} + update(reset) { + this.moduleEnabled = opt.get('messageTrayModule'); + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' MessageTrayModule - Keeping untouched'); + } + + _activateModule() { + this._setNotificationPosition(opt.NOTIFICATION_POSITION); + + console.debug(' MessageTrayModule - Activated'); + } + + _disableModule() { + this._setNotificationPosition(1); + + console.debug(' MessageTrayModule - Disabled'); + } -function setNotificationPosition(position) { - switch (position) { - case 0: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; - break; - case 1: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; - break; - case 2: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; - break; - case 3: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; - break; - case 4: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; - break; - case 5: - Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; - Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; - break; + _setNotificationPosition(position) { + switch (position) { + case 0: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 1: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 2: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START; + break; + case 3: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + case 4: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + case 5: + Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END; + Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END; + break; + } } -} +}; diff --git a/extensions/44/vertical-workspaces/lib/optionsFactory.js b/extensions/44/vertical-workspaces/lib/optionsFactory.js index da62dd1..df6c970 100644 --- a/extensions/44/vertical-workspaces/lib/optionsFactory.js +++ b/extensions/44/vertical-workspaces/lib/optionsFactory.js @@ -9,41 +9,35 @@ 'use strict'; -const { Gtk, Gio, GObject } = imports.gi; +const Adw = imports.gi.Adw; +const Gtk = imports.gi.Gtk; +const Gio = imports.gi.Gio; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.lib.settings; - -const shellVersion = Settings.shellVersion; +let Me; // gettext -const _ = Settings._; +let _; // = Settings._; -const ProfileNames = [ - _('GNOME 3'), - _('GNOME 40+ - Bottom Hot Edge'), - _('Hot Corner Centric - Top Left Hot Corner'), - _('Dock Overview - Bottom Hot Edge'), -]; +function init(me) { + Me = me; + _ = Me.gettext; +} -// libadwaita is available starting with GNOME Shell 42. -let Adw = null; -try { - Adw = imports.gi.Adw; -} catch (e) {} +function cleanGlobals() { + Me = null; + _ = null; +} function _newImageFromIconName(name) { return Gtk.Image.new_from_icon_name(name); } var ItemFactory = class ItemFactory { - constructor(gOptions) { - this._gOptions = gOptions; - this._settings = this._gOptions._gsettings; + constructor() { + this._settings = Me.Opt._gsettings; } - getRowWidget(text, caption, widget, variable, options = []) { + getRowWidget(text, caption, widget, variable, options = [], dependsOn) { let item = []; let label; if (widget) { @@ -81,8 +75,8 @@ var ItemFactory = class ItemFactory { let key; - if (variable && this._gOptions.options[variable]) { - const opt = this._gOptions.options[variable]; + if (variable && Me.Opt.options[variable]) { + const opt = Me.Opt.options[variable]; key = opt[1]; } @@ -95,6 +89,11 @@ var ItemFactory = class ItemFactory { this._connectComboBox(widget, key, variable, options); else if (widget._isDropDown) this._connectDropDown(widget, key, variable, options); + + if (dependsOn) { + const dKey = Me.Opt.options[dependsOn][1]; + this._settings.bind(dKey, widget, 'sensitive', Gio.SettingsBindFlags.GET); + } } return item; @@ -111,7 +110,7 @@ var ItemFactory = class ItemFactory { _connectComboBox(widget, key, variable, options) { let model = widget.get_model(); widget._comboMap = {}; - const currentValue = this._gOptions.get(variable); + const currentValue = Me.Opt.get(variable); for (const [label, value] of options) { let iter; model.set(iter = model.append(), [0, 1], [label, value]); @@ -120,8 +119,8 @@ var ItemFactory = class ItemFactory { widget._comboMap[value] = iter; } - this._gOptions.connect(`changed::${key}`, () => { - widget.set_active_iter(widget._comboMap[this._gOptions.get(variable, true)]); + Me.Opt.connect(`changed::${key}`, () => { + widget.set_active_iter(widget._comboMap[Me.Opt.get(variable, true)]); }); widget.connect('changed', () => { const [success, iter] = widget.get_active_iter(); @@ -129,17 +128,17 @@ var ItemFactory = class ItemFactory { if (!success) return; - this._gOptions.set(variable, model.get_value(iter, 1)); + Me.Opt.set(variable, model.get_value(iter, 1)); }); } _connectDropDown(widget, key, variable, options) { const model = widget.get_model(); - const currentValue = this._gOptions.get(variable); + const currentValue = Me.Opt.get(variable); for (let i = 0; i < options.length; i++) { const text = options[i][0]; const id = options[i][1]; - model.append(new DropDownItem({ text, id })); + model.append(new DropDownItemVW({ text, id })); if (id === currentValue) widget.set_selected(i); } @@ -157,11 +156,11 @@ var ItemFactory = class ItemFactory { widget.connect('notify::selected-item', dropDown => { const item = dropDown.get_selected_item(); - this._gOptions.set(variable, item.id); + Me.Opt.set(variable, item.id); }); - this._gOptions.connect(`changed::${key}`, () => { - const newId = this._gOptions.get(variable, true); + Me.Opt.connect(`changed::${key}`, () => { + const newId = Me.Opt.get(variable, true); for (let i = 0; i < options.length; i++) { const id = options[i][1]; if (id === newId) @@ -214,7 +213,7 @@ var ItemFactory = class ItemFactory { newDropDown() { const dropDown = new Gtk.DropDown({ model: new Gio.ListStore({ - item_type: DropDownItem, + item_type: DropDownItemVW, }), halign: Gtk.Align.END, valign: Gtk.Align.CENTER, @@ -255,11 +254,11 @@ var ItemFactory = class ItemFactory { newLinkButton(uri) { const linkBtn = new Gtk.LinkButton({ - label: shellVersion < 42 ? 'Click Me!' : '', uri, halign: Gtk.Align.END, valign: Gtk.Align.CENTER, hexpand: true, + icon_name: 'emblem-symbolic-link', }); return linkBtn; } @@ -298,18 +297,23 @@ var ItemFactory = class ItemFactory { entry.set_text(opt.get(`profileName${profileIndex}`)); entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, 'edit-clear-symbolic'); entry.set_icon_activatable(Gtk.EntryIconPosition.SECONDARY, true); - entry.connect('icon-press', e => e.set_text('')); - entry.connect('changed', e => opt.set(`profileName${profileIndex}`, e.get_text())); const resetProfile = this.newButton(); resetProfile.set({ tooltip_text: _('Reset profile to defaults'), - icon_name: 'edit-delete-symbolic', + icon_name: 'document-revert-symbolic', hexpand: false, css_classes: ['destructive-action'], }); function setName() { + const ProfileNames = [ + _('GNOME 3'), + _('GNOME 40+ - Bottom Hot Edge'), + _('Hot Corner Centric - Top Left Hot Corner'), + _('Dock Overview - Bottom Hot Edge'), + ]; + let name = opt.get(`profileName${profileIndex}`, true); if (!name) name = ProfileNames[profileIndex - 1]; @@ -317,6 +321,10 @@ var ItemFactory = class ItemFactory { } setName(); + + entry.connect('icon-press', e => e.set_text('')); + entry.connect('changed', e => opt.set(`profileName${profileIndex}`, e.get_text())); + resetProfile.connect('clicked', () => { reset(profileIndex); setName(); @@ -366,7 +374,7 @@ var ItemFactory = class ItemFactory { valign: Gtk.Align.CENTER, hexpand: true, css_classes: ['destructive-action'], - icon_name: 'edit-delete-symbolic', + icon_name: 'document-revert-symbolic', }); btn.connect('clicked', () => { @@ -382,7 +390,7 @@ var ItemFactory = class ItemFactory { var AdwPrefs = class { constructor(gOptions) { - this._gOptions = gOptions; + Me.Opt = gOptions; } getFilledWindow(window, pages) { @@ -457,158 +465,9 @@ var AdwPrefs = class { } }; -var LegacyPrefs = class { - constructor(gOptions) { - this._gOptions = gOptions; - } - - getPrefsWidget(pages) { - const prefsWidget = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - }); - const stack = new Gtk.Stack({ - hexpand: true, - }); - const stackSwitcher = new Gtk.StackSwitcher({ - halign: Gtk.Align.CENTER, - hexpand: true, - }); - - const context = stackSwitcher.get_style_context(); - context.add_class('caption'); - - stackSwitcher.set_stack(stack); - stack.set_transition_duration(300); - stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT); - - const pageProperties = { - hscrollbar_policy: Gtk.PolicyType.NEVER, - vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, - vexpand: true, - hexpand: true, - visible: true, - }; - - const pagesBtns = []; - - for (let page of pages) { - const name = page.name; - const title = page.title; - const iconName = page.iconName; - const optionList = page.optionList; - - stack.add_named(this._getLegacyPage(optionList, pageProperties), name); - pagesBtns.push( - [new Gtk.Label({ label: title }), _newImageFromIconName(iconName, Gtk.IconSize.BUTTON)] - ); - } - - let stBtn = stackSwitcher.get_first_child ? stackSwitcher.get_first_child() : null; - for (let i = 0; i < pagesBtns.length; i++) { - const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 6, visible: true }); - const icon = pagesBtns[i][1]; - icon.margin_start = 30; - icon.margin_end = 30; - box.append(icon); - box.append(pagesBtns[i][0]); - if (stackSwitcher.get_children) { - stBtn = stackSwitcher.get_children()[i]; - stBtn.add(box); - } else { - stBtn.set_child(box); - stBtn.visible = true; - stBtn = stBtn.get_next_sibling(); - } - } - - if (stack.show_all) - stack.show_all(); - if (stackSwitcher.show_all) - stackSwitcher.show_all(); - - prefsWidget.append(stack); - - if (prefsWidget.show_all) - prefsWidget.show_all(); - - prefsWidget._stackSwitcher = stackSwitcher; - - return prefsWidget; - } - - _getLegacyPage(optionList, pageProperties) { - const page = new Gtk.ScrolledWindow(pageProperties); - const mainBox = new Gtk.Box({ - orientation: Gtk.Orientation.VERTICAL, - spacing: 5, - homogeneous: false, - margin_start: 30, - margin_end: 30, - margin_top: 12, - margin_bottom: 12, - }); - - let context = page.get_style_context(); - context.add_class('background'); - - let frame; - let frameBox; - for (let item of optionList) { - // label can be plain text for Section Title - // or GtkBox for Option - const option = item[0]; - const widget = item[1]; - - if (!widget) { - const lbl = new Gtk.Label({ - label: option, - xalign: 0, - margin_bottom: 4, - }); - - context = lbl.get_style_context(); - context.add_class('heading'); - - mainBox.append(lbl); - - frame = new Gtk.Frame({ - margin_bottom: 16, - }); - - frameBox = new Gtk.ListBox({ - selection_mode: null, - }); - - mainBox.append(frame); - frame.set_child(frameBox); - continue; - } - - const grid = new Gtk.Grid({ - column_homogeneous: false, - column_spacing: 20, - margin_start: 8, - margin_end: 8, - margin_top: 8, - margin_bottom: 8, - hexpand: true, - }); - - grid.attach(option, 0, 0, 5, 1); - - if (widget) - grid.attach(widget, 5, 0, 2, 1); - - frameBox.append(grid); - } - page.set_child(mainBox); - - return page; - } -}; - -const DropDownItem = GObject.registerClass({ - GTypeName: 'DropdownItem', +const { GObject } = imports.gi; +const DropDownItemVW = GObject.registerClass({ + GTypeName: 'DropDownItemVW', Properties: { 'text': GObject.ParamSpec.string( 'text', @@ -622,10 +481,11 @@ const DropDownItem = GObject.registerClass({ 'Id', 'Item id stored in settings', GObject.ParamFlags.READWRITE, - 0, 100, 0 + // min, max, default + -2147483648, 2147483647, 0 ), }, -}, class DropDownItem extends GObject.Object { +}, class DropDownItemVW extends GObject.Object { get text() { return this._text; } @@ -641,5 +501,4 @@ const DropDownItem = GObject.registerClass({ set id(id) { this._id = id; } -} -); +}); diff --git a/extensions/44/vertical-workspaces/lib/osdWindow.js b/extensions/44/vertical-workspaces/lib/osdWindow.js index a010558..4699ddf 100644 --- a/extensions/44/vertical-workspaces/lib/osdWindow.js +++ b/extensions/44/vertical-workspaces/lib/osdWindow.js @@ -10,79 +10,104 @@ 'use strict'; -const { Clutter } = imports.gi; +const Clutter = imports.gi.Clutter; + const Main = imports.ui.main; const OsdWindow = imports.ui.osdWindow; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; - -const OsdPositions = { - 1: { - x_align: Clutter.ActorAlign.START, - y_align: Clutter.ActorAlign.START, - }, - 2: { - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.START, - }, - 3: { - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.START, - }, - 4: { - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.CENTER, - }, - 5: { - x_align: Clutter.ActorAlign.START, - y_align: Clutter.ActorAlign.END, - }, - 6: { - x_align: Clutter.ActorAlign.CENTER, - y_align: Clutter.ActorAlign.END, - }, - 7: { - x_align: Clutter.ActorAlign.END, - y_align: Clutter.ActorAlign.END, - }, -}; - -let _overrides; +let Me; let opt; -let _firstRun = true; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('osdWindowModule', true); - reset = reset || !moduleEnabled; +let OsdPositions; - // don't even touch this module if disabled - if (_firstRun && reset) - return; +var OsdWindowModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - _firstRun = false; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; - if (_overrides) - _overrides.removeAll(); + OsdPositions = { + 1: { + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.START, + }, + 2: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.START, + }, + 3: { + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + }, + 4: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + }, + 5: { + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.END, + }, + 6: { + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + }, + 7: { + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.END, + }, + }; + } - if (reset || !moduleEnabled) { - updateExistingOsdWindows(6); - _overrides = null; + cleanGlobals() { + Me = null; opt = null; - return; + OsdPositions = null; } - _overrides = new _Util.Overrides(); - _overrides.addOverride('osdWindow', OsdWindow.OsdWindow.prototype, OsdWindowCommon); -} + update(reset) { + this.moduleEnabled = opt.get('osdWindowModule'); + const conflict = false; -function updateExistingOsdWindows(position) { - position = position ? position : opt.OSD_POSITION; - Main.osdWindowManager._osdWindows.forEach(osd => { - osd.set(OsdPositions[position]); - }); -} + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' OsdWindowModule - Keeping untouched'); + } + + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + this._overrides.addOverride('osdWindow', OsdWindow.OsdWindow.prototype, OsdWindowCommon); + console.debug(' OsdWindowModule - Activated'); + } + + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + this._updateExistingOsdWindows(6); + + console.debug(' WorkspaceSwitcherPopupModule - Disabled'); + } + + _updateExistingOsdWindows(position) { + position = position ? position : opt.OSD_POSITION; + Main.osdWindowManager._osdWindows.forEach(osd => { + osd.set(OsdPositions[position]); + }); + } +}; const OsdWindowCommon = { after_show() { diff --git a/extensions/44/vertical-workspaces/lib/overlayKey.js b/extensions/44/vertical-workspaces/lib/overlayKey.js index e0fc11d..815abaa 100644 --- a/extensions/44/vertical-workspaces/lib/overlayKey.js +++ b/extensions/44/vertical-workspaces/lib/overlayKey.js @@ -9,81 +9,94 @@ */ 'use strict'; -const { GObject, Gio, GLib, Meta, St } = imports.gi; + +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const St = imports.gi.St; const Main = imports.ui.main; const Overview = imports.ui.overview; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; - -const _ = Me.imports.lib.settings._; -const shellVersion = _Util.shellVersion; -const WIN_SEARCH_PREFIX = Me.imports.lib.windowSearchProvider.prefix; -const RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix; -const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard'; - +let Me; let opt; -let _firstRun = true; -let _originalOverlayKeyHandlerId; -let _overlayKeyHandlerId; +var OverlayKeyModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('overlayKeyModule', true); - reset = reset || (!_firstRun && !moduleEnabled); + this._firstActivation = true; + this.moduleEnabled = false; + this._originalOverlayKeyHandlerId = 0; + this._overlayKeyHandlerId = 0; + } - // don't even touch this module if disabled - if (_firstRun && !moduleEnabled) - return; + cleanGlobals() { + Me = null; + opt = null; + } - _firstRun = false; + update(reset) { + this.moduleEnabled = opt.get('overlayKeyModule'); + const conflict = false; - if (reset) { - _updateOverlayKey(reset); - opt = null; - return; + reset = reset || !this.moduleEnabled || conflict; + + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' OverlayKeyModule - Keeping untouched'); } - _updateOverlayKey(); -} - -function _updateOverlayKey(reset = false) { - if (reset) { - _restoreOverlayKeyHandler(); - } else if (!_originalOverlayKeyHandlerId) { - _originalOverlayKeyHandlerId = GObject.signal_handler_find(global.display, { signalId: 'overlay-key' }); - if (_originalOverlayKeyHandlerId !== null) - global.display.block_signal_handler(_originalOverlayKeyHandlerId); - _connectOverlayKey.bind(Main.overview._overview.controls)(); + _activateModule() { + if (!this._originalOverlayKeyHandlerId) { + this._originalOverlayKeyHandlerId = GObject.signal_handler_find(global.display, { signalId: 'overlay-key' }); + if (this._originalOverlayKeyHandlerId !== null) { + global.display.block_signal_handler(this._originalOverlayKeyHandlerId); + this._connectOverlayKey(); + } + } + console.debug(' OverlayKeyModule - Activated'); } -} -function _restoreOverlayKeyHandler() { - // Disconnect modified overlay key handler - if (_overlayKeyHandlerId !== null) { - global.display.disconnect(_overlayKeyHandlerId); - _overlayKeyHandlerId = null; + _disableModule() { + this._restoreOverlayKeyHandler(); + + console.debug(' OverlayKeyModule - Disabled'); } - // Unblock original overlay key handler - if (_originalOverlayKeyHandlerId !== null) { - global.display.unblock_signal_handler(_originalOverlayKeyHandlerId); - _originalOverlayKeyHandlerId = null; + _restoreOverlayKeyHandler() { + // Disconnect modified overlay key handler + if (this._overlayKeyHandlerId) { + global.display.disconnect(this._overlayKeyHandlerId); + this._overlayKeyHandlerId = 0; + } + + // Unblock original overlay key handler + if (this._originalOverlayKeyHandlerId) { + global.display.unblock_signal_handler(this._originalOverlayKeyHandlerId); + this._originalOverlayKeyHandlerId = 0; + } } -} -function _connectOverlayKey() { - this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA }); + _connectOverlayKey() { + if (this._overlayKeyHandlerId) + return; + + this._overlayKeyHandlerId = global.display.connect('overlay-key', this._onOverlayKeyPressed.bind(Main.overview._overview.controls)); + } - this._lastOverlayKeyTime = 0; - _overlayKeyHandlerId = global.display.connect('overlay-key', () => { + _onOverlayKeyPressed() { if (this._a11ySettings.get_boolean('stickykeys-enable')) return; const { initialState, finalState, transitioning } = - this._stateAdjustment.getStateTransitionParams(); + this._stateAdjustment.getStateTransitionParams(); const time = GLib.get_monotonic_time() / 1000; const timeDiff = time - this._lastOverlayKeyTime; @@ -95,14 +108,61 @@ function _connectOverlayKey() { const mode = opt.OVERLAY_KEY_SECONDARY; if (shouldShift) { - if (mode === 1) + Me.Util.activateSearchProvider(''); + if (mode === 1) { this._shiftState(Meta.MotionDirection.UP); - else if (mode === 2) - _Util.activateSearchProvider(WIN_SEARCH_PREFIX); - else if (mode === 3) - _Util.activateSearchProvider(RECENT_FILES_PREFIX); + } else if (mode === 2) { + Me.Util.activateSearchProvider(Me.WSP_PREFIX); + } else if (mode === 3) { + // Changing the overview mode automatically changes the overview transition + opt.OVERVIEW_MODE = 0; + opt.OVERVIEW_MODE2 = false; + opt.WORKSPACE_MODE = 1; + } } else { - Main.overview.toggle(); + if (Main.overview._shown) { + Main.overview.hide(); + return; + } + switch (opt.OVERLAY_KEY_PRIMARY) { + case 0: // Disabled + return; + case 1: // Follow global overview mode + Main.overview.resetOverviewMode(); + break; + case 2: // Default overview + opt.OVERVIEW_MODE = 0; + opt.OVERVIEW_MODE2 = false; + opt.WORKSPACE_MODE = 1; + break; + case 3: // Default overview + if (Main.overview._shown) + Main.overview.hide(); + else + Main.overview.show(2); + return; + case 4: // Static WS preview + opt.OVERVIEW_MODE = 1; + opt.OVERVIEW_MODE2 = false; + if (!Main.overview._shown) + opt.WORKSPACE_MODE = 0; + break; + case 5: // Static WS + opt.OVERVIEW_MODE = 2; + opt.OVERVIEW_MODE2 = true; + opt.WORKSPACE_MODE = 0; + break; + case 6: // Window Search + opt.OVERVIEW_MODE = 2; + opt.OVERVIEW_MODE2 = true; + if (!Main.overview._shown) + opt.WORKSPACE_MODE = 0; + break; + } + const customOverviewMode = !Main.overview._shown; + Main.overview.toggle(customOverviewMode); + if (opt.OVERLAY_KEY_PRIMARY === 6) + Me.Util.activateSearchProvider(Me.WSP_PREFIX); } - }); -} + } +}; diff --git a/extensions/44/vertical-workspaces/lib/overview.js b/extensions/44/vertical-workspaces/lib/overview.js index 2f23d05..833fc58 100644 --- a/extensions/44/vertical-workspaces/lib/overview.js +++ b/extensions/44/vertical-workspaces/lib/overview.js @@ -10,43 +10,113 @@ 'use strict'; +const Main = imports.ui.main; const Overview = imports.ui.overview; +const OverviewControls = imports.ui.overviewControls; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; - -let _overrides; +let Me; let opt; -function update(reset = false) { - if (_overrides) - _overrides.removeAll(); +var OverviewModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - if (reset) { - _overrides = null; + cleanGlobals() { + Me = null; opt = null; - return; } - opt = Me.imports.lib.settings.opt; - _overrides = new _Util.Overrides(); + update(reset) { + this.moduleEnabled = true; + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' OverviewModule - Keeping untouched'); + } + + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - _overrides.addOverride('Overview', Overview.Overview.prototype, OverviewCommon); -} + this._overrides.addOverride('Overview', Overview.Overview.prototype, OverviewCommon); + console.debug(' OverviewModule - Activated'); + } + + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + console.debug(' OverviewModule - Disabled'); + } +}; const OverviewCommon = { + show(state = OverviewControls.ControlsState.WINDOW_PICKER, customOverviewMode) { + if (!customOverviewMode) + this.resetOverviewMode(); + + if (state === OverviewControls.ControlsState.HIDDEN) + throw new Error('Invalid state, use hide() to hide'); + + if (this.isDummy) + return; + if (this._shown) + return; + this._shown = true; + + if (!this._syncGrab()) + return; + + Main.layoutManager.showOverview(); + this._animateVisible(state); + }, + + toggle(customOverviewMode) { + if (this.isDummy) + return; + + if (this._visible) + this.hide(); + else + this.show(OverviewControls.ControlsState.WINDOW_PICKER, customOverviewMode); + }, + + resetOverviewMode() { + // reset Overview Mode do default + opt.OVERVIEW_MODE = opt.get('overviewMode'); + opt.OVERVIEW_MODE2 = opt.OVERVIEW_MODE === 2; + opt.WORKSPACE_MODE = opt.OVERVIEW_MODE > 0 ? 0 : 1; + }, + _showDone() { this._animationInProgress = false; this._coverPane.hide(); - this.emit('shown'); + if (Me.shellVersion < 44) + this.emit('shown'); + else if (this._shownState !== 'SHOWN') + this._changeShownState('SHOWN'); + // Handle any calls to hide* while we were showing if (!this._shown) this._animateNotVisible(); - this._syncGrab(); - // if user activates overview during startup animation, transition needs to be shifted to the state 2 here const controls = this._overview._controls; if (controls._searchController._searchActive && controls._stateAdjustment.value === 1) { @@ -55,5 +125,41 @@ const OverviewCommon = { else if (!opt.OVERVIEW_MODE2) controls._stateAdjustment.value = 2; } + + this._syncGrab(); + }, + + // Workaround - should probably be fixed elsewhere in the upstream code + // If a new window is opened from the overview + // and is realized before the overview animation is complete, + // the new window will not get focus + after__hideDone() { + if (!opt.FIX_NEW_WINDOW_FOCUS) + return; + + const workspace = global.workspace_manager.get_active_workspace(); + const recentDesktopWin = global.display.get_tab_list(1, workspace)[0]; + let recentNormalWin = null; + const tabList = global.display.get_tab_list(0, workspace); + + for (let i = 0; i < tabList.length; i++) { + if (tabList[i].minimized === false) { + recentNormalWin = tabList[i]; + break; + } + } + + let recentWin = recentNormalWin; + if (recentNormalWin && recentDesktopWin) { + recentWin = recentNormalWin.get_user_time() > recentDesktopWin.get_user_time() + ? recentNormalWin + : recentDesktopWin; + } + + const focusedWin = global.display.focus_window; + + if (recentWin && focusedWin !== recentWin) + recentWin.activate(global.get_current_time()); }, }; + diff --git a/extensions/44/vertical-workspaces/lib/overviewControls.js b/extensions/44/vertical-workspaces/lib/overviewControls.js index 4959b83..7528682 100644 --- a/extensions/44/vertical-workspaces/lib/overviewControls.js +++ b/extensions/44/vertical-workspaces/lib/overviewControls.js @@ -10,90 +10,148 @@ 'use strict'; -const { Clutter, GLib, GObject, St } = imports.gi; +const Clutter = imports.gi.Clutter; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const Background = imports.ui.background; +const Layout = imports.ui.layout; const Main = imports.ui.main; -const Util = imports.misc.util; +const Overview = imports.ui.overview; const OverviewControls = imports.ui.overviewControls; +const Workspace = imports.ui.workspace; const WorkspaceThumbnail = imports.ui.workspaceThumbnail; +const WorkspacesView = imports.ui.workspacesView; +const Util = imports.misc.util; -const ControlsState = imports.ui.overviewControls.ControlsState; -const FitMode = imports.ui.workspacesView.FitMode; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); - -const _Util = Me.imports.lib.util; - -let _overrides; +let Me; let opt; +// gettext +let _; + +const ControlsState = OverviewControls.ControlsState; +const FitMode = WorkspacesView.FitMode; -const ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; +const ANIMATION_TIME = Overview.ANIMATION_TIME; const DASH_MAX_SIZE_RATIO = 0.25; let _originalSearchControllerSigId; let _searchControllerSigId; let _timeouts; -let _startupInitComplete = false; -function update(reset = false) { - if (_overrides) - _overrides.removeAll(); +var OverviewControlsModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; - if (_timeouts) { - Object.values(_timeouts).forEach(id => { - if (id) - GLib.source_remove(id); - }); + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; } - _replaceOnSearchChanged(reset); - - if (reset) { - _overrides = null; + cleanGlobals() { + Me = null; opt = null; - _timeouts = null; - return; + _ = null; } - _timeouts = {}; + update(reset) { + this._removeTimeouts(); + this.moduleEnabled = true; + const conflict = false; - opt = Me.imports.lib.settings.opt; - _overrides = new _Util.Overrides(); + reset = reset || !this.moduleEnabled || conflict; - _overrides.addOverride('ControlsManager', OverviewControls.ControlsManager.prototype, ControlsManager); + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' OverviewControlsModule - Keeping untouched'); + } - if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) - _overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutVertical); - else - _overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutHorizontal); -} + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + _timeouts = {}; + + this._replaceOnSearchChanged(); + + this._overrides.addOverride('ControlsManager', OverviewControls.ControlsManager.prototype, ControlsManagerCommon); + + if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) + this._overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutVertical); + else + this._overrides.addOverride('ControlsManagerLayout', OverviewControls.ControlsManagerLayout.prototype, ControlsManagerLayoutHorizontal); + + // if DtD is enabled, we need to replace _prepareStartupAnimation() to minimize the mess in the overview + // const dashToDockEnabled = Me.Util.getEnabledExtensions('dash-to-dock').length || + // Me.Util.getEnabledExtensions('ubuntu-dock').length; + // if (dashToDockEnabled) + this._overrides.addOverride('LayoutManagerDtD', Layout.LayoutManager.prototype, LayoutManager); + + console.debug(' OverviewControlsModule - Activated'); + } + + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + const reset = true; + this._replaceOnSearchChanged(reset); + Main.overview._overview._controls._appDisplay.opacity = 255; -function _replaceOnSearchChanged(reset = false) { - const searchController = Main.overview._overview.controls._searchController; - if (reset) { - if (_searchControllerSigId) { - searchController.disconnect(_searchControllerSigId); - _searchControllerSigId = 0; + console.debug(' OverviewControlsModule - Disabled'); + } + + _removeTimeouts() { + if (_timeouts) { + Object.values(_timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + _timeouts = null; } - if (_originalSearchControllerSigId) { - searchController.unblock_signal_handler(_originalSearchControllerSigId); - _originalSearchControllerSigId = 0; + } + + _replaceOnSearchChanged(reset) { + const searchController = Main.overview._overview.controls._searchController; + if (reset) { + if (_searchControllerSigId) { + searchController.disconnect(_searchControllerSigId); + _searchControllerSigId = 0; + } + if (_originalSearchControllerSigId) { + searchController.unblock_signal_handler(_originalSearchControllerSigId); + _originalSearchControllerSigId = 0; + } + Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_x = 0; + Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_y = 0; + Main.overview.searchEntry.visible = true; + Main.overview.searchEntry.opacity = 255; + } else { + // reconnect signal to use custom function (callbacks cannot be overridden in class prototype, they are already in memory as a copy for the given callback) + if (!_originalSearchControllerSigId) + _originalSearchControllerSigId = GObject.signal_handler_find(searchController, { signalId: 'notify', detail: 'search-active' }); + if (_originalSearchControllerSigId) + searchController.block_signal_handler(_originalSearchControllerSigId); + + if (!_searchControllerSigId) + _searchControllerSigId = searchController.connect('notify::search-active', ControlsManagerCommon._onSearchChanged.bind(Main.overview._overview.controls)); } - Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_x = 0; - Main.overview._overview._controls.layoutManager._searchController._searchResults.translation_y = 0; - Main.overview.searchEntry.visible = true; - Main.overview.searchEntry.opacity = 255; - } else { - // reconnect signal to use custom function (callbacks cannot be overridden in class prototype, they are already in memory as a copy for the given callback) - _originalSearchControllerSigId = GObject.signal_handler_find(searchController, { signalId: 'notify', detail: 'search-active' }); - if (_originalSearchControllerSigId) - searchController.block_signal_handler(_originalSearchControllerSigId); - - _searchControllerSigId = searchController.connect('notify::search-active', ControlsManager._onSearchChanged.bind(Main.overview._overview.controls)); } -} +}; -const ControlsManager = { +const ControlsManagerCommon = { // this function is used as a callback by a signal handler, needs to be reconnected after modification as the original callback uses a copy of the original function /* _update: function() { ... @@ -105,16 +163,21 @@ const ControlsManager = { }, _updateThumbnailsBox() { + const { currentState } = this._stateAdjustment.getStateTransitionParams(); const { shouldShow } = this._thumbnailsBox; - const thumbnailsBoxVisible = shouldShow; + const thumbnailsBoxVisible = shouldShow && + ((currentState < ControlsState.APP_GRID && opt.SHOW_WS_TMB) || + (currentState > ControlsState.WINDOW_PICKER && opt.SHOW_WS_TMB_APPGRID) || + (currentState > ControlsState.WINDOW_PICKER && this._searchController.searchActive && opt.SHOW_WS_TMB) + ); this._thumbnailsBox.visible = thumbnailsBoxVisible; // this call should be directly in _update(), but it's used as a callback function and it would require to reconnect the signal - this._updateWorkspacesDisplay(); + this._updateOverview(); }, // this function is pure addition to the original code and handles wsDisp transition to APP_GRID view - _updateWorkspacesDisplay() { + _updateOverview() { this._workspacesDisplay.translation_x = 0; this._workspacesDisplay.translation_y = 0; this._workspacesDisplay.scale_x = 1; @@ -167,18 +230,21 @@ const ControlsManager = { ws._background.opacity = opacity; } + if (opt.WORKSPACE_MODE) + Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; + // if ws preview background is disabled, animate tmb box and dash const tmbBox = this._thumbnailsBox; const dash = this.dash; const searchEntryBin = this._searchEntryBin; // this dash transition collides with startup animation and freezes GS for good, needs to be delayed (first Main.overview 'hiding' event enables it) - const skipDash = _Util.dashNotDefault(); + const skipDash = Me.Util.dashNotDefault(); // OVERVIEW_MODE 2 should animate dash and wsTmbBox only if WORKSPACE_MODE === 0 (windows not spread) const animateOverviewMode2 = opt.OVERVIEW_MODE2 && !(finalState === 1 && opt.WORKSPACE_MODE); if (!Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2)) { if (!tmbBox._translationOriginal || Math.abs(tmbBox._translationOriginal[0]) > 500) { // swipe gesture can call this calculation before tmbBox is finalized, giving nonsense width - const [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY] = _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = this._getOverviewTranslations(dash, tmbBox, searchEntryBin); tmbBox._translationOriginal = [tmbTranslationX, tmbTranslationY]; dash._translationOriginal = [dashTranslationX, dashTranslationY]; searchEntryBin._translationOriginal = searchTranslationY; @@ -229,7 +295,7 @@ const ControlsManager = { // set searchEntry above appDisplay this.set_child_above_sibling(this._searchEntryBin, null); // move dash above wsTmb for case that dash and wsTmb animate from the same side - if (!_Util.dashNotDefault()) + if (!Me.Util.dashNotDefault()) this.set_child_above_sibling(dash, null); this.set_child_below_sibling(this._thumbnailsBox, null); this.set_child_below_sibling(this._workspacesDisplay, null); @@ -238,7 +304,7 @@ const ControlsManager = { // set dash above workspace in the overview this.set_child_above_sibling(this._thumbnailsBox, null); this.set_child_above_sibling(this._searchEntryBin, null); - if (!_Util.dashNotDefault()) + if (!Me.Util.dashNotDefault()) this.set_child_above_sibling(this.dash, null); this.dash._isAbove = true; @@ -247,6 +313,7 @@ const ControlsManager = { this.set_child_above_sibling(this._workspacesDisplay, null); this.dash._isAbove = false; } + }, // fix for upstream bug - appGrid.visible after transition from APP_GRID to HIDDEN @@ -258,12 +325,6 @@ const ControlsManager = { if (this.dash.showAppsButton.checked) this._searchTransition = false; - // update App Grid after settings changed - // only if the App Grid is currently visible on the screen, the paging updates correctly - if (currentState === ControlsState.APP_GRID && this._appDisplay.visible && opt._appGridNeedsRedisplay) { - Me.imports.lib.appDisplay._updateAppGridProperties(); - opt._appGridNeedsRedisplay = false; - } // if !APP_GRID_ANIMATION, appGrid needs to be hidden in WINDOW_PICKER mode (1) // but needs to be visible for transition from HIDDEN (0) to APP_GRID (2) this._appDisplay.visible = @@ -305,11 +366,19 @@ const ControlsManager = { this._workspacesDisplay.reactive = true; this._workspacesDisplay.setPrimaryWorkspaceVisible(true); } else { + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) + this._searchController._searchResults._statusText.add_style_class_name('search-statustext-om2'); + else + this._searchController._searchResults._statusText.remove_style_class_name('search-statustext-om2'); this._searchController.show(); entry.visible = true; entry.opacity = 255; + // avoid awkward ws scale animation during search activation + WorkspaceThumbnail.RESCALE_ANIMATION_TIME = 0; } + if (opt.SHOW_BG_IN_OVERVIEW && this._bgManagers) + this._updateBackground(this._bgManagers[0]); this._searchTransition = true; this._searchController._searchResults.translation_x = 0; @@ -317,34 +386,33 @@ const ControlsManager = { this._searchController.opacity = 255; this._searchController.visible = true; - if (opt.SEARCH_VIEW_ANIMATION && !this.dash.showAppsButton.checked && ![4, 8].includes(opt.WS_TMB_POSITION) /* && !opt.OVERVIEW_MODE2*/) { + if (opt.SEARCH_VIEW_ANIMATION && ![4, 8].includes(opt.WS_TMB_POSITION) /* && !opt.OVERVIEW_MODE2*/) { this._updateAppDisplayVisibility(); + this.layoutManager._searchController._searchResults._statusBin.opacity = 1; this._searchController.opacity = searchActive ? 255 : 0; let translationX = 0; let translationY = 0; const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); - if (currentState < ControlsState.APP_GRID) { - switch (opt.SEARCH_VIEW_ANIMATION) { - case 1: - // make it longer to cover the delay before results appears - translationX = geometry.width; - translationY = 0; - break; - case 2: - translationX = -geometry.width; - translationY = 0; - break; - case 3: - translationX = 0; - translationY = geometry.height; - break; - case 5: - translationX = 0; - translationY = -geometry.height; - break; - } + switch (opt.SEARCH_VIEW_ANIMATION) { + case 1: + // make it longer to cover the delay before results appears + translationX = geometry.width; + translationY = 0; + break; + case 2: + translationX = -geometry.width; + translationY = 0; + break; + case 3: + translationX = 0; + translationY = geometry.height; + break; + case 5: + translationX = 0; + translationY = -geometry.height; + break; } if (searchActive) { @@ -363,6 +431,8 @@ const ControlsManager = { onComplete: () => { this._searchController.visible = searchActive; this._searchTransition = false; + this.layoutManager._searchController._searchResults._statusBin.opacity = 255; + WorkspaceThumbnail.RESCALE_ANIMATION_TIME = 200; }, }); @@ -372,7 +442,10 @@ const ControlsManager = { opacity: searchActive || currentState < 2 ? 0 : 255, duration: SIDE_CONTROLS_ANIMATION_TIME / 2, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => this._updateAppDisplayVisibility(), + onComplete: () => { + this._updateAppDisplayVisibility(); + WorkspaceThumbnail.RESCALE_ANIMATION_TIME = 200; + }, }); // this._updateAppDisplayVisibility(); @@ -390,7 +463,7 @@ const ControlsManager = { this._searchController.opacity = searchActive ? 0 : 255; this._searchController.ease({ opacity: searchActive ? 255 : 0, - duration: searchActive ? SIDE_CONTROLS_ANIMATION_TIME * 2 : 0, + duration: searchActive ? SIDE_CONTROLS_ANIMATION_TIME : 0, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => (this._searchController.visible = searchActive), }); @@ -398,14 +471,16 @@ const ControlsManager = { // reuse already tuned overview transition, just replace APP_GRID with the search view if (!(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) && !Main.overview._animationInProgress && finalState !== ControlsState.HIDDEN && !this.dash.showAppsButton.checked) { - Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-om2'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-bg-om2'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.add_style_class_name('search-section-content-bg'); Main.overview.searchEntry.remove_style_class_name('search-entry-om2'); + const duration = opt.SEARCH_VIEW_ANIMATION ? 150 : 0; this._stateAdjustment.ease(searchActive ? ControlsState.APP_GRID : ControlsState.WINDOW_PICKER, { // shorter animation time when entering search view can avoid stuttering in transition // collecting search results take some time and the problematic part is the realization of the object on the screen // if the ws animation ends before this event, the whole transition is smoother // removing the ws transition (duration: 0) seems like the best solution here - duration: searchActive || (opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE) ? 80 : SIDE_CONTROLS_ANIMATION_TIME, + duration: searchActive ? duration : SIDE_CONTROLS_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, onComplete: () => { this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive); @@ -413,18 +488,20 @@ const ControlsManager = { }); } else if (opt.OVERVIEW_MODE2 && !(opt.WORKSPACE_MODE || this.dash.showAppsButton.checked)) { // add background to search results and make searchEntry border thicker for better visibility - Main.overview._overview._controls.layoutManager._searchController._searchResults._content.add_style_class_name('search-section-content-om2'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-bg'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.add_style_class_name('search-section-content-bg-om2'); Main.overview.searchEntry.add_style_class_name('search-entry-om2'); } else { - Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-om2'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.add_style_class_name('search-section-content-bg'); + Main.overview._overview._controls.layoutManager._searchController._searchResults._content.remove_style_class_name('search-section-content-bg-om2'); Main.overview.searchEntry.remove_style_class_name('search-entry-om2'); } }, async runStartupAnimation(callback) { this._ignoreShowAppsButtonToggle = true; - this._searchController.prepareToEnterOverview(); - this._workspacesDisplay.prepareToEnterOverview(); + + this.prepareToEnterOverview(); this._stateAdjustment.value = ControlsState.HIDDEN; this._stateAdjustment.ease(ControlsState.WINDOW_PICKER, { @@ -436,16 +513,20 @@ const ControlsManager = { this._ignoreShowAppsButtonToggle = false; // Set the opacity here to avoid a 1-frame flicker - this.opacity = 0; + this.opacity = 1; + this._appDisplay.opacity = 1; // We can't run the animation before the first allocation happens await this.layout_manager.ensureAllocation(); - const { STARTUP_ANIMATION_TIME } = imports.ui.layout; + this._setBackground(); + Main.panel.opacity = 255; + + const { STARTUP_ANIMATION_TIME } = Layout; // Opacity this.ease({ - opacity: 255, + opacity: opt.STARTUP_STATE === 1 ? 0 : 255, duration: STARTUP_ANIMATION_TIME, mode: Clutter.AnimationMode.LINEAR, onComplete: () => { @@ -465,53 +546,24 @@ const ControlsManager = { } const searchEntryBin = this._searchEntryBin; - const [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY] = - _Util.getOverviewTranslations(opt, dash, tmbBox, searchEntryBin); + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = + this._getOverviewTranslations(dash, tmbBox, searchEntryBin); const onComplete = function () { // running init callback again causes issues (multiple connections) - if (!_startupInitComplete) + if (!Main.overview._startupInitComplete) callback(); - _startupInitComplete = true; - - // force app grid to build before the first visible animation to remove possible stuttering - this._appDisplay.opacity = 1; - - const [x, y] = this._appDisplay.get_position(); - const translationX = -x; - const translationY = -y; - this._appDisplay.translation_x = translationX; - this._appDisplay.translation_y = translationY; - GLib.idle_add(0, () => { - this._appDisplay._removeItem(this._appDisplay._orderedItems[0]); - this._appDisplay._redisplay(); - }); - // let the main loop realize previous changes before continuing - _timeouts.startupAnim1 = GLib.timeout_add( - GLib.PRIORITY_DEFAULT, - 10, - () => { - GLib.idle_add(0, () => { - this._appDisplay._removeItem(this._appDisplay._orderedItems[0]); - this._appDisplay._redisplay(); - }); - this._appDisplay.translation_x = 0; - this._appDisplay.translation_y = 0; - this._appDisplay.visible = false; - if (opt.STARTUP_STATE === 1) { - Main.overview.hide(); - } else if (opt.STARTUP_STATE === 2) { - this._appDisplay.opacity = 255; - this.dash.showAppsButton.checked = true; - } - _timeouts.startupAnim1 = 0; - return GLib.SOURCE_REMOVE; - } - ); + const appDisplayModule = Me.Modules.appDisplayModule; + if (!appDisplayModule.moduleEnabled) + this._finishStartupSequence(); + else + this._realizeAppDisplayAndFinishSequence(); + + Main.overview._startupInitComplete = true; }.bind(this); - if (dash.visible && !_Util.dashNotDefault()) { + if (dash.visible && !Me.Util.dashNotDefault()) { dash.translation_x = dashTranslationX; dash.translation_y = dashTranslationY; dash.opacity = 255; @@ -521,9 +573,7 @@ const ControlsManager = { delay: STARTUP_ANIMATION_TIME / 2, duration: STARTUP_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => { - onComplete(); - }, + onComplete, }); } else { // set dash opacity to make it visible if user enable it later @@ -535,6 +585,7 @@ const ControlsManager = { STARTUP_ANIMATION_TIME * 2 * St.Settings.get().slow_down_factor, () => { onComplete(); + Main.overview._startupInitComplete = true; _timeouts.startupAnim2 = 0; return GLib.SOURCE_REMOVE; } @@ -571,7 +622,6 @@ const ControlsManager = { if (view._monitorIndex !== global.display.get_primary_monitor() && view._thumbnails.visible) { const secTmbBox = view._thumbnails; - _Util.getOverviewTranslations(opt, dash, secTmbBox, searchEntryBin); if (opt.SEC_WS_TMB_LEFT) secTmbBox.translation_x = -(secTmbBox.width + 12); // compensate for padding else if (opt.SEC_WS_TMB_RIGHT) @@ -594,13 +644,129 @@ const ControlsManager = { } }, + _realizeAppDisplayAndFinishSequence() { + const appDisplayModule = Me.Modules.appDisplayModule; + // realize app grid for smoother first animation + appDisplayModule._updateAppGrid(false, this._finishStartupSequence.bind(this)); + }, + + _finishStartupSequence(priority = GLib.PRIORITY_LOW) { + if (!this._bgManagers) + this._setBackground(); + + _timeouts.finishStartup = GLib.idle_add( + priority, () => { + this._appDisplay.opacity = 255; + if (opt.STARTUP_STATE === 1) { + Main.overview.hide(); + } else if (opt.STARTUP_STATE === 2) { + Main.overview.show(2); // just because of DtD, because we skipped startup animation + this.dash.showAppsButton.checked = true; + } else if (!opt.STARTUP_STATE && Me.Util.dashNotDefault()) { + Main.overview.show(); + } + + _timeouts.finishStartup = 0; + return GLib.SOURCE_REMOVE; + } + ); + }, + + setInitialTranslations() { + const dash = this.dash; + const tmbBox = this._thumbnailsBox; + const searchEntryBin = this._searchEntryBin; + const [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY] = + this._getOverviewTranslations(dash, tmbBox, searchEntryBin); + if (!Me.Util.dashNotDefault()) { + dash.translation_x = dashTranslationX; + dash.translation_y = dashTranslationY; + } + tmbBox.translation_x = tmbTranslationX; + tmbBox.translation_y = tmbTranslationY; + searchEntryBin.translation_y = searchTranslationY; + }, + + _getOverviewTranslations(dash, tmbBox, searchEntryBin) { + // const tmbBox = Main.overview._overview._controls._thumbnailsBox; + const animationsDisabled = !St.Settings.get().enable_animations || (opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2); + if (animationsDisabled) + return [0, 0, 0, 0, 0]; + + let searchTranslationY = 0; + if (searchEntryBin.visible) { + const offset = (dash.visible && (!opt.DASH_VERTICAL ? dash.height + 12 : 0)) + + (opt.WS_TMB_TOP ? tmbBox.height + 12 : 0); + searchTranslationY = -searchEntryBin.height - offset - 30; + } + + let tmbTranslationX = 0; + let tmbTranslationY = 0; + let offset; + if (tmbBox.visible) { + const tmbWidth = tmbBox.width === Infinity ? 0 : tmbBox.width; + const tmbHeight = tmbBox.height === Infinity ? 0 : tmbBox.height; + switch (opt.WS_TMB_POSITION) { + case 3: // left + offset = 10 + (dash?.visible && opt.DASH_LEFT ? dash.width : 0); + tmbTranslationX = -tmbWidth - offset; + tmbTranslationY = 0; + break; + case 1: // right + offset = 10 + (dash?.visible && opt.DASH_RIGHT ? dash.width : 0); + tmbTranslationX = tmbWidth + offset; + tmbTranslationY = 0; + break; + case 0: // top + offset = 10 + (dash?.visible && opt.DASH_TOP ? dash.height : 0) + Main.panel.height; + tmbTranslationX = 0; + tmbTranslationY = -tmbHeight - offset; + break; + case 2: // bottom + offset = 10 + (dash?.visible && opt.DASH_BOTTOM ? dash.height : 0) + Main.panel.height; // just for case the panel is at bottom + tmbTranslationX = 0; + tmbTranslationY = tmbHeight + offset; + break; + } + } + + let dashTranslationX = 0; + let dashTranslationY = 0; + let position = opt.DASH_POSITION; + // if DtD replaced the original Dash, read its position + if (Me.Util.dashIsDashToDock()) + position = dash._position; + + if (dash?.visible) { + const dashWidth = dash.width === Infinity ? 0 : dash.width; + const dashHeight = dash.height === Infinity ? 0 : dash.height; + switch (position) { + case 0: // top + dashTranslationX = 0; + dashTranslationY = -dashHeight - dash.margin_bottom - Main.panel.height; + break; + case 1: // right + dashTranslationX = dashWidth; + dashTranslationY = 0; + break; + case 2: // bottom + dashTranslationX = 0; + dashTranslationY = dashHeight + dash.margin_bottom + Main.panel.height; + break; + case 3: // left + dashTranslationX = -dashWidth; + dashTranslationY = 0; + break; + } + } + + return [dashTranslationX, dashTranslationY, tmbTranslationX, tmbTranslationY, searchTranslationY]; + }, + animateToOverview(state, callback) { this._ignoreShowAppsButtonToggle = true; this._searchTransition = false; - this._searchController.prepareToEnterOverview(); - this._workspacesDisplay.prepareToEnterOverview(); - this._stateAdjustment.value = ControlsState.HIDDEN; // building window thumbnails takes some time and with many windows on the workspace @@ -627,10 +793,151 @@ const ControlsManager = { this._ignoreShowAppsButtonToggle = false; }, + + _setBackground(reset = false) { + if (this._bgManagers) { + this._bgManagers.forEach(bg => { + Main.overview._overview._controls._stateAdjustment.disconnect(bg._fadeSignal); + bg.destroy(); + }); + } + + // if (!SHOW_BG_IN_OVERVIEW && !SHOW_WS_PREVIEW_BG) the background is used for static transition from wallpaper to empty bg in the overview + if (reset || (!opt.SHOW_BG_IN_OVERVIEW && opt.SHOW_WS_PREVIEW_BG)) { + delete this._bgManagers; + return; + } + + this._bgManagers = []; + for (const monitor of Main.layoutManager.monitors) { + const bgManager = new Background.BackgroundManager({ + monitorIndex: monitor.index, + container: Main.layoutManager.overviewGroup, + vignette: true, + }); + + bgManager.backgroundActor.content.vignette_sharpness = 0; + bgManager.backgroundActor.content.brightness = 1; + + + bgManager._fadeSignal = Main.overview._overview._controls._stateAdjustment.connect('notify::value', v => { + this._updateBackground(bgManager, v.value, v); + }); + + if (monitor.index === global.display.get_primary_monitor()) { + bgManager._primary = true; + this._bgManagers.unshift(bgManager); // primary monitor first + } else { + bgManager._primary = false; + this._bgManagers.push(bgManager); + } + } + }, + + _updateBackground(bgManager, stateValue = 2, stateAdjustment = null) { + // Blur My Shell extension destroys all background actors in the overview and doesn't care about consequences + if (this._bgManagers[0] && !Main.layoutManager.overviewGroup.get_children().includes(this._bgManagers[0].backgroundActor)) { + Main.notifyError(`[${Me.metadata.name}]`, _('Overview background crashed!\nIf you are using Blur My Shell, disable overview blur in its settings and re-enable V-Shell Overview Background to avoid visual glitches.')); + // remove and disconnect our destroyed backgrounds to avoid more errors + this._setBackground(true); + return; + } + + const finalState = stateAdjustment?.getStateTransitionParams().finalState; + if (!opt.SHOW_BG_IN_OVERVIEW && !opt.SHOW_WS_PREVIEW_BG) { + // if no bg shown in the overview, fade out the wallpaper + if (!(opt.OVERVIEW_MODE2 && opt.WORKSPACE_MODE && finalState === 1)) + bgManager.backgroundActor.opacity = Util.lerp(255, 0, Math.min(stateValue, 1)); + } else { + let VIGNETTE, BRIGHTNESS, bgValue; + if (opt.OVERVIEW_MODE2 && stateValue <= 1 && !opt.WORKSPACE_MODE) { + VIGNETTE = 0; + BRIGHTNESS = 1; + bgValue = stateValue; + } else { + VIGNETTE = 0.2; + BRIGHTNESS = opt.OVERVIEW_BG_BRIGHTNESS; + if (opt.OVERVIEW_MODE2 && stateValue > 1 && !opt.WORKSPACE_MODE) + bgValue = stateValue - 1; + else + bgValue = stateValue; + } + + let blurEffect = bgManager.backgroundActor.get_effect('blur'); + if (!blurEffect) { + blurEffect = new Shell.BlurEffect({ + brightness: 1, + sigma: 0, + mode: Shell.BlurMode.ACTOR, + }); + bgManager.backgroundActor.add_effect_with_name('blur', blurEffect); + } + + const searchActive = Main.overview._overview.controls._searchController.searchActive; + if (searchActive) + BRIGHTNESS = opt.SEARCH_BG_BRIGHTNESS; + + bgManager.backgroundActor.content.vignette_sharpness = VIGNETTE; + bgManager.backgroundActor.content.brightness = BRIGHTNESS; + + let vignetteInit, brightnessInit;// , sigmaInit; + if (opt.SHOW_BG_IN_OVERVIEW && opt.SHOW_WS_PREVIEW_BG) { + vignetteInit = VIGNETTE; + brightnessInit = BRIGHTNESS; + // sigmaInit = opt.OVERVIEW_BG_BLUR_SIGMA; + } else { + vignetteInit = 0; + brightnessInit = 1; + // sigmaInit = 0; + } + + if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { + bgManager.backgroundActor.content.vignette_sharpness = Util.lerp(vignetteInit, VIGNETTE, bgValue); + bgManager.backgroundActor.content.brightness = Util.lerp(brightnessInit, BRIGHTNESS, bgValue); + } else { + bgManager.backgroundActor.content.vignette_sharpness = Util.lerp(vignetteInit, VIGNETTE, Math.min(stateValue, 1)); + bgManager.backgroundActor.content.brightness = Util.lerp(brightnessInit, BRIGHTNESS, Math.min(stateValue, 1)); + } + + if (opt.OVERVIEW_BG_BLUR_SIGMA || opt.APP_GRID_BG_BLUR_SIGMA) { + // reduce number of steps of blur transition to improve performance + const step = opt.SMOOTH_BLUR_TRANSITIONS ? 0.05 : 0.2; + const progress = stateValue - (stateValue % step); + if (opt.SHOW_WS_PREVIEW_BG && stateValue < 1 && !searchActive) { // no need to animate transition, unless appGrid state is involved, static bg is covered by the ws preview bg + if (blurEffect.sigma !== opt.OVERVIEW_BG_BLUR_SIGMA) + blurEffect.sigma = opt.OVERVIEW_BG_BLUR_SIGMA; + } else if (stateValue < 1 && !searchActive && !(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress)); + if (sigma !== blurEffect.sigma) + blurEffect.sigma = sigma; + } else if (stateValue < 1 && !searchActive && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && blurEffect.sigma)) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress)); + if (sigma !== blurEffect.sigma) + blurEffect.sigma = sigma; + } else if (stateValue > 1 && !searchActive && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && finalState === 1)) { + const sigma = Math.round(Util.lerp(0, opt.OVERVIEW_BG_BLUR_SIGMA, progress % 1)); + if (sigma !== blurEffect.sigma) + blurEffect.sigma = sigma; + } else if ((stateValue > 1 && bgManager._primary) || searchActive) { + const sigma = Math.round(Util.lerp(opt.OVERVIEW_BG_BLUR_SIGMA, opt.APP_GRID_BG_BLUR_SIGMA, progress % 1)); + if (sigma !== blurEffect.sigma) + blurEffect.sigma = sigma; + } else if (stateValue === 1 && !(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)) { + blurEffect.sigma = opt.OVERVIEW_BG_BLUR_SIGMA; + } else if (stateValue === 0 || (stateValue === 1 && (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE))) { + blurEffect.sigma = 0; + } + } + } + }, }; const ControlsManagerLayoutVertical = { - _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsWidth, searchHeight, startY) { + _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsWidth, thumbnailsHeight, searchHeight, startY) { + // in case the function is called from the DtD + if (startY === undefined) { + workAreaBox = box; + } const workspaceBox = box.copy(); let [width, height] = workspaceBox.get_size(); // const { x1: startX/* y1: startY*/ } = workAreaBox; @@ -640,7 +947,7 @@ const ControlsManagerLayoutVertical = { const dash = Main.overview.dash; // including Dash to Dock and clones properties for compatibility - if (_Util.dashIsDashToDock()) { + if (Me.Util.dashIsDashToDock()) { // Dash to Dock also always affects workAreaBox Main.layoutManager._trackedActors.forEach(actor => { if (actor.affectsStruts && actor.actor.width === dash.width) { @@ -678,7 +985,7 @@ const ControlsManagerLayoutVertical = { case ControlsState.APP_GRID: if (opt.WS_ANIMATION && opt.SHOW_WS_TMB && state === ControlsState.APP_GRID) { workspaceBox.set_origin(...this._workspacesThumbnails.get_position()); - workspaceBox.set_size(...this._workspacesThumbnails.get_size()); + workspaceBox.set_size(thumbnailsWidth, thumbnailsHeight); } else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { if (opt.START_Y_OFFSET) { let [x, y] = workAreaBox.get_origin(); @@ -693,7 +1000,7 @@ const ControlsManagerLayoutVertical = { height = opt.PANEL_POSITION_TOP ? height : height - Main.panel.height; searchHeight = opt.SHOW_SEARCH_ENTRY ? searchHeight : 0; wWidth = width - - (opt.DASH_VERTICAL ? dash.width : 0) - + (opt.DASH_VERTICAL ? dashWidth : 0) - thumbnailsWidth - 4 * spacing; wHeight = height - @@ -739,7 +1046,7 @@ const ControlsManagerLayoutVertical = { } const wsBoxX = /* startX + */xOffset; - wsBoxY = Math.round(startY + yOffset); + wsBoxY = startY + yOffset; workspaceBox.set_origin(Math.round(wsBoxX), Math.round(wsBoxY)); workspaceBox.set_size(Math.round(wWidth), Math.round(wHeight)); } @@ -749,6 +1056,10 @@ const ControlsManagerLayoutVertical = { }, _getAppDisplayBoxForState(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsWidth, startY) { + // in case the function is called from the DtD + if (startY === undefined) { + workAreaBox = box; + } const [width] = box.get_size(); const { x1: startX } = workAreaBox; // const { y1: startY } = workAreaBox; @@ -762,11 +1073,11 @@ const ControlsManagerLayoutVertical = { const xOffsetR = (opt.WS_TMB_RIGHT ? thumbnailsWidth : 0) + (opt.DASH_RIGHT ? dashWidth : 0); const yOffsetT = (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0); const yOffsetB = opt.DASH_BOTTOM ? dashHeight : 0; - const adWidth = opt.CENTER_APP_GRID ? width - 2 * Math.max(xOffsetL, xOffsetR) - 4 * spacing : width - xOffsetL - xOffsetR - 4 * spacing; - const adHeight = height - yOffsetT - yOffsetB - 4 * spacing; + const adWidth = opt.CENTER_APP_GRID ? width - 2 * Math.max(xOffsetL, xOffsetR) - 2 * spacing : width - xOffsetL - xOffsetR - 2 * spacing; + const adHeight = height - yOffsetT - yOffsetB; const appDisplayX = opt.CENTER_APP_GRID ? (width - adWidth) / 2 : xOffsetL + 2 * spacing; - const appDisplayY = startY + yOffsetT + 2 * spacing; + const appDisplayY = startY + yOffsetT; switch (state) { case ControlsState.HIDDEN: @@ -774,43 +1085,43 @@ const ControlsManagerLayoutVertical = { // 1 - left, 2 - right, 3 - bottom, 5 - top switch (opt.APP_GRID_ANIMATION) { case 0: - appDisplayBox.set_origin(appDisplayX, appDisplayY); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(appDisplayY)); break; case 1: - appDisplayBox.set_origin(startX + width, appDisplayY); + appDisplayBox.set_origin(Math.round(startX + width), Math.round(appDisplayY)); break; case 2: - appDisplayBox.set_origin(startX - adWidth, appDisplayY); + appDisplayBox.set_origin(Math.round(startX - adWidth), Math.round(appDisplayY)); break; case 3: - appDisplayBox.set_origin(appDisplayX, workAreaBox.y2); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(workAreaBox.y2)); break; case 5: - appDisplayBox.set_origin(appDisplayX, workAreaBox.y1 - adHeight); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(workAreaBox.y1 - adHeight)); break; } break; case ControlsState.APP_GRID: - appDisplayBox.set_origin(appDisplayX, appDisplayY); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(appDisplayY)); break; } - appDisplayBox.set_size(adWidth, adHeight); + appDisplayBox.set_size(Math.round(adWidth), Math.round(adHeight)); return appDisplayBox; }, vfunc_allocate(container, box) { - const childBox = new Clutter.ActorBox(); + const transitionParams = this._stateAdjustment.getStateTransitionParams(); + const childBox = new Clutter.ActorBox(); const { spacing } = this; - + const halfSpacing = spacing / 2; const monitor = Main.layoutManager.findMonitorForActor(this._container); const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); const startX = workArea.x - monitor.x; // if PANEL_OVERVIEW_ONLY, the affectStruts property is set to false to avoid stuttering // therefore we need to add panel height to startY let startY = workArea.y - monitor.y + opt.START_Y_OFFSET; - const workAreaBox = new Clutter.ActorBox(); workAreaBox.set_origin(startX, startY); workAreaBox.set_size(workArea.width, workArea.height); @@ -818,7 +1129,7 @@ const ControlsManagerLayoutVertical = { box.x1 += startX; let [width, height] = box.get_size(); // if panel is at bottom position, - // compensate the height of the available box (the box size is calculated for top panel) + // compensate for the height of the available box (the box size is calculated for top panel) height = opt.PANEL_POSITION_TOP ? height : height - Main.panel.height; let availableHeight = height; @@ -830,8 +1141,8 @@ const ControlsManagerLayoutVertical = { // dash cloud be overridden by the Dash to Dock clone const dash = Main.overview.dash; - if (_Util.dashIsDashToDock()) { - // if Dash to Dock replaced the default dash and its inteli-hide id disabled we need to compensate for affected startY + if (Me.Util.dashIsDashToDock()) { + // if Dash to Dock replaced the default dash and its inteli-hide is disabled we need to compensate for affected startY if (!Main.overview.dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { if (Main.panel.y === monitor.y) startY = Main.panel.height + spacing; @@ -857,48 +1168,48 @@ const ControlsManagerLayoutVertical = { } } - const transitionParams = this._stateAdjustment.getStateTransitionParams(); - // Workspace Thumbnails let wsTmbWidth = 0; let wsTmbHeight = 0; - if (this._workspacesThumbnails.visible) { - // const { expandFraction } = this._workspacesThumbnails; + let maxWsTmbScale = opt.MAX_THUMBNAIL_SCALE; + if (opt.SHOW_WS_TMB) { const dashHeightReservation = !opt.WS_TMB_FULL && !opt.DASH_VERTICAL ? dashHeight : 0; - let maxScale = opt.MAX_THUMBNAIL_SCALE; - if (!opt.MAX_THUMBNAIL_SCALE_STABLE) { + const searchActive = this._searchController.searchActive; + if (!opt.MAX_THUMBNAIL_SCALE_STABLE && !searchActive) { const initState = transitionParams.initialState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; const finalState = transitionParams.finalState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; - maxScale = Util.lerp(initState, finalState, transitionParams.progress); + maxWsTmbScale = Util.lerp(initState, finalState, transitionParams.progress); } - wsTmbWidth = width * maxScale; + wsTmbWidth = width * maxWsTmbScale; let totalTmbSpacing; - [totalTmbSpacing, wsTmbHeight] = this._workspacesThumbnails.get_preferred_custom_height(wsTmbWidth); + [totalTmbSpacing, wsTmbHeight] = this._workspacesThumbnails.get_preferred_height(wsTmbWidth); wsTmbHeight += totalTmbSpacing; - const wsTmbHeightMax = height - dashHeightReservation; + const wsTmbHeightMax = opt.WS_TMB_FULL + ? height - spacing + : height - dashHeightReservation - 2 * spacing; if (wsTmbHeight > wsTmbHeightMax) { wsTmbHeight = wsTmbHeightMax; - wsTmbWidth = this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight)[1]; + wsTmbWidth = Math.round(this._workspacesThumbnails.get_preferred_width(wsTmbHeight)[1]); } let wsTmbX; if (opt.WS_TMB_RIGHT) - wsTmbX = Math.round(startX + width - (opt.DASH_RIGHT ? dashWidth : 0) - wsTmbWidth - spacing / 2); + wsTmbX = Math.round(startX + width - (opt.DASH_RIGHT ? dashWidth : 0) - wsTmbWidth /* - halfSpacing*/); // this halfSpacing is a part od dash style else - wsTmbX = Math.round((opt.DASH_LEFT ? dashWidth : 0) + spacing / 2); + wsTmbX = Math.round(opt.DASH_LEFT ? dashWidth : 0/* + halfSpacing*/); // this halfSpacing is a part od dash style let wstOffset = (height - wsTmbHeight - (opt.DASH_VERTICAL ? 0 : dashHeightReservation)) / 2; - wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - spacing / 2); + wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - halfSpacing); let wsTmbY = Math.round(startY + (dashHeightReservation && opt.DASH_TOP ? dashHeight : 0) + wstOffset); childBox.set_origin(wsTmbX, wsTmbY); - childBox.set_size(Math.round(wsTmbWidth), Math.round(wsTmbHeight)); + childBox.set_size(Math.max(wsTmbWidth, 1), Math.max(wsTmbHeight, 1)); this._workspacesThumbnails.allocate(childBox); } @@ -927,7 +1238,7 @@ const ControlsManagerLayoutVertical = { if (!opt.DASH_VERTICAL) { offset = (width - ((opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && !this._xAlignCenter ? wsTmbWidth : 0) - dashWidth) / 2; - offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing); dashX = offset; if ((opt.WS_TMB_FULL || opt.CENTER_DASH_WS) && !this._xAlignCenter) { @@ -944,7 +1255,7 @@ const ControlsManagerLayoutVertical = { } } else { offset = (height - dashHeight) / 2; - dashY = startY + (offset - opt.DASH_POSITION_ADJUSTMENT * offset); + dashY = startY + (offset - opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing)); } childBox.set_origin(Math.round(startX + dashX), Math.round(dashY)); @@ -957,7 +1268,7 @@ const ControlsManagerLayoutVertical = { let [searchHeight] = this._searchEntry.get_preferred_height(width - wsTmbWidth); // Workspaces - let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbWidth, searchHeight, startY]; + let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbWidth, wsTmbHeight, searchHeight, startY]; // Update cached boxes for (const state of Object.values(ControlsState)) { @@ -983,7 +1294,7 @@ const ControlsManagerLayoutVertical = { // Y position under top Dash let searchEntryX, searchEntryY; if (opt.DASH_TOP) - searchEntryY = startY + dashHeight - spacing; + searchEntryY = startY + dashHeight; else searchEntryY = startY; @@ -1005,7 +1316,11 @@ const ControlsManagerLayoutVertical = { availableHeight -= searchHeight + spacing; // if (this._appDisplay.visible)... ? Can cause problems - params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbWidth, startY]; // send startY, can be corrected + // Calculate appDisplay always for AppGrid state WsTmb scale + let wsTmbWidthAppGrid = opt.MAX_THUMBNAIL_SCALE_APPGRID > 0 + ? Math.round(wsTmbWidth / maxWsTmbScale * opt.MAX_THUMBNAIL_SCALE_APPGRID) + : Math.round(wsTmbWidth / maxWsTmbScale * opt.MAX_THUMBNAIL_SCALE); + params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbWidthAppGrid, startY]; // send startY, can be corrected let appDisplayBox; if (!transitionParams.transitioning) { appDisplayBox = @@ -1024,9 +1339,9 @@ const ControlsManagerLayoutVertical = { if (opt.CENTER_SEARCH_VIEW) { const dashW = (opt.DASH_VERTICAL ? dashWidth : 0) + spacing; searchWidth = width - 2 * wsTmbWidth - 2 * dashW; - childBox.set_origin(wsTmbWidth + dashW, startY + (opt.DASH_TOP ? dashHeight : spacing) + searchHeight); + childBox.set_origin(wsTmbWidth + dashW, startY + (opt.DASH_TOP ? dashHeight + spacing : spacing) + searchHeight); } else { - childBox.set_origin(this._xAlignCenter ? wsTmbWidth + spacing : searchXoffset, startY + (opt.DASH_TOP ? dashHeight : spacing) + searchHeight); + childBox.set_origin(this._xAlignCenter ? wsTmbWidth + spacing : searchXoffset, startY + (opt.DASH_TOP ? dashHeight + spacing : spacing) + searchHeight); } childBox.set_size(searchWidth, availableHeight); @@ -1037,7 +1352,11 @@ const ControlsManagerLayoutVertical = { }; const ControlsManagerLayoutHorizontal = { - _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsHeight, searchHeight, startY) { + _computeWorkspacesBoxForState(state, box, workAreaBox, dashWidth, dashHeight, thumbnailsWidth, thumbnailsHeight, searchHeight, startY) { + // in case the function is called from the DtD + if (startY === undefined) { + workAreaBox = box; + } const workspaceBox = box.copy(); let [width, height] = workspaceBox.get_size(); // let { x1: startX/* , y1: startY*/ } = workAreaBox; @@ -1046,7 +1365,7 @@ const ControlsManagerLayoutHorizontal = { const dash = Main.overview.dash; // including Dash to Dock and clones properties for compatibility - if (_Util.dashIsDashToDock()) { + if (Me.Util.dashIsDashToDock()) { // Dash to Dock always affects workAreaBox Main.layoutManager._trackedActors.forEach(actor => { if (actor.affectsStruts && actor.actor.width === dash.width) { @@ -1084,7 +1403,7 @@ const ControlsManagerLayoutHorizontal = { case ControlsState.APP_GRID: if (opt.WS_ANIMATION && opt.SHOW_WS_TMB && state === ControlsState.APP_GRID) { workspaceBox.set_origin(...this._workspacesThumbnails.get_position()); - workspaceBox.set_size(...this._workspacesThumbnails.get_size()); + workspaceBox.set_size(thumbnailsWidth, thumbnailsHeight); } else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE) { if (opt.START_Y_OFFSET) { let [x, y] = workAreaBox.get_origin(); @@ -1146,7 +1465,7 @@ const ControlsManagerLayoutHorizontal = { } wsBoxX = /* startX + */xOffset; - wsBoxY = Math.round(startY + yOffset); + wsBoxY = startY + yOffset; workspaceBox.set_origin(Math.round(wsBoxX), Math.round(wsBoxY)); workspaceBox.set_size(Math.round(wWidth), Math.round(wHeight)); } @@ -1156,6 +1475,10 @@ const ControlsManagerLayoutHorizontal = { }, _getAppDisplayBoxForState(state, box, workAreaBox, searchHeight, dashWidth, dashHeight, thumbnailsHeight, startY) { + // in case the function is called from the DtD + if (startY === undefined) { + workAreaBox = box; + } const [width] = box.get_size(); const { x1: startX } = workAreaBox; // const { y1: startY } = workAreaBox; @@ -1163,16 +1486,16 @@ const ControlsManagerLayoutHorizontal = { const appDisplayBox = new Clutter.ActorBox(); const { spacing } = this; - const yOffsetT = (opt.WS_TMB_TOP ? thumbnailsHeight : 0) + (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0) + 2 * spacing; - const yOffsetB = (opt.WS_TMB_BOTTOM ? thumbnailsHeight : 0) + (opt.DASH_BOTTOM ? dashHeight : 0); + const yOffsetT = (opt.WS_TMB_TOP ? thumbnailsHeight + spacing : 0) + (opt.DASH_TOP ? dashHeight : 0) + (opt.SHOW_SEARCH_ENTRY ? searchHeight : 0); + const yOffsetB = (opt.WS_TMB_BOTTOM ? thumbnailsHeight + spacing : 0) + (opt.DASH_BOTTOM ? dashHeight : 0); const xOffsetL = opt.DASH_LEFT ? dashWidth : 0; const xOffsetR = opt.DASH_RIGHT ? dashWidth : 0; - const hSpacing = xOffsetL + xOffsetR ? 2 * spacing : 0; + const hSpacing = xOffsetL + xOffsetR ? spacing : 0; const adWidth = opt.CENTER_APP_GRID ? width - 2 * Math.max(xOffsetL, xOffsetR) - 2 * hSpacing : width - xOffsetL - xOffsetR - 2 * hSpacing; - const adHeight = height - yOffsetT - yOffsetB - 4 * spacing; + const adHeight = height - yOffsetT - yOffsetB; const appDisplayX = opt.CENTER_APP_GRID ? (width - adWidth) / 2 : xOffsetL + hSpacing; - const appDisplayY = startY + yOffsetT + hSpacing; + const appDisplayY = startY + yOffsetT; switch (state) { case ControlsState.HIDDEN: @@ -1180,36 +1503,36 @@ const ControlsManagerLayoutHorizontal = { // 1 - left, 2 - right, 3 - bottom, 5 - top switch (opt.APP_GRID_ANIMATION) { case 0: - appDisplayBox.set_origin(appDisplayX, appDisplayY); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(appDisplayY)); break; case 1: - appDisplayBox.set_origin(startX + width, appDisplayY); + appDisplayBox.set_origin(Math.round(startX + width), Math.round(appDisplayY)); break; case 2: - appDisplayBox.set_origin(startX - adWidth, appDisplayY); + appDisplayBox.set_origin(Math.round(startX - adWidth), Math.round(appDisplayY)); break; case 3: - appDisplayBox.set_origin(appDisplayX, workAreaBox.y2); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(workAreaBox.y2)); break; case 5: - appDisplayBox.set_origin(appDisplayX, workAreaBox.y1 - adHeight); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(workAreaBox.y1 - adHeight)); break; } break; case ControlsState.APP_GRID: - appDisplayBox.set_origin(appDisplayX, appDisplayY); + appDisplayBox.set_origin(Math.round(appDisplayX), Math.round(appDisplayY)); break; } - appDisplayBox.set_size(adWidth, adHeight); + appDisplayBox.set_size(Math.round(adWidth), Math.round(adHeight)); return appDisplayBox; }, vfunc_allocate(container, box) { + const transitionParams = this._stateAdjustment.getStateTransitionParams(); const childBox = new Clutter.ActorBox(); - const { spacing } = this; - + const halfSpacing = spacing / 2; const monitor = Main.layoutManager.findMonitorForActor(this._container); const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); const startX = workArea.x - monitor.x; @@ -1235,7 +1558,7 @@ const ControlsManagerLayoutHorizontal = { // dash cloud be overridden by the Dash to Dock clone const dash = Main.overview.dash; - if (_Util.dashIsDashToDock()) { + if (Me.Util.dashIsDashToDock()) { // if Dash to Dock replaced the default dash and its inteli-hide is disabled we need to compensate for affected startY if (!Main.overview.dash.get_parent()?.get_parent()?.get_parent()?._intellihideIsEnabled) { // if (Main.panel.y === monitor.y) @@ -1265,60 +1588,53 @@ const ControlsManagerLayoutHorizontal = { let [searchHeight] = this._searchEntry.get_preferred_height(width); - const transitionParams = this._stateAdjustment.getStateTransitionParams(); - // Workspace Thumbnails let wsTmbWidth = 0; let wsTmbHeight = 0; - if (this._workspacesThumbnails.visible) { - // const { expandFraction } = this._workspacesThumbnails; + let maxWsTmbScale = opt.MAX_THUMBNAIL_SCALE; + if (opt.SHOW_WS_TMB) { const dashWidthReservation = !opt.WS_TMB_FULL && opt.DASH_VERTICAL ? dashWidth : 0; - let maxScale = opt.MAX_THUMBNAIL_SCALE; - if (!opt.MAX_THUMBNAIL_SCALE_STABLE) { + const searchActive = this._searchController.searchActive; + if (!opt.MAX_THUMBNAIL_SCALE_STABLE && !searchActive) { const initState = transitionParams.initialState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; const finalState = transitionParams.finalState === ControlsState.APP_GRID ? opt.MAX_THUMBNAIL_SCALE_APPGRID : opt.MAX_THUMBNAIL_SCALE; - maxScale = Util.lerp(initState, finalState, transitionParams.progress); + maxWsTmbScale = Util.lerp(initState, finalState, transitionParams.progress); } - wsTmbHeight = height * maxScale; + wsTmbHeight = Math.round(height * maxWsTmbScale); let totalTmbSpacing; - [totalTmbSpacing, wsTmbWidth] = this._workspacesThumbnails.get_preferred_custom_width(wsTmbHeight); + [totalTmbSpacing, wsTmbWidth] = this._workspacesThumbnails.get_preferred_width(wsTmbHeight); wsTmbWidth += totalTmbSpacing; const wsTmbWidthMax = opt.WS_TMB_FULL - ? width - : width - (opt.DASH_VERTICAL ? 0 : dashWidthReservation); + ? width - spacing + : width - dashWidthReservation - 2 * spacing; if (wsTmbWidth > wsTmbWidthMax) { wsTmbWidth = wsTmbWidthMax; - wsTmbHeight = this._workspacesThumbnails.get_preferred_custom_height(wsTmbWidth)[1]; + wsTmbHeight = Math.round(this._workspacesThumbnails.get_preferred_height(wsTmbWidth)[1]); } let wsTmbY; if (opt.WS_TMB_TOP) - wsTmbY = Math.round(startY + /* searchHeight + */(opt.DASH_TOP ? dashHeight : spacing / 2)); + wsTmbY = Math.round(startY + (opt.DASH_TOP ? dashHeight : halfSpacing)); else - wsTmbY = Math.round(startY + height - (opt.DASH_BOTTOM ? dashHeight : 0) - wsTmbHeight); + wsTmbY = Math.round(startY + height - (opt.DASH_BOTTOM ? dashHeight : halfSpacing) - wsTmbHeight); - let wstOffset = (width - wsTmbWidth) / 2; - wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * (wstOffset - spacing / 2); - let wsTmbX = Math.round(Math.clamp( - startX + wstOffset, - startX + (opt.DASH_LEFT ? dashWidthReservation : 0), - width - wsTmbWidth - startX - (opt.DASH_RIGHT ? dashWidthReservation : 0) - )); + let wstOffset = (width - wsTmbWidth - dashWidthReservation) / 2; + wstOffset -= opt.WS_TMB_POSITION_ADJUSTMENT * wstOffset; + let wsTmbX = Math.round(startX + (opt.DASH_LEFT ? dashWidthReservation : 0) + wstOffset); childBox.set_origin(wsTmbX, wsTmbY); - childBox.set_size(Math.round(wsTmbWidth), Math.round(wsTmbHeight)); + childBox.set_size(Math.max(wsTmbWidth, 1), Math.max(wsTmbHeight, 1)); this._workspacesThumbnails.allocate(childBox); availableHeight -= wsTmbHeight + spacing; } - if (this._dash.visible) { if (opt.WS_TMB_FULL && opt.DASH_VERTICAL) { const wMaxHeight = height - spacing - wsTmbHeight; @@ -1339,25 +1655,24 @@ const ControlsManagerLayoutHorizontal = { else dashY = startY + height - dashHeight; - if (opt.DASH_VERTICAL) { if (opt.WS_TMB_FULL) { offset = (height - dashHeight - wsTmbHeight) / 2; if (opt.WS_TMB_TOP) { - offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing); dashY = startY + offset + wsTmbHeight; } else { - offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing); dashY = startY + offset; } } else { offset = (height - dashHeight) / 2; - offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - spacing / 2); + offset -= opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing); dashY = startY + offset; } } else { offset = (width - dashWidth) / 2; - dashX = startX + (offset - opt.DASH_POSITION_ADJUSTMENT * (offset - spacing)); + dashX = startX + (offset - opt.DASH_POSITION_ADJUSTMENT * (offset - halfSpacing)); } childBox.set_origin(Math.round(startX + dashX), Math.round(dashY)); @@ -1368,7 +1683,7 @@ const ControlsManagerLayoutHorizontal = { availableHeight -= opt.DASH_VERTICAL ? 0 : dashHeight; // Workspaces - let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbHeight, searchHeight, startY]; + let params = [box, workAreaBox, dashWidth, dashHeight, wsTmbWidth, wsTmbHeight, searchHeight, startY]; // Update cached boxes for (const state of Object.values(ControlsState)) { @@ -1394,7 +1709,7 @@ const ControlsManagerLayoutHorizontal = { // Y position under top Dash let searchEntryX, searchEntryY; if (opt.DASH_TOP) - searchEntryY = startY + (opt.WS_TMB_TOP ? wsTmbHeight : 0) + dashHeight - spacing; + searchEntryY = startY + (opt.WS_TMB_TOP ? wsTmbHeight : 0) + dashHeight; else searchEntryY = startY + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0); @@ -1416,7 +1731,11 @@ const ControlsManagerLayoutHorizontal = { availableHeight -= searchHeight + spacing; // if (this._appDisplay.visible)... ? Can cause problems - params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbHeight, startY]; + // Calculate appDisplay always for AppGrid state WsTmb scale + let wsTmbHeightAppGrid = opt.MAX_THUMBNAIL_SCALE_APPGRID > 0 + ? Math.round(wsTmbHeight / maxWsTmbScale * opt.MAX_THUMBNAIL_SCALE_APPGRID) + : Math.round(wsTmbHeight / maxWsTmbScale * opt.MAX_THUMBNAIL_SCALE); + params = [box, workAreaBox, searchHeight, dashWidth, dashHeight, wsTmbHeightAppGrid, startY]; let appDisplayBox; if (!transitionParams.transitioning) { appDisplayBox = @@ -1435,9 +1754,9 @@ const ControlsManagerLayoutHorizontal = { if (opt.CENTER_SEARCH_VIEW) { const dashW = (opt.DASH_VERTICAL ? dashWidth : 0) + spacing; searchWidth = width - 2 * dashW; - childBox.set_origin(dashW, startY + (opt.DASH_TOP ? dashHeight : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0) + searchHeight); + childBox.set_origin(dashW, startY + (opt.DASH_TOP ? dashHeight + spacing : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0) + searchHeight); } else { - childBox.set_origin(this._xAlignCenter ? spacing : searchXoffset, startY + (opt.DASH_TOP ? dashHeight : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0) + searchHeight); + childBox.set_origin(this._xAlignCenter ? spacing : searchXoffset, startY + (opt.DASH_TOP ? dashHeight + spacing : spacing) + (opt.WS_TMB_TOP ? wsTmbHeight + spacing : 0) + searchHeight); } childBox.set_size(searchWidth, availableHeight); @@ -1462,3 +1781,21 @@ function _getFitModeForState(state) { return FitMode.SINGLE; } } + +const LayoutManager = { + _startupAnimation() { + if (Me.Util.dashIsDashToDock() && !Meta.is_restart()) { + // DtD breaks overview on startup + // Skip animation to hide the mess + this._startupAnimationComplete(); + const controlsManager = Main.overview._overview.controls; + controlsManager._finishStartupSequence.bind(controlsManager)(); + } else if (Meta.is_restart()) { + this._startupAnimationComplete(); + } else if (Main.sessionMode.isGreeter) { + this._startupAnimationGreeter(); + } else { + this._startupAnimationSession(); + } + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/panel.js b/extensions/44/vertical-workspaces/lib/panel.js index 3f44ae7..898407a 100644 --- a/extensions/44/vertical-workspaces/lib/panel.js +++ b/extensions/44/vertical-workspaces/lib/panel.js @@ -10,188 +10,247 @@ 'use strict'; -const { GLib } = imports.gi; -const Main = imports.ui.main; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; +const Clutter = imports.gi.Clutter; -const ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; +const Main = imports.ui.main; +const Overview = imports.ui.overview; +const Panel = imports.ui.panel; +let Me; let opt; -let _firstRun = true; -let _showingOverviewConId; -let _hidingOverviewConId; -let _styleChangedConId; +const ANIMATION_TIME = Overview.ANIMATION_TIME; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('panelModule', true); - // Avoid conflict with other extensions - const conflict = _Util.getEnabledExtensions('dash-to-panel').length || - _Util.getEnabledExtensions('hidetopbar').length; - reset = reset || (!_firstRun && !moduleEnabled); +var PanelModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - // don't even touch this module if disabled or in potential conflict - if (_firstRun && (reset || conflict)) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; - _firstRun = false; + this._showingOverviewConId = 0; + this._hidingOverviewConId = 0; + this._styleChangedConId = 0; + } - const panelBox = Main.layoutManager.panelBox; - if (reset || !moduleEnabled) { - // _disconnectPanel(); - reset = true; - _setPanelPosition(reset); - _updateOverviewConnection(reset); - _reparentPanel(false); + cleanGlobals() { + Me = null; + opt = null; + } - _updateStyleChangedConnection(reset); + update(reset) { + this.moduleEnabled = opt.get('panelModule'); + const conflict = Me.Util.getEnabledExtensions('dash-to-panel').length || + Me.Util.getEnabledExtensions('hidetopbar').length; - panelBox.translation_y = 0; - Main.panel.opacity = 255; - _setPanelStructs(true); - return; + if (conflict && !reset) + console.warn(`[${Me.metadata.name}] Warning: "Panel" module disabled due to potential conflict with another extension`); + + reset = reset || !this.moduleEnabled || conflict || Main.sessionMode.isLocked; + + // don't touch original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' PanelModule - Keeping untouched'); + } + + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + const panelBox = Main.layoutManager.panelBox; + + this._setPanelPosition(); + this._updateStyleChangedConnection(); + + if (opt.PANEL_MODE === 0) { + this._updateOverviewConnection(true); + this._reparentPanel(false); + panelBox.translation_y = 0; + Main.panel.opacity = 255; + this._setPanelStructs(true); + } else if (opt.PANEL_MODE === 1) { + if (opt.SHOW_WS_PREVIEW_BG) { + this._reparentPanel(true); + if (opt.OVERVIEW_MODE2) { + // in OM2 if the panel has been moved to the overviewGroup move panel above all + Main.layoutManager.overviewGroup.set_child_above_sibling(panelBox, null); + this._updateOverviewConnection(); + } else { + // otherwise move the panel below overviewGroup so it can get below workspacesDisplay + Main.layoutManager.overviewGroup.set_child_below_sibling(panelBox, Main.overview._overview); + this._updateOverviewConnection(true); + } + this._showPanel(true); + } else { + // if ws preview bg is disabled, panel can stay in uiGroup + this._reparentPanel(false); + this._showPanel(false); + this._updateOverviewConnection(); + } + // _connectPanel(); + } else if (opt.PANEL_MODE === 2) { + this._updateOverviewConnection(true); + this._reparentPanel(false); + this._showPanel(false); + // _connectPanel(); + } + this._setPanelStructs(opt.PANEL_MODE === 0); + Main.layoutManager._updateHotCorners(); + + this._overrides.addOverride('ActivitiesButton', Panel.ActivitiesButton.prototype, ActivitiesButton); + + console.debug(' PanelModule - Activated'); } - _setPanelPosition(); - _updateStyleChangedConnection(); + _disableModule() { + const reset = true; + this._setPanelPosition(reset); + this._updateOverviewConnection(reset); + this._reparentPanel(false); + + this._updateStyleChangedConnection(reset); - if (opt.PANEL_MODE === 0) { - _updateOverviewConnection(true); - _reparentPanel(false); + const panelBox = Main.layoutManager.panelBox; panelBox.translation_y = 0; Main.panel.opacity = 255; - _setPanelStructs(true); - } else if (opt.PANEL_MODE === 1) { - if (opt.SHOW_WS_PREVIEW_BG) { - _reparentPanel(true); - if (opt.OVERVIEW_MODE2) { - // in OM2 if the panel has been moved to the overviewGroup move panel above all - Main.layoutManager.overviewGroup.set_child_above_sibling(panelBox, null); - _updateOverviewConnection(); - } else { - // otherwise move the panel below overviewGroup so it can get below workspacesDisplay - Main.layoutManager.overviewGroup.set_child_below_sibling(panelBox, Main.overview._overview); - _updateOverviewConnection(true); + this._setPanelStructs(true); + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + console.debug(' PanelModule - Disabled'); + } + + _setPanelPosition(reset = false) { + const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); + const panelBox = Main.layoutManager.panelBox; + const panelHeight = Main.panel.height; // panelBox height can be 0 after shell start + + if (opt.PANEL_POSITION_TOP || reset) + panelBox.set_position(geometry.x, geometry.y); + else + panelBox.set_position(geometry.x, geometry.y + geometry.height - panelHeight); + } + + _updateStyleChangedConnection(reset = false) { + if (reset) { + if (this._styleChangedConId) { + Main.panel.disconnect(this._styleChangedConId); + this._styleChangedConId = 0; + } + } else if (!this._styleChangedConId) { + this._styleChangedConId = Main.panel.connect('style-changed', () => { + if (opt.PANEL_MODE === 1 && !opt.OVERVIEW_MODE2) + Main.panel.add_style_pseudo_class('overview'); + else if (opt.OVERVIEW_MODE2) + Main.panel.remove_style_pseudo_class('overview'); + }); + } + } + + _updateOverviewConnection(reset = false) { + if (reset) { + if (this._hidingOverviewConId) { + Main.overview.disconnect(this._hidingOverviewConId); + this._hidingOverviewConId = 0; + } + if (this._showingOverviewConId) { + Main.overview.disconnect(this._showingOverviewConId); + this._showingOverviewConId = 0; } - _showPanel(true); } else { - // if ws preview bg is disabled, panel can stay in uiGroup - _reparentPanel(false); - _showPanel(false); - _updateOverviewConnection(); + if (!this._hidingOverviewConId) { + this._hidingOverviewConId = Main.overview.connect('hiding', () => { + if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2) + this._showPanel(false); + }); + } + if (!this._showingOverviewConId) { + this._showingOverviewConId = Main.overview.connect('showing', () => { + if (Main.layoutManager._startingUp) + return; + if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2 || Main.layoutManager.panelBox.translation_y) + this._showPanel(true); + }); + } } - // _connectPanel(); - } else if (opt.PANEL_MODE === 2) { - _updateOverviewConnection(true); - _reparentPanel(false); - _showPanel(false); - // _connectPanel(); } - _setPanelStructs(opt.PANEL_MODE === 0); - Main.layoutManager._updateHotCorners(); -} - -function _setPanelPosition(reset = false) { - const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor()); - const panelBox = Main.layoutManager.panelBox; - const panelHeight = Main.panel.height; // panelBox height can be 0 after shell start - - if (opt.PANEL_POSITION_TOP || reset) - panelBox.set_position(geometry.x, geometry.y); - else - panelBox.set_position(geometry.x, geometry.y + geometry.height - panelHeight); -} - -function _updateStyleChangedConnection(reset = false) { - if (reset) { - if (_styleChangedConId) { - Main.panel.disconnect(_styleChangedConId); - _styleChangedConId = 0; + + _reparentPanel(reparent = false) { + const panel = Main.layoutManager.panelBox; + if (reparent && panel.get_parent() === Main.layoutManager.uiGroup) { + Main.layoutManager.uiGroup.remove_child(panel); + Main.layoutManager.overviewGroup.add_child(panel); + } else if (!reparent && panel.get_parent() === Main.layoutManager.overviewGroup) { + Main.layoutManager.overviewGroup.remove_child(panel); + // return the panel at default position, panel shouldn't cover objects that should be above + Main.layoutManager.uiGroup.insert_child_at_index(panel, 4); } - } else if (!_styleChangedConId) { - Main.panel.connect('style-changed', () => { - if (opt.PANEL_MODE === 1) - Main.panel.add_style_pseudo_class('overview'); - else if (opt.OVERVIEW_MODE2) - Main.panel.remove_style_pseudo_class('overview'); + } + + _setPanelStructs(state) { + Main.layoutManager._trackedActors.forEach(a => { + if (a.actor === Main.layoutManager.panelBox) + a.affectsStruts = state; }); + + // workaround to force maximized windows to resize after removing affectsStruts + // simulation of minimal swipe gesture to the opposite direction + // todo - needs better solution!!!!!!!!!!! + // const direction = _getAppGridAnimationDirection() === 2 ? 1 : -1; + // Main.overview._swipeTracker._beginTouchSwipe(null, global.get_current_time(), 1, 1); + // Main.overview._swipeTracker._updateGesture(null, global.get_current_time(), direction, 1); + // GLib.timeout_add(0, 50, () => Main.overview._swipeTracker._endGesture(global.get_current_time(), 1, true));*/ } -} -function _updateOverviewConnection(reset = false) { - if (reset) { - if (_hidingOverviewConId) { - Main.overview.disconnect(_hidingOverviewConId); - _hidingOverviewConId = 0; - } - if (_showingOverviewConId) { - Main.overview.disconnect(_showingOverviewConId); - _showingOverviewConId = 0; - } - } else { - if (!_hidingOverviewConId) { - _hidingOverviewConId = Main.overview.connect('hiding', () => { - if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2) - _showPanel(false); + _showPanel(show = true) { + if (show) { + Main.panel.opacity = 255; + Main.layoutManager.panelBox.ease({ + duration: ANIMATION_TIME, + translation_y: 0, + onComplete: () => { + this._setPanelStructs(opt.PANEL_MODE === 0); + }, }); - } - if (!_showingOverviewConId) { - _showingOverviewConId = Main.overview.connect('showing', () => { - if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2 || Main.layoutManager.panelBox.translation_y) - _showPanel(true); + } else { + const panelHeight = Main.panel.height; + Main.layoutManager.panelBox.ease({ + duration: ANIMATION_TIME, + translation_y: opt.PANEL_POSITION_TOP ? -panelHeight + 1 : panelHeight - 1, + onComplete: () => { + Main.panel.opacity = 0; + this._setPanelStructs(opt.PANEL_MODE === 0); + }, }); } } -} - -function _reparentPanel(reparent = false) { - const panel = Main.layoutManager.panelBox; - if (reparent && panel.get_parent() === Main.layoutManager.uiGroup) { - Main.layoutManager.uiGroup.remove_child(panel); - Main.layoutManager.overviewGroup.add_child(panel); - } else if (!reparent && panel.get_parent() === Main.layoutManager.overviewGroup) { - Main.layoutManager.overviewGroup.remove_child(panel); - // return the panel at default position, panel shouldn't cover objects that should be above - Main.layoutManager.uiGroup.insert_child_at_index(panel, 4); - } -} - -function _setPanelStructs(state) { - Main.layoutManager._trackedActors.forEach(a => { - if (a.actor === Main.layoutManager.panelBox) - a.affectsStruts = state; - }); - - // workaround to force maximized windows to resize after removing affectsStruts - // simulation of minimal swipe gesture to the opposite direction - // todo - needs better solution!!!!!!!!!!! - // const direction = _getAppGridAnimationDirection() === 2 ? 1 : -1; - // Main.overview._swipeTracker._beginTouchSwipe(null, global.get_current_time(), 1, 1); - // Main.overview._swipeTracker._updateGesture(null, global.get_current_time(), direction, 1); - // GLib.timeout_add(0, 50, () => Main.overview._swipeTracker._endGesture(global.get_current_time(), 1, true));*/ -} - -function _showPanel(show = true) { - if (show) { - Main.panel.opacity = 255; - Main.layoutManager.panelBox.ease({ - duration: ANIMATION_TIME, - translation_y: 0, - onComplete: () => { - _setPanelStructs(opt.PANEL_MODE === 0); - }, - }); - } else { - const panelHeight = Main.panel.height; - Main.layoutManager.panelBox.ease({ - duration: ANIMATION_TIME, - translation_y: opt.PANEL_POSITION_TOP ? -panelHeight + 1 : panelHeight - 1, - onComplete: () => { - Main.panel.opacity = 0; - _setPanelStructs(opt.PANEL_MODE === 0); - }, - }); - } -} +}; + +const ActivitiesButton = { + vfunc_event(event) { + if (event.type() === Clutter.EventType.TOUCH_END || + event.type() === Clutter.EventType.BUTTON_RELEASE) { + if (Main.overview.shouldToggleByCornerOrButton()) { + if (event.get_button() === Clutter.BUTTON_SECONDARY && !Main.overview.dash.showAppsButton.checked) { + Main.overview.show(2); + Main.overview.dash.showAppsButton.checked = true; + } else { + Main.overview.toggle(); + } + } + } else if (event.type() === Clutter.EventType.SCROLL) { + Main.wm.handleWorkspaceScroll(event); + } + + return Clutter.EVENT_PROPAGATE; + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/recentFilesSearchProvider.js b/extensions/44/vertical-workspaces/lib/recentFilesSearchProvider.js index 86e38f4..b567ff2 100644 --- a/extensions/44/vertical-workspaces/lib/recentFilesSearchProvider.js +++ b/extensions/44/vertical-workspaces/lib/recentFilesSearchProvider.js @@ -1,5 +1,5 @@ /** - * Vertical Workspaces +* V-Shell (Vertical Workspaces) * recentFilesSearchProvider.js * * @author GdH <G-dH@github.com> @@ -9,116 +9,146 @@ 'use strict'; -const { GLib, Gio, Meta, St, Shell, Gtk } = imports.gi; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const RecentManager = imports.gi.Gtk.RecentManager; +const St = imports.gi.St; +const Shell = imports.gi.Shell; const Main = imports.ui.main; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.lib.settings; -const _Util = Me.imports.lib.util; +let Me; +let opt; // gettext -const _ = Settings._; - -const shellVersion = Settings.shellVersion; - -const ModifierType = imports.gi.Clutter.ModifierType; - -let recentFilesSearchProvider; -let _enableTimeoutId = 0; +let _; // prefix helps to eliminate results from other search providers // so it needs to be something less common // needs to be accessible from vw module -var prefix = 'fq//'; +const PREFIX = 'fq//'; + +var RecentFilesSearchProviderModule = class { + // export for other modules + static _PREFIX = PREFIX; + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + this._recentFilesSearchProvider = null; + this._enableTimeoutId = 0; + } -var opt; + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } -function getOverviewSearchResult() { - return Main.overview._overview.controls._searchController._searchResults; -} + update(reset) { + this.moduleEnabled = opt.get('recentFilesSearchProviderModule'); + reset = reset || !this.moduleEnabled; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - if (!reset && opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && !recentFilesSearchProvider) { - enable(); - } else if (reset || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) { - disable(); - opt = null; + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' RecentFilesSearchProviderModule - Keeping untouched'); } -} -function enable() { - // delay because Fedora had problem to register a new provider soon after Shell restarts - _enableTimeoutId = GLib.timeout_add( - GLib.PRIORITY_DEFAULT, - 2000, - () => { - if (!recentFilesSearchProvider) { - recentFilesSearchProvider = new RecentFilesSearchProvider(opt); - getOverviewSearchResult()._registerProvider(recentFilesSearchProvider); + _activateModule() { + // delay because Fedora had problem to register a new provider soon after Shell restarts + this._enableTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + 2000, + () => { + if (!this._recentFilesSearchProvider) { + this._recentFilesSearchProvider = new RecentFilesSearchProvider(opt); + this._getOverviewSearchResult()._registerProvider(this._recentFilesSearchProvider); + } + this._enableTimeoutId = 0; + return GLib.SOURCE_REMOVE; } - _enableTimeoutId = 0; - return GLib.SOURCE_REMOVE; - } - ); -} + ); -function disable() { - if (recentFilesSearchProvider) { - getOverviewSearchResult()._unregisterProvider(recentFilesSearchProvider); - recentFilesSearchProvider = null; - } - if (_enableTimeoutId) { - GLib.source_remove(_enableTimeoutId); - _enableTimeoutId = 0; + console.debug(' RecentFilesSearchProviderModule - Activated'); } -} -function makeResult(window, i) { - const app = Shell.WindowTracker.get_default().get_window_app(window); - const appName = app ? app.get_name() : 'Unknown'; - const windowTitle = window.get_title(); - const wsIndex = window.get_workspace().index(); - - return { - 'id': i, - // convert all accented chars to their basic form and lower case for search - 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), - appName, - windowTitle, - window, - }; -} + _disableModule() { + if (this._recentFilesSearchProvider) { + this._getOverviewSearchResult()._unregisterProvider(this._recentFilesSearchProvider); + this._recentFilesSearchProvider = null; + } + if (this._enableTimeoutId) { + GLib.source_remove(this._enableTimeoutId); + this._enableTimeoutId = 0; + } -const closeSelectedRegex = /^\/x!$/; -const closeAllResultsRegex = /^\/xa!$/; -const moveToWsRegex = /^\/m[0-9]+$/; -const moveAllToWsRegex = /^\/ma[0-9]+$/; + console.debug(' RecentFilesSearchProviderModule - Disabled'); + } -const RecentFilesSearchProvider = class RecentFilesSearchProvider { - constructor() { - this.id = 'org.gnome.Nautilus.desktop'; - this.appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/nautilus -ws recent:///', 'Recent Files', null); - // this.appInfo = Shell.AppSystem.get_default().lookup_app('org.gnome.Nautilus.desktop').appInfo; - this.appInfo.get_description = () => _('Search recent files'); - this.appInfo.get_name = () => _('Recent Files'); - this.appInfo.get_id = () => this.id; - this.appInfo.get_icon = () => Gio.icon_new_for_string('document-open-recent-symbolic'); - this.appInfo.should_show = () => true; + _getOverviewSearchResult() { + return Main.overview._overview.controls._searchController._searchResults; + } +}; +class RecentFilesSearchProvider { + constructor() { + this.id = 'recent-files'; + const appSystem = Shell.AppSystem.get_default(); + let appInfo = appSystem.lookup_app('org.gnome.Nautilus.desktop')?.get_app_info(); + if (!appInfo) + appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/nautilus -w', _('Recent Files'), null); + appInfo.get_description = () => _('Search recent files'); + appInfo.get_name = () => _('Recent Files'); + appInfo.get_id = () => 'org.gnome.Nautilus.desktop'; + appInfo.get_icon = () => Gio.icon_new_for_string('document-open-recent-symbolic'); + appInfo.should_show = () => true; + + this.appInfo = appInfo; this.canLaunchSearch = true; this.isRemoteProvider = false; } + getInitialResultSet(terms, callback /* , cancellable = null*/) { + // In GS 43 callback arg has been removed + /* if (Me.shellVersion >= 43) + cancellable = callback; */ + + const filesDict = {}; + let files = []; + if (terms[0].startsWith(PREFIX)) + files = RecentManager.get_default().get_items(); + + // Detect whether time stamps are in int, or in GLib.DateTime object + this._timeNeedsConversion = files[0]?.get_modified().to_unix; + + for (let file of files) + filesDict[file.get_uri()] = file; + + this.files = filesDict; + + if (Me.shellVersion >= 43) + return new Promise(resolve => resolve(this._getResultSet(terms))); + else + callback(this._getResultSet(terms)); + + return null; + } + _getResultSet(terms) { - if (!terms[0].startsWith(prefix)) + if (!terms[0].startsWith(PREFIX)) return []; // do not modify original terms let termsCopy = [...terms]; // search for terms without prefix - termsCopy[0] = termsCopy[0].replace(prefix, ''); + termsCopy[0] = termsCopy[0].replace(PREFIX, ''); const candidates = this.files; const _terms = [].concat(termsCopy); @@ -135,15 +165,18 @@ const RecentFilesSearchProvider = class RecentFilesSearchProvider { const file = this.files[id]; const name = `${file.get_age()}d: ${file.get_display_name()} ${file.get_uri_display().replace(`/${file.get_display_name()}`, '')}`; if (opt.SEARCH_FUZZY) - m = _Util.fuzzyMatch(term, name); + m = Me.Util.fuzzyMatch(term, name); else - m = _Util.strictMatch(term, name); + m = Me.Util.strictMatch(term, name); if (m !== -1) results.push({ weight: m, id }); } - results.sort((a, b) => this.files[a.id].get_visited() < this.files[b.id].get_visited()); + if (this._timeNeedsConversion) + results.sort((a, b) => this.files[a.id].get_modified().to_unix() < this.files[b.id].get_modified().to_unix()); + else + results.sort((a, b) => this.files[a.id].get_modified() < this.files[b.id].get_modified()); this.resultIds = results.map(item => item.id); return this.resultIds; @@ -151,7 +184,7 @@ const RecentFilesSearchProvider = class RecentFilesSearchProvider { getResultMetas(resultIds, callback = null) { const metas = resultIds.map(id => this.getResultMeta(id)); - if (shellVersion >= 43) + if (Me.shellVersion >= 43) return new Promise(resolve => resolve(metas)); else if (callback) callback(metas); @@ -172,89 +205,54 @@ const RecentFilesSearchProvider = class RecentFilesSearchProvider { } getIcon(result, size) { - let file = Gio.File.new_for_uri(result.get_uri()); - let info = file.query_info(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH, - Gio.FileQueryInfoFlags.NONE, null); - let path = info.get_attribute_byte_string( - Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH); - let icon, gicon; - if (path) { - gicon = Gio.FileIcon.new(Gio.File.new_for_path(path)); - } else { - const appInfo = Gio.AppInfo.get_default_for_type(result.get_mime_type(), false); - if (appInfo) - gicon = appInfo.get_icon(); - } + const appInfo = Gio.AppInfo.get_default_for_type(result.get_mime_type(), false); + if (appInfo) + gicon = appInfo.get_icon(); if (gicon) icon = new St.Icon({ gicon, icon_size: size }); else icon = new St.Icon({ icon_name: 'icon-missing', icon_size: size }); - return icon; } - launchSearch(/* terms, timeStamp */) { - this._openNautilus('recent:///'); + launchSearch(terms, timeStamp) { + const appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/nautilus -w recent:///', 'Nautilus', null); + appInfo.launch([], global.create_app_launch_context(timeStamp, -1)); } - _openNautilus(uri) { - try { - GLib.spawn_command_line_async(`nautilus -ws ${uri}`); - } catch (e) { - log(e); - } - } - - activateResult(resultId /* , terms, timeStamp */) { - const file = this.files[resultId]; - - if (_Util.isShiftPressed()) { + activateResult(resultId, terms, timeStamp) { + const uri = resultId; + const context = global.create_app_launch_context(timeStamp, -1); + if (Me.Util.isShiftPressed()) { Main.overview.toggle(); - this._openNautilus(file.get_uri()); + this.appInfo.launch_uris([uri], context); + } else if (Gio.app_info_launch_default_for_uri(uri, context)) { + // update recent list after (hopefully) successful activation + const recentManager = RecentManager.get_default(); + recentManager.add_item(resultId); } else { - const appInfo = Gio.AppInfo.get_default_for_type(file.get_mime_type(), false); - if (!(appInfo && appInfo.launch_uris([file.get_uri()], null))) - this._openNautilus(file.get_uri()); + this.appInfo.launch_uris([uri], context); } } - getInitialResultSet(terms, callback /* , cancellable = null*/) { - // In GS 43 callback arg has been removed - /* if (shellVersion >= 43) - cancellable = callback; */ - - const filesDict = {}; - const files = Gtk.RecentManager.get_default().get_items().filter(f => f.exists()); - - for (let file of files) - filesDict[file.get_uri()] = file; - - - this.files = filesDict; - - if (shellVersion >= 43) - return new Promise(resolve => resolve(this._getResultSet(terms))); - else - callback(this._getResultSet(terms)); - - return null; + filterResults(results /* , maxResults*/) { + // return results.slice(0, maxResults); + return results.slice(0, 20); } - filterResults(results, maxResults) { - return results.slice(0, 20); - // return results.slice(0, maxResults); + getSubsearchResultSet(previousResults, terms, callback) { + if (Me.shellVersion < 43) { + this.getSubsearchResultSet42(terms, callback); + return null; + } + return this.getInitialResultSet(terms); } - getSubsearchResultSet(previousResults, terms, callback /* , cancellable*/) { - // if we return previous results, quick typers get non-actual results + getSubsearchResultSet42(terms, callback) { callback(this._getResultSet(terms)); } - - /* createResultObject(resultMeta) { - return this.files[resultMeta.id]; - }*/ -}; +} diff --git a/extensions/44/vertical-workspaces/lib/search.js b/extensions/44/vertical-workspaces/lib/search.js index 8540626..618c5ed 100644 --- a/extensions/44/vertical-workspaces/lib/search.js +++ b/extensions/44/vertical-workspaces/lib/search.js @@ -9,92 +9,137 @@ */ 'use strict'; -const { Shell, Gio, St, Clutter } = imports.gi; -const Main = imports.ui.main; + +const Clutter = imports.gi.Clutter; +const Shell = imports.gi.Shell; +const St = imports.gi.St; const AppDisplay = imports.ui.appDisplay; +const IconGrid = imports.ui.iconGrid; +const Main = imports.ui.main; const Search = imports.ui.search; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; - -const _ = Me.imports.lib.settings._; -const shellVersion = _Util.shellVersion; - +let Me; +// gettext +let _; let opt; -let _overrides; -let _firstRun = true; + let SEARCH_MAX_WIDTH; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('searchModule', true); - reset = reset || !moduleEnabled; +var SearchModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; - // don't even touch this module if disabled - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - _firstRun = false; + cleanGlobals() { + Me = null; + opt = null; + _ = null; + } - if (_overrides) - _overrides.removeAll(); + update(reset) { + this.moduleEnabled = opt.get('searchModule'); + const conflict = false; - _updateSearchViewWidth(reset); + reset = reset || !this.moduleEnabled || conflict; - if (reset) { - Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.FILL; - opt = null; - _overrides = null; - return; + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' SearchModule - Keeping untouched'); + } + + _activateModule() { + this._updateSearchViewWidth(); + + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + this._overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); + this._overrides.addOverride('SearchResult', Search.SearchResult.prototype, SearchResult); + this._overrides.addOverride('SearchResultsView', Search.SearchResultsView.prototype, SearchResultsView); + this._overrides.addOverride('ProviderInfo', Search.ProviderInfo.prototype, ProviderInfo); + + // Don't expand the search view vertically and align it to the top + // this is important in the static workspace mode when the search view bg is not transparent + // also the "Searching..." and "No Results" notifications will be closer to the search entry, with the distance given by margin-top in the stylesheet + Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.START; + console.debug(' SearchModule - Activated'); } - _overrides = new _Util.Overrides(); + _disableModule() { + const reset = true; + this._updateSearchViewWidth(reset); - _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); - _overrides.addOverride('SearchResult', Search.SearchResult.prototype, SearchResult); - _overrides.addOverride('SearchResultsView', Search.SearchResultsView.prototype, SearchResultsView); + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.FILL; - // Don't expand the search view vertically and align it to the top - // this is important in the static workspace mode when the search view bg is not transparent - // also the "Searching..." and "No Results" notifications will be closer to the search entry, with the distance given by margin-top in the stylesheet - Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.START; -} -function _updateSearchViewWidth(reset = false) { - const searchContent = Main.overview._overview._controls.layoutManager._searchController._searchResults._content; - if (!SEARCH_MAX_WIDTH) { // just store original value; - const themeNode = searchContent.get_theme_node(); - const width = themeNode.get_max_width(); - SEARCH_MAX_WIDTH = width; + console.debug(' WorkspaceSwitcherPopupModule - Disabled'); } - if (reset) { - searchContent.set_style(''); - } else { - let width = Math.round(SEARCH_MAX_WIDTH * opt.SEARCH_VIEW_SCALE); - searchContent.set_style(`max-width: ${width}px;`); + _updateSearchViewWidth(reset = false) { + const searchContent = Main.overview._overview._controls.layoutManager._searchController._searchResults._content; + if (!SEARCH_MAX_WIDTH) { // just store original value; + const themeNode = searchContent.get_theme_node(); + const width = themeNode.get_max_width(); + SEARCH_MAX_WIDTH = width; + } + + if (reset) { + searchContent.set_style(''); + } else { + let width = Math.round(SEARCH_MAX_WIDTH * opt.SEARCH_VIEW_SCALE); + searchContent.set_style(`max-width: ${width}px;`); + } } -} +}; // AppDisplay.AppSearchProvider const AppSearchProvider = { - getInitialResultSet(terms, callback, _cancellable) { + getInitialResultSet(terms, callback, cancellable) { // Defer until the parental controls manager is initialized, so the // results can be filtered correctly. if (!this._parentalControlsManager.initialized) { - let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => { - if (this._parentalControlsManager.initialized) { - this._parentalControlsManager.disconnect(initializedId); - this.getInitialResultSet(terms, callback, _cancellable); - } - }); - return; + if (Me.shellVersion < 43) { + let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => { + if (this._parentalControlsManager.initialized) { + this._parentalControlsManager.disconnect(initializedId); + this.getInitialResultSet(terms, callback, cancellable); + } + }); + return null; + } else { + // callback has been removed in 43 + cancellable = callback; + return new Promise(resolve => { + let initializedId = this._parentalControlsManager.connect('app-filter-changed', async () => { + if (this._parentalControlsManager.initialized) { + this._parentalControlsManager.disconnect(initializedId); + resolve(await this.getInitialResultSet(terms, cancellable)); + } + }); + }); + } } - const pattern = terms.join(' '); + let appInfoList = Shell.AppSystem.get_default().get_installed(); let weightList = {}; @@ -113,22 +158,25 @@ const AppSearchProvider = { shouldShow = appInfo.should_show() && this._parentalControlsManager.shouldShowApp(appInfo); if (shouldShow) { + let id = appInfo.get_id().split('.'); + id = id[id.length - 2] || ''; + let baseName = appInfo.get_string('Name') || ''; let dispName = appInfo.get_display_name() || ''; let gName = appInfo.get_generic_name() || ''; let description = appInfo.get_description() || ''; let categories = appInfo.get_string('Categories') || ''; let keywords = appInfo.get_string('Keywords') || ''; - name = dispName; - string = `${dispName} ${gName} ${description} ${categories} ${keywords}`; + name = `${dispName} ${id}`; + string = `${dispName} ${gName} ${baseName} ${description} ${categories} ${keywords} ${id}`; } } let m = -1; if (shouldShow && opt.SEARCH_FUZZY) { - m = _Util.fuzzyMatch(pattern, name); - m = (m + _Util.strictMatch(pattern, string)) / 2; + m = Me.Util.fuzzyMatch(pattern, name); + m = (m + Me.Util.strictMatch(pattern, string)) / 2; } else if (shouldShow) { - m = _Util.strictMatch(pattern, string); + m = Me.Util.strictMatch(pattern, string); } if (m !== -1) @@ -143,16 +191,18 @@ const AppSearchProvider = { // sort apps by usage list appInfoList.sort((a, b) => usage.compare(a.get_id(), b.get_id())); // prefer apps where any word in their name starts with the pattern - appInfoList.sort((a, b) => _Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern)); + appInfoList.sort((a, b) => Me.Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern)); let results = appInfoList.map(app => app.get_id()); results = results.concat(this._systemActions.getMatchingActions(terms)); - if (shellVersion < 43) + if (Me.shellVersion < 43) { callback(results); - else + return null; + } else { return new Promise(resolve => resolve(results)); + } }, // App search result size @@ -181,12 +231,69 @@ const SearchResult = { St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText); } // don't close overview if Shift key is pressed - Shift moves windows to the workspace - if (!_Util.isShiftPressed()) + if (!Me.Util.isShiftPressed()) Main.overview.toggle(); }, }; const SearchResultsView = { + _doSearch() { + if (!this._doProviderSearch) { + this._doSearchLegacy(); + return; + } + this._startingSearch = false; + + let previousResults = this._results; + this._results = {}; + + this._providers.forEach(provider => { + const onlyVShellProviders = this._terms.includes('wq//') || this._terms.includes('fq//'); + if (!onlyVShellProviders || (onlyVShellProviders && (provider.id.includes('open-windows') || provider.id.includes('recent-files')))) { + let previousProviderResults = previousResults[provider.id]; + this._doProviderSearch(provider, previousProviderResults); + } + }); + + this._updateSearchProgress(); + + this._clearSearchTimeout(); + }, + + _doSearchLegacy() { + this._startingSearch = false; + + let previousResults = this._results; + this._results = {}; + + this._providers.forEach(provider => { + const onlyVShellProviders = this._terms.includes('wq//') || this._terms.includes('fq//'); + if (!onlyVShellProviders || (onlyVShellProviders && (provider.id.includes('open-windows') || provider.id.includes('recent-files')))) { + provider.searchInProgress = true; + + let previousProviderResults = previousResults[provider.id]; + if (this._isSubSearch && previousProviderResults) { + provider.getSubsearchResultSet(previousProviderResults, + this._terms, + results => { + this._gotResults(results, provider); + }, + this._cancellable); + } else { + provider.getInitialResultSet(this._terms, + results => { + this._gotResults(results, provider); + }, + this._cancellable); + } + } + }); + + this._updateSearchProgress(); + + this._clearSearchTimeout(); + }, + _updateSearchProgress() { let haveResults = this._providers.some(provider => { let display = provider.display; @@ -204,3 +311,13 @@ const SearchResultsView = { } }, }; + +// fixes app is null error if search provider id is not a desktop app id. +const ProviderInfo = { + animateLaunch() { + let appSys = Shell.AppSystem.get_default(); + let app = appSys.lookup_app(this.provider.appInfo.get_id()); + if (app && app.state === Shell.AppState.STOPPED) + IconGrid.zoomOutActor(this._content); + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/searchController.js b/extensions/44/vertical-workspaces/lib/searchController.js new file mode 100644 index 0000000..76b65e8 --- /dev/null +++ b/extensions/44/vertical-workspaces/lib/searchController.js @@ -0,0 +1,94 @@ +/** + * V-Shell (Vertical Workspaces) + * searchController.js + * + * @author GdH <G-dH@github.com> + * @copyright 2022 - 2023 + * @license GPL-3.0 + * + */ + +'use strict'; + +const Clutter = imports.gi.Clutter; + +const Main = imports.ui.main; + +let Me; +let opt; + +var SearchControllerModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + + this._firstActivation = true; + this.moduleEnabled = false; + this._originalOnStageKeyPress = null; + } + + cleanGlobals() { + Me = null; + opt = null; + } + + update(reset) { + this.moduleEnabled = opt.get('searchControllerModule'); + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' SearchControllerModule - Keeping untouched'); + } + + _activateModule() { + if (!this._originalOnStageKeyPress) + this._originalOnStageKeyPress = Main.overview._overview.controls._searchController._onStageKeyPress; + + Main.overview._overview.controls._searchController._onStageKeyPress = SearchControllerCommon._onStageKeyPress; + console.debug(' SearchControllerModule - Activated'); + } + + _disableModule() { + if (this._originalOnStageKeyPress) + Main.overview._overview.controls._searchController._onStageKeyPress = this._originalOnStageKeyPress; + this._originalOnStageKeyPress = null; + + console.debug(' SearchControlerModule - Disabled'); + } +}; + +// if opt.ESC_BEHAVIOR > 0 force close the overview +const SearchControllerCommon = { + _onStageKeyPress(actor, event) { + // Ignore events while anything but the overview has + // pushed a modal (system modals, looking glass, ...) + if (Main.modalCount > 1) + return Clutter.EVENT_PROPAGATE; + + let symbol = event.get_key_symbol(); + if (symbol === Clutter.KEY_Escape) { + if (this._searchActive && !opt.ESC_BEHAVIOR) { + this.reset(); + } else if (this._showAppsButton.checked && !opt.ESC_BEHAVIOR) { + this._showAppsButton.checked = false; + } else { + this.reset(); + Main.overview.hide(); + } + + return Clutter.EVENT_STOP; + } else if (this._shouldTriggerSearch(symbol)) { + this.startSearch(event); + } + return Clutter.EVENT_PROPAGATE; + }, +}; diff --git a/extensions/44/vertical-workspaces/lib/settings.js b/extensions/44/vertical-workspaces/lib/settings.js index 66f3a45..cbb74f2 100644 --- a/extensions/44/vertical-workspaces/lib/settings.js +++ b/extensions/44/vertical-workspaces/lib/settings.js @@ -9,25 +9,15 @@ 'use strict'; -const { GLib } = imports.gi; +const GLib = imports.gi.GLib; -const Config = imports.misc.config; - -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); - -var shellVersion = parseFloat(Config.PACKAGE_VERSION); - -const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); -var _ = Gettext.gettext; -const _schema = Me.metadata['settings-schema']; - -// common instance of Options accessible from all modules -var opt; +let Me; var Options = class Options { - constructor() { - this._gsettings = ExtensionUtils.getSettings(_schema); + constructor(me) { + Me = me; + + this._gsettings = Me.gSettings; this._connectionIds = []; this._writeTimeoutId = 0; this._gsettings.delay(); @@ -51,8 +41,8 @@ var Options = class Options { wsMaxSpacing: ['int', 'ws-max-spacing'], wsPreviewScale: ['int', 'ws-preview-scale'], secWsPreviewScale: ['int', 'secondary-ws-preview-scale'], - secWsPreviewShift: ['bool', 'secondary-ws-preview-shift'], - wsThumbnailsFull: ['bool', 'ws-thumbnails-full'], + secWsPreviewShift: ['boolean', 'secondary-ws-preview-shift'], + wsThumbnailsFull: ['boolean', 'ws-thumbnails-full'], secWsThumbnailsPosition: ['int', 'secondary-ws-thumbnails-position'], dashPosition: ['int', 'dash-position'], dashPositionAdjust: ['int', 'dash-position-adjust'], @@ -64,6 +54,7 @@ var Options = class Options { dashMaxIconSize: ['int', 'dash-max-icon-size'], dashShowWindowsIcon: ['int', 'dash-show-windows-icon'], dashShowRecentFilesIcon: ['int', 'dash-show-recent-files-icon'], + dashShowExtensionsIcon: ['int', 'dash-show-extensions-icon'], centerDashToWs: ['boolean', 'center-dash-to-ws'], showAppsIconPosition: ['int', 'show-app-icon-position'], wsThumbnailScale: ['int', 'ws-thumbnail-scale'], @@ -73,13 +64,17 @@ var Options = class Options { centerSearch: ['boolean', 'center-search'], centerAppGrid: ['boolean', 'center-app-grid'], dashBgOpacity: ['int', 'dash-bg-opacity'], + dashBgColor: ['int', 'dash-bg-color'], dashBgRadius: ['int', 'dash-bg-radius'], + dashBgGS3Style: ['boolean', 'dash-bg-gs3-style'], + runningDotStyle: ['int', 'running-dot-style'], enablePageShortcuts: ['boolean', 'enable-page-shortcuts'], showWsSwitcherBg: ['boolean', 'show-ws-switcher-bg'], showWsPreviewBg: ['boolean', 'show-ws-preview-bg'], wsPreviewBgRadius: ['int', 'ws-preview-bg-radius'], showBgInOverview: ['boolean', 'show-bg-in-overview'], overviewBgBrightness: ['int', 'overview-bg-brightness'], + searchBgBrightness: ['int', 'search-bg-brightness'], overviewBgBlurSigma: ['int', 'overview-bg-blur-sigma'], appGridBgBlurSigma: ['int', 'app-grid-bg-blur-sigma'], smoothBlurTransitions: ['boolean', 'smooth-blur-transitions'], @@ -87,9 +82,8 @@ var Options = class Options { searchViewAnimation: ['int', 'search-view-animation'], workspaceAnimation: ['int', 'workspace-animation'], animationSpeedFactor: ['int', 'animation-speed-factor'], - fixUbuntuDock: ['boolean', 'fix-ubuntu-dock'], winPreviewIconSize: ['int', 'win-preview-icon-size'], - alwaysShowWinTitles: ['boolean', 'always-show-win-titles'], + winTitlePosition: ['int', 'win-title-position'], startupState: ['int', 'startup-state'], overviewMode: ['int', 'overview-mode'], workspaceSwitcherAnimation: ['int', 'workspace-switcher-animation'], @@ -105,17 +99,18 @@ var Options = class Options { appGridContent: ['int', 'app-grid-content'], appGridIncompletePages: ['boolean', 'app-grid-incomplete-pages'], appGridOrder: ['int', 'app-grid-order'], + appFolderOrder: ['int', 'app-folder-order'], appGridNamesMode: ['int', 'app-grid-names'], appGridActivePreview: ['boolean', 'app-grid-active-preview'], appGridFolderCenter: ['boolean', 'app-grid-folder-center'], appGridPageWidthScale: ['int', 'app-grid-page-width-scale'], appGridSpacing: ['int', 'app-grid-spacing'], - searchWindowsEnable: ['boolean', 'search-windows-enable'], - searchRecentFilesEnable: ['boolean', 'search-recent-files-enable'], + searchWindowsOrder: ['int', 'search-windows-order'], searchFuzzy: ['boolean', 'search-fuzzy'], searchMaxResultsRows: ['int', 'search-max-results-rows'], dashShowWindowsBeforeActivation: ['int', 'dash-show-windows-before-activation'], dashIconScroll: ['int', 'dash-icon-scroll'], + dashIsolateWorkspaces: ['boolean', 'dash-isolate-workspaces'], searchWindowsIconScroll: ['int', 'search-windows-icon-scroll'], panelVisibility: ['int', 'panel-visibility'], panelPosition: ['int', 'panel-position'], @@ -123,6 +118,8 @@ var Options = class Options { wsSwPopupHPosition: ['int', 'ws-sw-popup-h-position'], wsSwPopupVPosition: ['int', 'ws-sw-popup-v-position'], wsSwPopupMode: ['int', 'ws-sw-popup-mode'], + wsSwitcherWraparound: ['boolean', 'ws-switcher-wraparound'], + wsSwitcherIgnoreLast: ['boolean', 'ws-switcher-ignore-last'], favoritesNotify: ['int', 'favorites-notify'], notificationPosition: ['int', 'notification-position'], osdPosition: ['int', 'osd-position'], @@ -131,17 +128,26 @@ var Options = class Options { hotCornerFullscreen: ['boolean', 'hot-corner-fullscreen'], hotCornerRipples: ['boolean', 'hot-corner-ripples'], alwaysActivateSelectedWindow: ['boolean', 'always-activate-selected-window'], - windowIconClickSearch: ['boolean', 'window-icon-click-search'], + winPreviewSecBtnAction: ['int', 'win-preview-sec-mouse-btn-action'], + winPreviewMidBtnAction: ['int', 'win-preview-mid-mouse-btn-action'], + winPreviewShowCloseButton: ['boolean', 'win-preview-show-close-button'], + windowIconClickAction: ['int', 'window-icon-click-action'], + overlayKeyPrimary: ['int', 'overlay-key-primary'], overlayKeySecondary: ['int', 'overlay-key-secondary'], + overviewEscBehavior: ['int', 'overview-esc-behavior'], + newWindowFocusFix: ['boolean', 'new-window-focus-fix'], + appGridPerformance: ['boolean', 'app-grid-performance'], + windowThumbnailScale: ['int', 'window-thumbnail-scale'], - workspaceThumbnailsModule: ['boolean', 'workspace-thumbnails-module'], workspaceSwitcherPopupModule: ['boolean', 'workspace-switcher-popup-module'], workspaceAnimationModule: ['boolean', 'workspace-animation-module'], workspaceModule: ['boolean', 'workspace-module'], windowManagerModule: ['boolean', 'window-manager-module'], windowPreviewModule: ['boolean', 'window-preview-module'], - winAttentionHandlerModule: ['boolean', 'win-attention-handler-module'], + windowAttentionHandlerModule: ['boolean', 'win-attention-handler-module'], + windowThumbnailModule: ['boolean', 'window-thumbnail-module'], swipeTrackerModule: ['boolean', 'swipe-tracker-module'], + searchControllerModule: ['boolean', 'search-controller-module'], searchModule: ['boolean', 'search-module'], panelModule: ['boolean', 'panel-module'], overlayKeyModule: ['boolean', 'overlay-key-module'], @@ -151,6 +157,9 @@ var Options = class Options { dashModule: ['boolean', 'dash-module'], appFavoritesModule: ['boolean', 'app-favorites-module'], appDisplayModule: ['boolean', 'app-display-module'], + windowSearchProviderModule: ['boolean', 'window-search-provider-module'], + recentFilesSearchProviderModule: ['boolean', 'recent-files-search-provider-module'], + extensionsSearchProviderModule: ['boolean', 'extensions-search-provider-module'], profileName1: ['string', 'profile-name-1'], profileName2: ['string', 'profile-name-2'], @@ -158,9 +167,10 @@ var Options = class Options { profileName4: ['string', 'profile-name-4'], }; this.cachedOptions = {}; + } - this.shellVersion = shellVersion; - // this.storeProfile(0); + cleanGlobals() { + Me = null; } connect(name, callback) { @@ -183,7 +193,7 @@ var Options = class Options { get(option, updateCache = false) { if (!this.options[option]) { - log(`[${Me.metadata.name}] Error: Option ${option} is undefined.`); + console.error(`[${Me.metadata.name}] Error: Option ${option} is undefined.`); return null; } @@ -195,7 +205,6 @@ var Options = class Options { else gSettings = this._gsettings; - this.cachedOptions[option] = gSettings.get_value(key).deep_unpack(); } @@ -242,7 +251,8 @@ var Options = class Options { storeProfile(index) { const profile = {}; Object.keys(this.options).forEach(v => { - profile[v] = this.get(v).toString(); + if (!v.startsWith('profileName')) + profile[v] = this.get(v).toString(); }); this._gsettings.set_value(`profile-data-${index}`, new GLib.Variant('a{ss}', profile)); @@ -250,8 +260,14 @@ var Options = class Options { loadProfile(index) { const options = this._gsettings.get_value(`profile-data-${index}`).deep_unpack(); + // set the aaa-loading-data so extension.js doesn't reset V-Shell after each profile item + // delayed gsettings writes are processed alphabetically, so this key will be processed first this._gsettings.set_boolean('aaa-loading-profile', !this._gsettings.get_boolean('aaa-loading-profile')); for (let o of Object.keys(options)) { + if (!this.options[o]) { + console.error(`[${Me.metadata.name}] Error: "${o}" is not a valid profile key -> Update your profile`); + continue; + } const [type] = this.options[o]; let value = options[o]; switch (type) { @@ -275,7 +291,14 @@ var Options = class Options { } _updateSettings() { - this.DASH_POSITION = this.get('dashPosition', true); + this._updateCachedSettings(); + this.DASH_BG_ALPHA = this.get('dashBgOpacity') / 100; + this.DASH_BG_OPACITY = this.get('dashBgOpacity') * 2.5; + this.DASH_BG_COLOR = this.get('dashBgColor'); + this.DASH_BG_RADIUS = this.get('dashBgRadius'); + this.DASH_BG_LIGHT = this.DASH_BG_COLOR === 1; + this.DASH_BG_GS3_STYLE = this.get('dashBgGS3Style'); + this.DASH_POSITION = this.get('dashModule') ? this.get('dashPosition') : 2; this.DASH_TOP = this.DASH_POSITION === 0; this.DASH_RIGHT = this.DASH_POSITION === 1; this.DASH_BOTTOM = this.DASH_POSITION === 2; @@ -284,26 +307,35 @@ var Options = class Options { this.DASH_VISIBLE = this.DASH_POSITION !== 4; // 4 - disable this.DASH_FOLLOW_RECENT_WIN = false; - this.DASH_CLICK_ACTION = this.get('dashShowWindowsBeforeActivation', true); - this.DASH_ICON_SCROLL = this.get('dashIconScroll', true); + this.DASH_ISOLATE_WS = this.get('dashIsolateWorkspaces'); + + this.DASH_CLICK_ACTION = this.get('dashShowWindowsBeforeActivation'); + this.DASH_CLICK_SWITCH_BEFORE_ACTIVATION = this.DASH_CLICK_ACTION === 1; + this.DASH_CLICK_OPEN_NEW_WIN = this.DASH_CLICK_ACTION === 2; + this.DASH_CLICK_PREFER_WORKSPACE = this.DASH_CLICK_ACTION === 3; + + this.DASH_ICON_SCROLL = this.get('dashIconScroll'); this.DASH_SHIFT_CLICK_MV = true; - this.SEARCH_WINDOWS_ICON_SCROLL = this.get('searchWindowsIconScroll', true); + this.RUNNING_DOT_STYLE = this.get('runningDotStyle'); + + this.SEARCH_WINDOWS_ICON_SCROLL = this.get('searchWindowsIconScroll'); - this.DASH_POSITION_ADJUSTMENT = this.get('dashPositionAdjust', true); + this.DASH_POSITION_ADJUSTMENT = this.get('dashPositionAdjust'); this.DASH_POSITION_ADJUSTMENT = this.DASH_POSITION_ADJUSTMENT * -1 / 100; // range 1 to -1 - this.CENTER_DASH_WS = this.get('centerDashToWs', true); + this.CENTER_DASH_WS = this.get('centerDashToWs'); - this.MAX_ICON_SIZE = 64; // updates from main module - this.SHOW_WINDOWS_ICON = this.get('dashShowWindowsIcon', true); - this.SHOW_RECENT_FILES_ICON = this.get('dashShowRecentFilesIcon', true); + this.MAX_ICON_SIZE = this.get('dashMaxIconSize'); + this.SHOW_WINDOWS_ICON = this.get('dashShowWindowsIcon'); + this.SHOW_RECENT_FILES_ICON = this.get('dashShowRecentFilesIcon'); + this.SHOW_EXTENSIONS_ICON = this.get('dashShowExtensionsIcon'); - this.WS_TMB_POSITION = this.get('workspaceThumbnailsPosition', true); + this.WS_TMB_POSITION = this.get('workspaceThumbnailsPosition'); this.ORIENTATION = this.WS_TMB_POSITION > 4 ? 0 : 1; - this.WORKSPACE_MAX_SPACING = this.get('wsMaxSpacing', true); + this.WORKSPACE_MAX_SPACING = this.get('wsMaxSpacing'); // ORIENTATION || DASH_LEFT || DASH_RIGHT ? 350 : 80; this.SHOW_WS_TMB = ![4, 9].includes(this.WS_TMB_POSITION); // 4, 9 - disable - this.WS_TMB_FULL = this.get('wsThumbnailsFull', true); + this.WS_TMB_FULL = this.get('wsThumbnailsFull'); // translate ws tmb position to 0 top, 1 right, 2 bottom, 3 left // 0L 1R, 2LF, 3RF, 4DV, 5T, 6B, 7TF, 8BF, 9DH this.WS_TMB_POSITION = [3, 1, 3, 1, 4, 0, 2, 0, 2, 8][this.WS_TMB_POSITION]; @@ -311,81 +343,104 @@ var Options = class Options { this.WS_TMB_RIGHT = this.WS_TMB_POSITION === 1; this.WS_TMB_BOTTOM = this.WS_TMB_POSITION === 2; this.WS_TMB_LEFT = this.WS_TMB_POSITION === 3; - this.WS_TMB_POSITION_ADJUSTMENT = this.get('wsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1 - this.SEC_WS_TMB_POSITION = this.get('secWsThumbnailsPosition', true); + this.WS_TMB_POSITION_ADJUSTMENT = this.get('wsTmbPositionAdjust') * -1 / 100; // range 1 to -1 + this.SEC_WS_TMB_POSITION = this.get('secWsThumbnailsPosition'); this.SHOW_SEC_WS_TMB = this.SEC_WS_TMB_POSITION !== 3 && this.SHOW_WS_TMB; this.SEC_WS_TMB_TOP = (this.SEC_WS_TMB_POSITION === 0 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_TOP); this.SEC_WS_TMB_RIGHT = (this.SEC_WS_TMB_POSITION === 1 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_RIGHT); this.SEC_WS_TMB_BOTTOM = (this.SEC_WS_TMB_POSITION === 1 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_BOTTOM); this.SEC_WS_TMB_LEFT = (this.SEC_WS_TMB_POSITION === 0 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_LEFT); - this.SEC_WS_TMB_POSITION_ADJUSTMENT = this.get('secWsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1 - this.SEC_WS_PREVIEW_SHIFT = this.get('secWsPreviewShift', true); - this.SHOW_WST_LABELS = this.get('showWsTmbLabels', true); - this.SHOW_WST_LABELS_ON_HOVER = this.get('showWsTmbLabelsOnHover', true); - this.CLOSE_WS_BUTTON_MODE = this.get('closeWsButtonMode', true); + this.SEC_WS_TMB_POSITION_ADJUSTMENT = this.get('secWsTmbPositionAdjust') * -1 / 100; // range 1 to -1 + this.SEC_WS_PREVIEW_SHIFT = this.get('secWsPreviewShift'); + this.SHOW_WST_LABELS = this.get('showWsTmbLabels'); + this.SHOW_WST_LABELS_ON_HOVER = this.get('showWsTmbLabelsOnHover'); + this.CLOSE_WS_BUTTON_MODE = this.get('closeWsButtonMode'); - this.MAX_THUMBNAIL_SCALE = this.get('wsThumbnailScale', true) / 100; - this.MAX_THUMBNAIL_SCALE_APPGRID = this.get('wsThumbnailScaleAppGrid', true) / 100; - if (this.MAX_THUMBNAIL_SCALE_APPGRID === 0) - this.MAX_THUMBNAIL_SCALE_APPGRID = this.MAX_THUMBNAIL_SCALE; + this.MAX_THUMBNAIL_SCALE = this.get('wsThumbnailScale') / 100; + if (this.MAX_THUMBNAIL_SCALE === 0) { + this.MAX_THUMBNAIL_SCALE = 0.01; + this.SHOW_WS_TMB = false; + } + this.MAX_THUMBNAIL_SCALE_APPGRID = this.get('wsThumbnailScaleAppGrid') / 100; + this.SHOW_WS_TMB_APPGRID = true; + if (this.MAX_THUMBNAIL_SCALE_APPGRID === 0) { + this.MAX_THUMBNAIL_SCALE_APPGRID = 0.01; + this.SHOW_WS_TMB_APPGRID = false; + } this.MAX_THUMBNAIL_SCALE_STABLE = this.MAX_THUMBNAIL_SCALE === this.MAX_THUMBNAIL_SCALE_APPGRID; - this.SEC_MAX_THUMBNAIL_SCALE = this.get('secWsThumbnailScale', true) / 100; - this.WS_PREVIEW_SCALE = this.get('wsPreviewScale', true) / 100; - this.SEC_WS_PREVIEW_SCALE = this.get('secWsPreviewScale', true) / 100; + this.SEC_MAX_THUMBNAIL_SCALE = this.get('secWsThumbnailScale') / 100; + if (this.SEC_MAX_THUMBNAIL_SCALE === 0) { + this.SEC_MAX_THUMBNAIL_SCALE = 0.01; + this.SHOW_SEC_WS_TMB = false; + } + + this.WS_PREVIEW_SCALE = this.get('wsPreviewScale') / 100; + this.SEC_WS_PREVIEW_SCALE = this.get('secWsPreviewScale') / 100; // calculate number of possibly visible neighbor previews according to ws scale this.NUMBER_OF_VISIBLE_NEIGHBORS = Math.round(1 + (1 - this.WS_PREVIEW_SCALE) / 4); - this.SHOW_WS_TMB_BG = this.get('showWsSwitcherBg', true) && this.SHOW_WS_TMB; - this.WS_PREVIEW_BG_RADIUS = this.get('wsPreviewBgRadius', true); - this.SHOW_WS_PREVIEW_BG = this.get('showWsPreviewBg', true); + this.SHOW_WS_TMB_BG = this.get('showWsSwitcherBg') && this.SHOW_WS_TMB; + this.WS_PREVIEW_BG_RADIUS = this.get('wsPreviewBgRadius'); + this.SHOW_WS_PREVIEW_BG = this.get('showWsPreviewBg'); - this.CENTER_APP_GRID = this.get('centerAppGrid', true); + this.CENTER_APP_GRID = this.get('centerAppGrid'); - this.SHOW_SEARCH_ENTRY = this.get('showSearchEntry', true); - this.CENTER_SEARCH_VIEW = this.get('centerSearch', true); - this.APP_GRID_ANIMATION = this.get('appGridAnimation', true); + this.SHOW_SEARCH_ENTRY = this.get('showSearchEntry'); + this.CENTER_SEARCH_VIEW = this.get('centerSearch'); + this.APP_GRID_ANIMATION = this.get('appGridAnimation'); if (this.APP_GRID_ANIMATION === 4) this.APP_GRID_ANIMATION = this._getAnimationDirection(); - this.SEARCH_VIEW_ANIMATION = this.get('searchViewAnimation', true); + this.SEARCH_VIEW_ANIMATION = this.get('searchViewAnimation'); if (this.SEARCH_VIEW_ANIMATION === 4) this.SEARCH_VIEW_ANIMATION = 3; - this.WS_ANIMATION = this.get('workspaceAnimation', true); + this.WS_ANIMATION = this.get('workspaceAnimation'); - this.WIN_PREVIEW_ICON_SIZE = [64, 48, 32, 22, 8][this.get('winPreviewIconSize', true)]; - this.ALWAYS_SHOW_WIN_TITLES = this.get('alwaysShowWinTitles', true); + this.WIN_PREVIEW_ICON_SIZE = [64, 48, 32, 22, 8][this.get('winPreviewIconSize')]; + this.WIN_TITLES_POSITION = this.get('winTitlePosition'); + this.ALWAYS_SHOW_WIN_TITLES = this.WIN_TITLES_POSITION === 1; - this.STARTUP_STATE = this.get('startupState', true); - this.SHOW_BG_IN_OVERVIEW = this.get('showBgInOverview', true); - this.OVERVIEW_BG_BRIGHTNESS = this.get('overviewBgBrightness', true) / 100; - this.OVERVIEW_BG_BLUR_SIGMA = this.get('overviewBgBlurSigma', true); - this.APP_GRID_BG_BLUR_SIGMA = this.get('appGridBgBlurSigma', true); - this.SMOOTH_BLUR_TRANSITIONS = this.get('smoothBlurTransitions', true); + this.STARTUP_STATE = this.get('startupState'); + this.SHOW_BG_IN_OVERVIEW = this.get('showBgInOverview'); + this.OVERVIEW_BG_BRIGHTNESS = this.get('overviewBgBrightness') / 100; + this.SEARCH_BG_BRIGHTNESS = this.get('searchBgBrightness') / 100; + this.OVERVIEW_BG_BLUR_SIGMA = this.get('overviewBgBlurSigma'); + this.APP_GRID_BG_BLUR_SIGMA = this.get('appGridBgBlurSigma'); + this.SMOOTH_BLUR_TRANSITIONS = this.get('smoothBlurTransitions'); - this.OVERVIEW_MODE = this.get('overviewMode', true); + this.OVERVIEW_MODE = this.get('overviewMode'); this.OVERVIEW_MODE2 = this.OVERVIEW_MODE === 2; this.WORKSPACE_MODE = this.OVERVIEW_MODE ? 0 : 1; - this.STATIC_WS_SWITCHER_BG = this.get('workspaceSwitcherAnimation', true); + this.STATIC_WS_SWITCHER_BG = this.get('workspaceSwitcherAnimation'); - this.ANIMATION_TIME_FACTOR = this.get('animationSpeedFactor', true) / 100; + this.ANIMATION_TIME_FACTOR = this.get('animationSpeedFactor') / 100; - this.SEARCH_ICON_SIZE = this.get('searchIconSize', true); - this.SEARCH_VIEW_SCALE = this.get('searchViewScale', true) / 100; - this.SEARCH_MAX_ROWS = this.get('searchMaxResultsRows', true); - this.SEARCH_FUZZY = this.get('searchFuzzy', true); + this.SEARCH_ICON_SIZE = this.get('searchIconSize'); + this.SEARCH_VIEW_SCALE = this.get('searchViewScale') / 100; + this.SEARCH_MAX_ROWS = this.get('searchMaxResultsRows'); + this.SEARCH_FUZZY = this.get('searchFuzzy'); - this.APP_GRID_ALLOW_INCOMPLETE_PAGES = this.get('appGridIncompletePages', true); - this.APP_GRID_ICON_SIZE = this.get('appGridIconSize', true); - this.APP_GRID_COLUMNS = this.get('appGridColumns', true); - this.APP_GRID_ROWS = this.get('appGridRows', true); + this.APP_GRID_ALLOW_INCOMPLETE_PAGES = this.get('appGridIncompletePages'); + this.APP_GRID_ICON_SIZE = this.get('appGridIconSize'); + this.APP_GRID_COLUMNS = this.get('appGridColumns'); + this.APP_GRID_ROWS = this.get('appGridRows'); this.APP_GRID_ADAPTIVE = !this.APP_GRID_COLUMNS && !this.APP_GRID_ROWS; - this.APP_GRID_ORDER = this.get('appGridOrder', true); - this.APP_GRID_INCLUDE_DASH = this.get('appGridContent', true); + this.APP_GRID_ORDER = this.get('appGridOrder'); + this.APP_GRID_ALPHABET = [1, 2].includes(this.APP_GRID_ORDER); + this.APP_GRID_FOLDERS_FIRST = this.APP_GRID_ORDER === 1; + this.APP_GRID_FOLDERS_LAST = this.APP_GRID_ORDER === 2; + this.APP_GRID_USAGE = this.APP_GRID_ORDER === 3; + + this.APP_FOLDER_ORDER = this.get('appFolderOrder'); + this.APP_FOLDER_ALPHABET = this.APP_FOLDER_ORDER === 1; + this.APP_FOLDER_USAGE = this.APP_FOLDER_ORDER === 2; + + this.APP_GRID_INCLUDE_DASH = this.get('appGridContent'); /* APP_GRID_INCLUDE_DASH 0 - Include All 1 - Include All - Favorites and Runnings First @@ -397,46 +452,50 @@ var Options = class Options { this.APP_GRID_EXCLUDE_RUNNING = this.APP_GRID_INCLUDE_DASH === 3 || this.APP_GRID_INCLUDE_DASH === 4; this.APP_GRID_DASH_FIRST = this.APP_GRID_INCLUDE_DASH === 1; - this.APP_GRID_NAMES_MODE = this.get('appGridNamesMode', true); + this.APP_GRID_NAMES_MODE = this.get('appGridNamesMode'); - this.APP_GRID_FOLDER_ICON_SIZE = this.get('appGridFolderIconSize', true); - this.APP_GRID_FOLDER_ICON_GRID = this.get('appGridFolderIconGrid', true); - this.APP_GRID_FOLDER_COLUMNS = this.get('appGridFolderColumns', true); - this.APP_GRID_FOLDER_ROWS = this.get('appGridFolderRows', true); - this.APP_GRID_SPACING = this.get('appGridSpacing', true); + this.APP_GRID_FOLDER_ICON_SIZE = this.get('appGridFolderIconSize'); + this.APP_GRID_FOLDER_ICON_GRID = this.get('appGridFolderIconGrid'); + this.APP_GRID_FOLDER_COLUMNS = this.get('appGridFolderColumns'); + this.APP_GRID_FOLDER_ROWS = this.get('appGridFolderRows'); + this.APP_GRID_SPACING = this.get('appGridSpacing'); this.APP_GRID_FOLDER_DEFAULT = this.APP_GRID_FOLDER_ROWS === 3 && this.APP_GRID_FOLDER_COLUMNS === 3; - this.APP_GRID_ACTIVE_PREVIEW = this.get('appGridActivePreview', true); - this.APP_GRID_FOLDER_CENTER = this.get('appGridFolderCenter', true); - this.APP_GRID_PAGE_WIDTH_SCALE = this.get('appGridPageWidthScale', true) / 100; + this.APP_GRID_FOLDER_ADAPTIVE = !this.APP_GRID_FOLDER_COLUMNS && !this.APP_GRID_FOLDER_ROWS; + this.APP_GRID_ACTIVE_PREVIEW = this.get('appGridActivePreview'); + this.APP_GRID_FOLDER_CENTER = this.get('appGridFolderCenter'); + this.APP_GRID_PAGE_WIDTH_SCALE = this.get('appGridPageWidthScale') / 100; - this.APP_GRID_ICON_SIZE_DEFAULT = this.APP_GRID_ACTIVE_PREVIEW && !this.APP_GRID_ORDER ? 176 : 96; + this.APP_GRID_ICON_SIZE_DEFAULT = this.APP_GRID_ACTIVE_PREVIEW && !this.APP_GRID_USAGE ? 176 : 96; this.APP_GRID_FOLDER_ICON_SIZE_DEFAULT = 96; - this.WINDOW_SEARCH_PROVIDER_ENABLED = this.get('searchWindowsEnable', true); - this.RECENT_FILES_SEARCH_PROVIDER_ENABLED = this.get('searchRecentFilesEnable', true); + this.APP_GRID_PERFORMANCE = this.get('appGridPerformance'); - this.PANEL_POSITION_TOP = this.get('panelPosition', true) === 0; - this.PANEL_MODE = this.get('panelVisibility', true); + this.WINDOW_SEARCH_ORDER = this.get('searchWindowsOrder'); + + this.PANEL_POSITION_TOP = this.get('panelPosition') === 0; + this.PANEL_MODE = this.get('panelVisibility'); this.PANEL_DISABLED = this.PANEL_MODE === 2; this.PANEL_OVERVIEW_ONLY = this.PANEL_MODE === 1; this.START_Y_OFFSET = 0; // set from main module - this.FIX_UBUNTU_DOCK = this.get('fixUbuntuDock', true); - this.WINDOW_ATTENTION_MODE = this.get('windowAttentionMode', true); + this.WINDOW_ATTENTION_MODE = this.get('windowAttentionMode'); this.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS = this.WINDOW_ATTENTION_MODE === 1; this.WINDOW_ATTENTION_FOCUS_IMMEDIATELY = this.WINDOW_ATTENTION_MODE === 2; - this.WS_SW_POPUP_H_POSITION = this.get('wsSwPopupHPosition', true) / 100; - this.WS_SW_POPUP_V_POSITION = this.get('wsSwPopupVPosition', true) / 100; - this.WS_SW_POPUP_MODE = this.get('wsSwPopupMode', true); + this.WS_SW_POPUP_H_POSITION = this.get('wsSwPopupHPosition') / 100; + this.WS_SW_POPUP_V_POSITION = this.get('wsSwPopupVPosition') / 100; + this.WS_SW_POPUP_MODE = this.get('wsSwPopupMode'); + + this.WS_WRAPAROUND = this.get('wsSwitcherWraparound'); + this.WS_IGNORE_LAST = this.get('wsSwitcherIgnoreLast'); - this.SHOW_FAV_NOTIFICATION = this.get('favoritesNotify', true); - this.NOTIFICATION_POSITION = this.get('notificationPosition', true); + this.SHOW_FAV_NOTIFICATION = this.get('favoritesNotify'); + this.NOTIFICATION_POSITION = this.get('notificationPosition'); - this.OSD_POSITION = this.get('osdPosition', true); + this.OSD_POSITION = this.get('osdPosition'); - this.HOT_CORNER_ACTION = this.get('hotCornerAction', true); - this.HOT_CORNER_POSITION = this.get('hotCornerPosition', true); + this.HOT_CORNER_ACTION = this.get('hotCornerAction'); + this.HOT_CORNER_POSITION = this.get('hotCornerPosition'); if (this.HOT_CORNER_POSITION === 6 && this.DASH_VISIBLE) this.HOT_CORNER_EDGE = true; else @@ -451,13 +510,24 @@ var Options = class Options { else this.HOT_CORNER_POSITION = 0; } - this.HOT_CORNER_FULLSCREEN = this.get('hotCornerFullscreen', true); - this.HOT_CORNER_RIPPLES = this.get('hotCornerRipples', true); + this.HOT_CORNER_FULLSCREEN = this.get('hotCornerFullscreen'); + this.HOT_CORNER_RIPPLES = this.get('hotCornerRipples'); + + this.ALWAYS_ACTIVATE_SELECTED_WINDOW = this.get('alwaysActivateSelectedWindow'); + this.WIN_PREVIEW_SEC_BTN_ACTION = this.get('winPreviewSecBtnAction'); + this.WIN_PREVIEW_MID_BTN_ACTION = this.get('winPreviewMidBtnAction'); + this.SHOW_CLOSE_BUTTON = this.get('winPreviewShowCloseButton'); + this.WINDOW_ICON_CLICK_ACTION = this.get('windowIconClickAction'); + + this.OVERLAY_KEY_PRIMARY = this.get('overlayKeyPrimary'); + this.OVERLAY_KEY_SECONDARY = this.get('overlayKeySecondary'); + + this.ESC_BEHAVIOR = this.get('overviewEscBehavior'); - this.ALWAYS_ACTIVATE_SELECTED_WINDOW = this.get('alwaysActivateSelectedWindow', true); - this.WINDOW_ICON_CLICK_SEARCH = this.get('windowIconClickSearch', true); + this.WINDOW_THUMBNAIL_ENABLED = this.get('windowThumbnailModule'); + this.WINDOW_THUMBNAIL_SCALE = this.get('windowThumbnailScale') / 100; - this.OVERLAY_KEY_SECONDARY = this.get('overlayKeySecondary', true); + this.FIX_NEW_WINDOW_FOCUS = this.get('newWindowFocusFix'); } _getAnimationDirection() { diff --git a/extensions/44/vertical-workspaces/lib/swipeTracker.js b/extensions/44/vertical-workspaces/lib/swipeTracker.js index d9c3407..7122ead 100644 --- a/extensions/44/vertical-workspaces/lib/swipeTracker.js +++ b/extensions/44/vertical-workspaces/lib/swipeTracker.js @@ -10,61 +10,90 @@ 'use strict'; -const { Clutter, GObject } = imports.gi; +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; + const Main = imports.ui.main; const SwipeTracker = imports.ui.swipeTracker; -const Me = imports.misc.extensionUtils.getCurrentExtension(); - +let Me; let opt; -let _firstRun = true; -let _vwGestureUpdateId; -let _originalGestureUpdateId; +var SwipeTrackerModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('swipeTrackerModule', true); - reset = reset || !moduleEnabled; + this._firstActivation = true; + this.moduleEnabled = false; + } - // don't even touch this module if disabled - if (_firstRun && reset) - return; + cleanGlobals() { + Me = null; + opt = null; + } - _firstRun = false; + update(reset) { + this.moduleEnabled = opt.get('swipeTrackerModule'); + const conflict = false; - if (reset || !opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL - // original swipeTrackers' orientation and updateGesture function - Main.overview._swipeTracker.orientation = Clutter.Orientation.VERTICAL; - Main.wm._workspaceAnimation._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL; - Main.overview._swipeTracker._updateGesture = SwipeTracker.SwipeTracker.prototype._updateGesture; - if (_vwGestureUpdateId) { - Main.overview._swipeTracker._touchpadGesture.disconnect(_vwGestureUpdateId); - _vwGestureUpdateId = 0; + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); } - if (_originalGestureUpdateId) { - Main.overview._swipeTracker._touchpadGesture.unblock_signal_handler(_originalGestureUpdateId); - _originalGestureUpdateId = 0; + if (reset && this._firstActivation) + console.debug(' SwipeTrackerModule - Keeping untouched'); + } + + _activateModule() { + if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL + this._setVertical(); + } else { + this._setHorizontal(); } + console.debug(' SwipeTrackerModule - Activated'); + } - opt = null; - return; + _disableModule() { + this._setHorizontal(); + + console.debug(' SwipeTrackerModule - Disabled'); } - if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL + _setVertical() { // reverse swipe gestures for enter/leave overview and ws switching Main.overview._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL; Main.wm._workspaceAnimation._swipeTracker.orientation = Clutter.Orientation.VERTICAL; // overview's updateGesture() function should reflect ws tmb position to match appGrid/ws animation direction // function in connection cannot be overridden in prototype of its class because connected is actually another copy of the original function - if (!_originalGestureUpdateId) { - _originalGestureUpdateId = GObject.signal_handler_find(Main.overview._swipeTracker._touchpadGesture, { signalId: 'update' }); - Main.overview._swipeTracker._touchpadGesture.block_signal_handler(_originalGestureUpdateId); + if (!this._originalGestureUpdateId) { + this._originalGestureUpdateId = GObject.signal_handler_find(Main.overview._swipeTracker._touchpadGesture, { signalId: 'update' }); + Main.overview._swipeTracker._touchpadGesture.block_signal_handler(this._originalGestureUpdateId); Main.overview._swipeTracker._updateGesture = SwipeTrackerVertical._updateGesture; - _vwGestureUpdateId = Main.overview._swipeTracker._touchpadGesture.connect('update', SwipeTrackerVertical._updateGesture.bind(Main.overview._swipeTracker)); + this._vwGestureUpdateId = Main.overview._swipeTracker._touchpadGesture.connect('update', SwipeTrackerVertical._updateGesture.bind(Main.overview._swipeTracker)); + } + } + + _setHorizontal() { + // original swipeTrackers' orientation and updateGesture function + Main.overview._swipeTracker.orientation = Clutter.Orientation.VERTICAL; + Main.wm._workspaceAnimation._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL; + Main.overview._swipeTracker._updateGesture = SwipeTracker.SwipeTracker.prototype._updateGesture; + if (this._vwGestureUpdateId) { + Main.overview._swipeTracker._touchpadGesture.disconnect(this._vwGestureUpdateId); + this._vwGestureUpdateId = 0; + } + if (this._originalGestureUpdateId) { + Main.overview._swipeTracker._touchpadGesture.unblock_signal_handler(this._originalGestureUpdateId); + this._originalGestureUpdateId = 0; } } -} +}; const SwipeTrackerVertical = { _updateGesture(gesture, time, delta, distance) { diff --git a/extensions/44/vertical-workspaces/lib/util.js b/extensions/44/vertical-workspaces/lib/util.js index 5f5c069..9bc4365 100644 --- a/extensions/44/vertical-workspaces/lib/util.js +++ b/extensions/44/vertical-workspaces/lib/util.js @@ -10,16 +10,27 @@ 'use strict'; +const Clutter = imports.gi.Clutter; +const Meta = imports.gi.Meta; const Gi = imports._gi; -const { Shell, Meta, Clutter } = imports.gi; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Shell = imports.gi.Shell; -const Config = imports.misc.config; -const Main = imports.ui.main; +const Main = imports.ui.main; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); +let Me; -var shellVersion = parseFloat(Config.PACKAGE_VERSION); +let _installedExtensions; + +function init(me) { + Me = me; +} + +function cleanGlobals() { + Me = null; + _installedExtensions = null; +} var Overrides = class { constructor() { @@ -27,8 +38,13 @@ var Overrides = class { } addOverride(name, prototype, overrideList) { + const backup = this.overrideProto(prototype, overrideList, name); + // don't update originals when override's just refreshing, keep initial content + let originals = this._overrides[name]?.originals; + if (!originals) + originals = backup; this._overrides[name] = { - originals: this.overrideProto(prototype, overrideList), + originals, prototype, }; } @@ -38,15 +54,15 @@ var Overrides = class { if (!override) return false; - this.overrideProto(override.prototype, override.originals); - this._overrides[name] = undefined; + this.overrideProto(override.prototype, override.originals, name); + delete this._overrides[name]; return true; } removeAll() { for (let name in this._overrides) { this.removeOverride(name); - this._overrides[name] = undefined; + delete this._overrides[name]; } } @@ -54,13 +70,17 @@ var Overrides = class { proto[Gi.hook_up_vfunc_symbol](symbol, func); } - overrideProto(proto, overrides) { + overrideProto(proto, overrides, name) { const backup = {}; - + const originals = this._overrides[name]?.originals; for (let symbol in overrides) { if (symbol.startsWith('after_')) { const actualSymbol = symbol.slice('after_'.length); - const fn = proto[actualSymbol]; + let fn; + if (originals && originals[actualSymbol]) + fn = originals[actualSymbol]; + else + fn = proto[actualSymbol]; const afterFn = overrides[symbol]; proto[actualSymbol] = function (...args) { args = Array.prototype.slice.call(args); @@ -72,11 +92,11 @@ var Overrides = class { } else { backup[symbol] = proto[symbol]; if (symbol.startsWith('vfunc')) { - if (shellVersion < 42) + if (Me.shellVersion < 42) this.hookVfunc(proto, symbol.slice(6), overrides[symbol]); else this.hookVfunc(proto[Gi.gobject_prototype_symbol], symbol.slice(6), overrides[symbol]); - } else { + } else if (overrides[symbol] !== null) { proto[symbol] = overrides[symbol]; } } @@ -85,87 +105,21 @@ var Overrides = class { } }; -function getOverviewTranslations(opt, dash, tmbBox, searchEntryBin) { - // const tmbBox = Main.overview._overview._controls._thumbnailsBox; - let searchTranslationY = 0; - if (searchEntryBin.visible) { - const offset = (dash.visible && (!opt.DASH_VERTICAL ? dash.height + 12 : 0)) + - (opt.WS_TMB_TOP ? tmbBox.height + 12 : 0); - searchTranslationY = -searchEntryBin.height - offset - 30; - } - - let tmbTranslationX = 0; - let tmbTranslationY = 0; - let offset; - if (tmbBox.visible) { - switch (opt.WS_TMB_POSITION) { - case 3: // left - offset = 10 + (dash?.visible && opt.DASH_LEFT ? dash.width : 0); - tmbTranslationX = -tmbBox.width - offset; - tmbTranslationY = 0; - break; - case 1: // right - offset = 10 + (dash?.visible && opt.DASH_RIGHT ? dash.width : 0); - tmbTranslationX = tmbBox.width + offset; - tmbTranslationY = 0; - break; - case 0: // top - offset = 10 + (dash?.visible && opt.DASH_TOP ? dash.height : 0) + Main.panel.height; - tmbTranslationX = 0; - tmbTranslationY = -tmbBox.height - offset; - break; - case 2: // bottom - offset = 10 + (dash?.visible && opt.DASH_BOTTOM ? dash.height : 0) + Main.panel.height; // just for case the panel is at bottom - tmbTranslationX = 0; - tmbTranslationY = tmbBox.height + offset; - break; - } - } - - let dashTranslationX = 0; - let dashTranslationY = 0; - let position = opt.DASH_POSITION; - // if DtD replaced the original Dash, read its position - if (dashIsDashToDock()) - position = dash._position; - - if (dash?.visible) { - switch (position) { - case 0: // top - dashTranslationX = 0; - dashTranslationY = -dash.height - dash.margin_bottom - Main.panel.height; - break; - case 1: // right - dashTranslationX = dash.width; - dashTranslationY = 0; - break; - case 2: // bottom - dashTranslationX = 0; - dashTranslationY = dash.height + dash.margin_bottom + Main.panel.height; - break; - case 3: // left - dashTranslationX = -dash.width; - dashTranslationY = 0; - break; - } - } - - return [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY]; -} - -function openPreferences() { +function openPreferences(metadata) { + if (!metadata) + metadata = Me.metadata; const windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null); let tracker = Shell.WindowTracker.get_default(); let metaWin, isVW = null; for (let win of windows) { const app = tracker.get_window_app(win); - if (win.get_title().includes(Me.metadata.name) && app.get_name() === 'Extensions') { + if (win.get_title()?.includes(metadata.name) && app.get_name() === 'Extensions') { // this is our existing window metaWin = win; isVW = true; break; - } else if (win.wm_class.includes('org.gnome.Shell.Extensions')) { + } else if (win.wm_class?.includes('org.gnome.Shell.Extensions')) { // this is prefs window of another extension metaWin = win; isVW = false; @@ -182,17 +136,20 @@ function openPreferences() { } if (!metaWin || (metaWin && !isVW)) { - try { - Main.extensionManager.openExtensionPrefs(Me.metadata.uuid, '', {}); - } catch (e) { - log(e); - } + GLib.idle_add(GLib.PRIORITY_LOW, () => { + try { + Main.extensionManager.openExtensionPrefs(metadata.uuid, '', {}); + } catch (e) { + console.error(e); + } + }); } } function activateSearchProvider(prefix = '') { const searchEntry = Main.overview.searchEntry; - if (!searchEntry.get_text() || !searchEntry.get_text().startsWith(prefix)) { + const searchEntryText = searchEntry.get_text(); + if (!searchEntryText || (searchEntryText && !searchEntry.get_text().startsWith(prefix))) { prefix = `${prefix} `; const position = prefix.length; searchEntry.set_text(prefix); @@ -220,9 +177,16 @@ function reorderWorkspace(direction = 0) { global.workspace_manager.reorder_workspace(activeWs, targetIdx); } +function activateKeyboardForWorkspaceView() { + Main.ctrlAltTabManager._items.forEach(i => { + if (i.sortGroup === 1 && i.name === 'Windows') + Main.ctrlAltTabManager.focusGroup(i); + }); +} + function exposeWindows(adjustment, activateKeyboard) { // expose windows for static overview modes - if (!adjustment.value && !Main.overview._animationInProgress) { + if (!adjustment.value/* && !Main.overview._animationInProgress*/) { if (adjustment.value === 0) { adjustment.value = 0; adjustment.ease(1, { @@ -330,13 +294,48 @@ function isMoreRelevant(stringA, stringB, pattern) { return !aAny && bAny; } -function getEnabledExtensions(uuid = '') { - let extensions = []; - Main.extensionManager._extensions.forEach(e => { - if (e.state === 1 && e.uuid.includes(uuid)) - extensions.push(e); - }); - return extensions; +function getEnabledExtensions(pattern = '') { + let result = []; + // extensionManager is unreliable at startup (if not all extensions were loaded) + // but gsettings key can contain removed extensions... + // therefore we have to look into filesystem, what's really installed + if (!_installedExtensions) { + const extensionFiles = [...collectFromDatadirs('extensions', true)]; + _installedExtensions = extensionFiles.map(({ info }) => { + let fileType = info.get_file_type(); + if (fileType !== Gio.FileType.DIRECTORY) + return null; + const uuid = info.get_name(); + return uuid; + }); + } + const enabled = Main.extensionManager._enabledExtensions; + result = _installedExtensions.filter(ext => enabled.includes(ext)); + return result.filter(uuid => uuid !== null && uuid.includes(pattern)); +} + +function* collectFromDatadirs(subdir, includeUserDir) { + let dataDirs = GLib.get_system_data_dirs(); + if (includeUserDir) + dataDirs.unshift(GLib.get_user_data_dir()); + + for (let i = 0; i < dataDirs.length; i++) { + let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]); + let dir = Gio.File.new_for_path(path); + + let fileEnum; + try { + fileEnum = dir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + } catch (e) { + fileEnum = null; + } + if (fileEnum !== null) { + let info; + while ((info = fileEnum.next_file(null))) + yield { dir: fileEnum.get_child(info), info }; + } + } } function getScrollDirection(event) { diff --git a/extensions/44/vertical-workspaces/lib/winTmb.js b/extensions/44/vertical-workspaces/lib/winTmb.js new file mode 100644 index 0000000..b18ea18 --- /dev/null +++ b/extensions/44/vertical-workspaces/lib/winTmb.js @@ -0,0 +1,525 @@ +/** + * V-Shell (Vertical Workspaces) + * WinTmb + * + * @author GdH <G-dH@github.com> + * @copyright 2021-2023 + * @license GPL-3.0 + */ + +'use strict'; + +const Clutter = imports.gi.Clutter; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const St = imports.gi.St; + +const AltTab = imports.ui.altTab; +const DND = imports.ui.dnd; +const Main = imports.ui.main; + +let Me; +let opt; + +const SCROLL_ICON_OPACITY = 240; +const DRAG_OPACITY = 200; +const CLOSE_BTN_OPACITY = 240; + + +var WinTmbModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + + this._firstActivation = true; + this.moduleEnabled = false; + } + + cleanGlobals() { + Me = null; + opt = null; + } + + update(reset) { + this._removeTimeouts(); + + this.moduleEnabled = opt.get('windowThumbnailModule'); + + reset = reset || !this.moduleEnabled; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WinTmb - Keeping untouched'); + } + + _activateModule() { + this._timeouts = {}; + if (!this._windowThumbnails) + this._windowThumbnails = []; + + Main.overview.connectObject('hidden', () => this.showThumbnails(), this); + console.debug(' WinTmb - Activated'); + } + + _disableModule() { + Main.overview.disconnectObject(this); + this._disconnectStateAdjustment(); + this.removeAllThumbnails(); + console.debug(' WinTmb - Disabled'); + } + + _removeTimeouts() { + if (this._timeouts) { + Object.values(this._timeouts).forEach(t => { + if (t) + GLib.source_remove(t); + }); + this._timeouts = null; + } + } + + createThumbnail(metaWin) { + const thumbnail = new WindowThumbnail(metaWin, { + 'height': Math.floor(opt.WINDOW_THUMBNAIL_SCALE * global.display.get_monitor_geometry(global.display.get_current_monitor()).height), + 'thumbnailsOnScreen': this._windowThumbnails.length, + }); + + this._windowThumbnails.push(thumbnail); + thumbnail.connect('removed', tmb => { + this._windowThumbnails.splice(this._windowThumbnails.indexOf(tmb), 1); + tmb.destroy(); + if (!this._windowThumbnails.length) + this._disconnectStateAdjustment(); + }); + + if (!this._stateAdjustmentConId) { + this._stateAdjustmentConId = Main.overview._overview.controls._stateAdjustment.connectObject('notify::value', () => { + if (!this._thumbnailsHidden && (!opt.OVERVIEW_MODE2 || opt.WORKSPACE_MODE)) + this.hideThumbnails(); + }, this); + } + } + + hideThumbnails() { + this._windowThumbnails.forEach(tmb => { + tmb.ease({ + opacity: 0, + duration: 200, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => tmb.hide(), + }); + }); + this._thumbnailsHidden = true; + } + + showThumbnails() { + this._windowThumbnails.forEach(tmb => { + tmb.show(); + tmb.ease({ + opacity: 255, + duration: 100, + mode: Clutter.AnimationMode.LINEAR, + }); + }); + this._thumbnailsHidden = false; + } + + removeAllThumbnails() { + this._windowThumbnails.forEach(tmb => tmb.remove()); + this._windowThumbnails = []; + } + + _disconnectStateAdjustment() { + Main.overview._overview.controls._stateAdjustment.disconnectObject(this); + } +}; + +const WindowThumbnail = GObject.registerClass({ + Signals: { 'removed': {} }, +}, class WindowThumbnail extends St.Widget { + _init(metaWin, args) { + this._hoverShowsPreview = false; + this._customOpacity = 255; + this._initTmbHeight = args.height; + this._minimumHeight = Math.floor(5 / 100 * global.display.get_monitor_geometry(global.display.get_current_monitor()).height); + this._scrollTimeout = 100; + this._positionOffset = args.thumbnailsOnScreen; + this._reverseTmbWheelFunc = false; + this._click_count = 1; + this._prevBtnPressTime = 0; + this.w = metaWin; + super._init({ + layout_manager: new Clutter.BinLayout(), + visible: true, + reactive: true, + can_focus: true, + track_hover: true, + }); + this.connect('button-release-event', this._onBtnReleased.bind(this)); + this.connect('scroll-event', this._onScrollEvent.bind(this)); + // this.connect('motion-event', this._onMouseMove.bind(this)); // may be useful in the future.. + + this._delegate = this; + this._draggable = DND.makeDraggable(this, { dragActorOpacity: DRAG_OPACITY }); + this._draggable.connect('drag-end', this._end_drag.bind(this)); + this._draggable.connect('drag-cancelled', this._end_drag.bind(this)); + this._draggable._animateDragEnd = eventTime => { + this._draggable._animationInProgress = true; + this._draggable._onAnimationComplete(this._draggable._dragActor, eventTime); + this.opacity = this._customOpacity; + }; + + this.clone = new Clutter.Clone({ reactive: true }); + Main.layoutManager.addChrome(this); + + this.window = this.w.get_compositor_private(); + + this.clone.set_source(this.window); + + this.add_child(this.clone); + this._addCloseButton(); + this._addScrollModeIcon(); + + this.connect('enter-event', () => { + global.display.set_cursor(Meta.Cursor.POINTING_HAND); + this._closeButton.opacity = CLOSE_BTN_OPACITY; + this._scrollModeBin.opacity = SCROLL_ICON_OPACITY; + if (this._hoverShowsPreview && !Main.overview._shown) { + this._closeButton.opacity = 50; + this._showWindowPreview(false, true); + } + }); + + this.connect('leave-event', () => { + global.display.set_cursor(Meta.Cursor.DEFAULT); + this._closeButton.opacity = 0; + this._scrollModeBin.opacity = 0; + if (this._winPreview) + this._destroyWindowPreview(); + }); + + this._setSize(true); + this.set_position(...this._getInitialPosition()); + this.show(); + this.window_id = this.w.get_id(); + this.tmbRedrawDirection = true; + + // remove thumbnail content and hide thumbnail if its window is destroyed + this.windowConnect = this.window.connect('destroy', () => { + if (this) + this.remove(); + }); + } + + _getInitialPosition() { + const offset = 20; + let monitor = Main.layoutManager.monitors[global.display.get_current_monitor()]; + let x = Math.min(monitor.x + monitor.width - (this.window.width * this.scale) - offset); + let y = Math.min(monitor.y + monitor.height - (this.window.height * this.scale) - offset - ((this._positionOffset * this._initTmbHeight) % (monitor.height - this._initTmbHeight))); + return [x, y]; + } + + _setSize(resetScale = false) { + if (resetScale) + this.scale = Math.min(1.0, this._initTmbHeight / this.window.height); + + const width = this.window.width * this.scale; + const height = this.window.height * this.scale; + this.set_size(width, height); + if (this.icon) { + this.icon.scale_x = this.scale; + this.icon.scale_y = this.scale; + } + + // when the scale of this. actor change, this.clone resize accordingly, + // but the reactive area of the actor doesn't change until the actor is redrawn + // this updates the actor's input region area: + Main.layoutManager._queueUpdateRegions(); + } + + /* _onMouseMove(actor, event) { + let [pos_x, pos_y] = event.get_coords(); + let state = event.get_state(); + if (this._ctrlPressed(state)) { + } + }*/ + + _onBtnReleased(actor, event) { + // Clutter.Event.click_count property in no longer available, since GS42 + if ((event.get_time() - this._prevBtnPressTime) < Clutter.Settings.get_default().double_click_time) + this._click_count += 1; + else + this._click_count = 1; + + this._prevBtnPressTime = event.get_time(); + + if (this._click_count === 2 && event.get_button() === Clutter.BUTTON_PRIMARY) + this.w.activate(global.get_current_time()); + + + const button = event.get_button(); + const state = event.get_state(); + switch (button) { + case Clutter.BUTTON_PRIMARY: + if (this._ctrlPressed(state)) { + this._setSize(); + } else { + this._reverseTmbWheelFunc = !this._reverseTmbWheelFunc; + this._scrollModeBin.set_child(this._reverseTmbWheelFunc ? this._scrollModeSourceIcon : this._scrollModeResizeIcon); + } + return Clutter.EVENT_STOP; + case Clutter.BUTTON_SECONDARY: + if (this._ctrlPressed(state)) { + this.remove(); + } else { + this._hoverShowsPreview = !this._hoverShowsPreview; + this._showWindowPreview(); + } + return Clutter.EVENT_STOP; + case Clutter.BUTTON_MIDDLE: + if (this._ctrlPressed(state)) + this.w.delete(global.get_current_time()); + return Clutter.EVENT_STOP; + default: + return Clutter.EVENT_PROPAGATE; + } + } + + _onScrollEvent(actor, event) { + let direction = Me.Util.getScrollDirection(event); + + if (this._actionTimeoutActive()) + return Clutter.EVENT_PROPAGATE; + let state = event.get_state(); + switch (direction) { + case Clutter.ScrollDirection.UP: + if (this._shiftPressed(state)) { + this.opacity = Math.min(255, this.opacity + 24); + this._customOpacity = this.opacity; + } else if (this._reverseTmbWheelFunc !== this._ctrlPressed(state)) { + this._switchSourceWin(-1); + } else if (this._reverseTmbWheelFunc === this._ctrlPressed(state)) { + this.scale = Math.max(0.05, this.scale - 0.025); + } + break; + case Clutter.ScrollDirection.DOWN: + if (this._shiftPressed(state)) { + this.opacity = Math.max(48, this.opacity - 24); + this._customOpacity = this.opacity; + } else if (this._reverseTmbWheelFunc !== this._ctrlPressed(state)) { + this._switchSourceWin(+1); + } else if (this._reverseTmbWheelFunc === this._ctrlPressed(state)) { + this.scale = Math.min(1, this.scale + 0.025); + } + break; + default: + return Clutter.EVENT_PROPAGATE; + } + this._setSize(); + return Clutter.EVENT_STOP; + } + + remove() { + if (this.clone) { + this.window.disconnect(this.windowConnect); + this.clone.set_source(null); + } + if (this._winPreview) + this._destroyWindowPreview(); + + this.emit('removed'); + } + + _end_drag() { + this.set_position(this._draggable._dragOffsetX + this._draggable._dragX, this._draggable._dragOffsetY + this._draggable._dragY); + this._setSize(); + } + + _ctrlPressed(state) { + return (state & Clutter.ModifierType.CONTROL_MASK) !== 0; + } + + _shiftPressed(state) { + return (state & Clutter.ModifierType.SHIFT_MASK) !== 0; + } + + _switchSourceWin(direction) { + let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null); + windows = windows.filter(w => !(w.skip_taskbar || w.minimized)); + let idx = -1; + for (let i = 0; i < windows.length; i++) { + if (windows[i] === this.w) { + idx = i + direction; + break; + } + } + idx = idx >= windows.length ? 0 : idx; + idx = idx < 0 ? windows.length - 1 : idx; + let w = windows[idx]; + let win = w.get_compositor_private(); + this.clone.set_source(win); + this.window.disconnect(this.windowConnect); + // the new thumbnail should be the same height as the previous one + this.scale = (this.scale * this.window.height) / win.height; + this.window = win; + this.windowConnect = this.window.connect('destroy', () => { + if (this) + this.remove(); + }); + this.w = w; + + if (this._winPreview) + this._showWindowPreview(true); + } + + _actionTimeoutActive() { + const timeout = this._reverseTmbWheelFunc ? this._scrollTimeout : this._scrollTimeout / 4; + if (!this._lastActionTime || Date.now() - this._lastActionTime > timeout) { + this._lastActionTime = Date.now(); + return false; + } + return true; + } + + /* _setIcon() { + let tracker = Shell.WindowTracker.get_default(); + let app = tracker.get_window_app(this.w); + let icon = app + ? app.create_icon_texture(this.height) + : new St.Icon({ icon_name: 'icon-missing', icon_size: this.height }); + icon.x_expand = icon.y_expand = true; + if (this.icon) + this.icon.destroy(); + this.icon = icon; + }*/ + + _addCloseButton() { + const closeButton = new St.Button({ + opacity: 0, + style_class: 'window-close', + child: new St.Icon({ icon_name: 'preview-close-symbolic' }), + x_align: Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + }); + + closeButton.set_style(` + margin: 3px; + background-color: rgba(200, 0, 0, 0.9); + `); + + closeButton.connect('clicked', () => { + this.remove(); + return Clutter.EVENT_STOP; + }); + + this._closeButton = closeButton; + this.add_child(this._closeButton); + } + + _addScrollModeIcon() { + this._scrollModeBin = new St.Bin({ + x_expand: true, + y_expand: true, + }); + this._scrollModeResizeIcon = new St.Icon({ + icon_name: 'view-fullscreen-symbolic', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true, + opacity: SCROLL_ICON_OPACITY, + style_class: 'icon-dropshadow', + scale_x: 0.5, + scale_y: 0.5, + }); + this._scrollModeResizeIcon.set_style(` + margin: 13px; + color: rgb(255, 255, 255); + box-shadow: 0 0 40px 40px rgba(0,0,0,0.7); + `); + this._scrollModeSourceIcon = new St.Icon({ + icon_name: 'media-skip-forward-symbolic', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.END, + x_expand: true, + y_expand: true, + opacity: SCROLL_ICON_OPACITY, + style_class: 'icon-dropshadow', + scale_x: 0.5, + scale_y: 0.5, + }); + this._scrollModeSourceIcon.set_style(` + margin: 13px; + color: rgb(255, 255, 255); + box-shadow: 0 0 40px 40px rgba(0,0,0,0.7); + `); + this._scrollModeBin.set_child(this._scrollModeResizeIcon); + this.add_child(this._scrollModeBin); + this._scrollModeBin.opacity = 0; + } + + _showWindowPreview(update = false, dontDestroy = false) { + if (this._winPreview && !dontDestroy) { + this._destroyWindowPreview(); + this._previewCreationTime = 0; + this._closeButton.opacity = CLOSE_BTN_OPACITY; + if (!update) + return; + } + + if (!this._winPreview) { + this._winPreview = new AltTab.CyclerHighlight(); + global.window_group.add_actor(this._winPreview); + [this._winPreview._xPointer, this._winPreview._yPointer] = global.get_pointer(); + } + + if (!update) { + this._winPreview.opacity = 0; + this._winPreview.ease({ + opacity: 255, + duration: 70, + mode: Clutter.AnimationMode.LINEAR, + /* onComplete: () => { + this._closeButton.opacity = 50; + },*/ + }); + + this.ease({ + opacity: Math.min(50, this._customOpacity), + duration: 70, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => { + }, + }); + } else { + this._winPreview.opacity = 255; + } + this._winPreview.window = this.w; + this._winPreview._window = this.w; + global.window_group.set_child_above_sibling(this._winPreview, null); + } + + _destroyWindowPreview() { + if (this._winPreview) { + this._winPreview.ease({ + opacity: 0, + duration: 100, + mode: Clutter.AnimationMode.LINEAR, + onComplete: () => { + this._winPreview.destroy(); + this._winPreview = null; + this.opacity = this._customOpacity; + }, + }); + } + } +}); diff --git a/extensions/44/vertical-workspaces/lib/windowAttentionHandler.js b/extensions/44/vertical-workspaces/lib/windowAttentionHandler.js index 10703c2..a3db986 100644 --- a/extensions/44/vertical-workspaces/lib/windowAttentionHandler.js +++ b/extensions/44/vertical-workspaces/lib/windowAttentionHandler.js @@ -11,47 +11,69 @@ 'use strict'; const Main = imports.ui.main; -const WindowAttentionHandler = imports.ui.windowAttentionHandler; const MessageTray = imports.ui.messageTray; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); - -const _Util = Me.imports.lib.util; +const WindowAttentionHandler = imports.ui.windowAttentionHandler; +let Me; let opt; -let _firstRun = false; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('winAttentionHandlerModule', true); - reset = reset || !moduleEnabled; +var WindowAttentionHandlerModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - _firstRun = false; - if (reset) { - reset = true; - _updateConnections(reset); + cleanGlobals() { + Me = null; opt = null; - return; } - _updateConnections(); -} + update(reset) { + this.moduleEnabled = opt.get('windowAttentionHandlerModule'); + const conflict = false; -function _updateConnections(reset) { - global.display.disconnectObject(Main.windowAttentionHandler); + reset = reset || !this.moduleEnabled || conflict; - const handlerFnc = reset - ? Main.windowAttentionHandler._onWindowDemandsAttention - : WindowAttentionHandlerCommon._onWindowDemandsAttention; + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WindowAttentionHandlerModule - Keeping untouched'); + } + + _activateModule() { + this._updateConnections(); + console.debug(' WindowAttentionHandlerModule - Activated'); + } - global.display.connectObject( - 'window-demands-attention', handlerFnc.bind(Main.windowAttentionHandler), - 'window-marked-urgent', handlerFnc.bind(Main.windowAttentionHandler), - Main.windowAttentionHandler); -} + _disableModule() { + const reset = true; + this._updateConnections(reset); + + console.debug(' WindowAttentionHandlerModule - Disabled'); + } + + _updateConnections(reset) { + global.display.disconnectObject(Main.windowAttentionHandler); + + const handlerFnc = reset + ? Main.windowAttentionHandler._onWindowDemandsAttention + : WindowAttentionHandlerCommon._onWindowDemandsAttention; + + global.display.connectObject( + 'window-demands-attention', handlerFnc.bind(Main.windowAttentionHandler), + 'window-marked-urgent', handlerFnc.bind(Main.windowAttentionHandler), + Main.windowAttentionHandler); + } +}; const WindowAttentionHandlerCommon = { _onWindowDemandsAttention(display, window) { diff --git a/extensions/44/vertical-workspaces/lib/windowManager.js b/extensions/44/vertical-workspaces/lib/windowManager.js index 2d46b0b..0cae6aa 100644 --- a/extensions/44/vertical-workspaces/lib/windowManager.js +++ b/extensions/44/vertical-workspaces/lib/windowManager.js @@ -10,81 +10,101 @@ 'use strict'; -const { GObject, Clutter, Meta } = imports.gi; +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; const Main = imports.ui.main; const WindowManager = imports.ui.windowManager; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; -let _overrides; - -const MINIMIZE_WINDOW_ANIMATION_TIME = WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME; -const MINIMIZE_WINDOW_ANIMATION_MODE = WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE; +let Me; let opt; -let _firstRun = true; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('windowManagerModule', true); - reset = reset || !moduleEnabled; +var WindowManagerModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - // don't even touch this module if disabled - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; - _firstRun = false; + this._originalMinimizeSigId = 0; + this._minimizeSigId = 0; + this._originalUnminimizeSigId = 0; + this._unminimizeSigId = 0; + } - if (_overrides) - _overrides.removeAll(); + cleanGlobals() { + Me = null; + opt = null; + } + update(reset) { + this.moduleEnabled = opt.get('windowManagerModule'); + const conflict = false; - _replaceMinimizeFunction(reset); + reset = reset || !this.moduleEnabled || conflict; + // don't even touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WindowManagerModule - Keeping untouched'); + } - if (reset) { - _overrides = null; - opt = null; - return; + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + + this._overrides.addOverride('WindowManager', WindowManager.WindowManager.prototype, WindowManagerCommon); + + if (!this._minimizeSigId) { + this._originalMinimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'minimize' }); + if (this._originalMinimizeSigId) { + Main.wm._shellwm.block_signal_handler(this._originalMinimizeSigId); + this._minimizeSigId = Main.wm._shellwm.connect('minimize', WindowManagerCommon._minimizeWindow.bind(Main.wm)); + } + + this._originalUnminimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'unminimize' }); + if (this._originalUnminimizeSigId) { + Main.wm._shellwm.block_signal_handler(this._originalUnminimizeSigId); + this._unminimizeSigId = Main.wm._shellwm.connect('unminimize', WindowManagerCommon._unminimizeWindow.bind(Main.wm)); + } + } + console.debug(' WindowManagerModule - Activated'); } - _overrides = new _Util.Overrides(); - - _overrides.addOverride('WindowManager', WindowManager.WindowManager.prototype, WindowManagerCommon); -} - -// ------------- Fix and adapt minimize/unminimize animations -------------------------------------- - -let _originalMinimizeSigId; -let _minimizeSigId; -let _originalUnminimizeSigId; -let _unminimizeSigId; - -function _replaceMinimizeFunction(reset = false) { - if (reset) { - Main.wm._shellwm.disconnect(_minimizeSigId); - _minimizeSigId = 0; - Main.wm._shellwm.unblock_signal_handler(_originalMinimizeSigId); - _originalMinimizeSigId = 0; - - Main.wm._shellwm.disconnect(_unminimizeSigId); - _unminimizeSigId = 0; - Main.wm._shellwm.unblock_signal_handler(_originalUnminimizeSigId); - _originalUnminimizeSigId = 0; - } else if (!_minimizeSigId) { - _originalMinimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'minimize' }); - if (_originalMinimizeSigId) { - Main.wm._shellwm.block_signal_handler(_originalMinimizeSigId); - _minimizeSigId = Main.wm._shellwm.connect('minimize', WindowManagerCommon._minimizeWindow.bind(Main.wm)); + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + if (this._minimizeSigId) { + Main.wm._shellwm.disconnect(this._minimizeSigId); + this._minimizeSigId = 0; + } + if (this._originalMinimizeSigId) { + Main.wm._shellwm.unblock_signal_handler(this._originalMinimizeSigId); + this._originalMinimizeSigId = 0; } - _originalUnminimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'unminimize' }); - if (_originalUnminimizeSigId) { - Main.wm._shellwm.block_signal_handler(_originalUnminimizeSigId); - _unminimizeSigId = Main.wm._shellwm.connect('unminimize', WindowManagerCommon._unminimizeWindow.bind(Main.wm)); + if (this._unminimizeSigId) { + Main.wm._shellwm.disconnect(this._unminimizeSigId); + this._unminimizeSigId = 0; + } + if (this._originalUnminimizeSigId) { + Main.wm._shellwm.unblock_signal_handler(this._originalUnminimizeSigId); + this._originalUnminimizeSigId = 0; } + + console.debug(' WindowManagerModule - Disabled'); } -} +}; // fix for mainstream bug - fullscreen windows should minimize using opacity transition // but its being applied directly on window actor and that doesn't work @@ -109,8 +129,8 @@ const WindowManagerCommon = { /* if (actor.meta_window.is_monitor_sized()) { actor.get_first_child().ease({ opacity: 0, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, + duration: WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME, + mode: WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._minimizeWindowDone(shellwm, actor), }); } else { */ @@ -140,8 +160,8 @@ const WindowManagerCommon = { scale_y: yScale, x: xDest, y: yDest, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, + duration: WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME, + mode: WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._minimizeWindowDone(shellwm, actor), }); // } @@ -176,8 +196,8 @@ const WindowManagerCommon = { actor.set_scale(1.0, 1.0); actor.ease({ opacity: 255, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, + duration: WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME, + mode: WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._unminimizeWindowDone(shellwm, actor), }); } else { */ @@ -208,8 +228,8 @@ const WindowManagerCommon = { scale_y: 1, x: xDest, y: yDest, - duration: MINIMIZE_WINDOW_ANIMATION_TIME, - mode: MINIMIZE_WINDOW_ANIMATION_MODE, + duration: WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME, + mode: WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE, onStopped: () => this._unminimizeWindowDone(shellwm, actor), }); // } diff --git a/extensions/44/vertical-workspaces/lib/windowPreview.js b/extensions/44/vertical-workspaces/lib/windowPreview.js index 5d2bd61..2766138 100644 --- a/extensions/44/vertical-workspaces/lib/windowPreview.js +++ b/extensions/44/vertical-workspaces/lib/windowPreview.js @@ -10,103 +10,372 @@ 'use strict'; -const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; - +const Clutter = imports.gi.Clutter; +const Atk = imports.gi.Atk; +const GLib = imports.gi.GLib; +const Graphene = imports.gi.Graphene; +const Meta = imports.gi.Meta; +const Pango = imports.gi.Pango; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const DND = imports.ui.dnd; const Main = imports.ui.main; +const OverviewControls = imports.ui.overviewControls; const WindowPreview = imports.ui.windowPreview; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); - -const _Util = Me.imports.lib.util; -const shellVersion = _Util.shellVersion; +let Me; +let opt; -let _overrides; +const WINDOW_SCALE_TIME = 200; +const WINDOW_ACTIVE_SIZE_INC = 5; +const WINDOW_OVERLAY_FADE_TIME = 200; +const WINDOW_DND_SIZE = 256; +const DRAGGING_WINDOW_OPACITY = 100; -const WINDOW_SCALE_TIME = imports.ui.windowPreview.WINDOW_SCALE_TIME; -const WINDOW_ACTIVE_SIZE_INC = imports.ui.windowPreview.WINDOW_ACTIVE_SIZE_INC; -const WINDOW_OVERLAY_FADE_TIME = imports.ui.windowPreview.WINDOW_OVERLAY_FADE_TIME; -const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix; +const ControlsState = OverviewControls.ControlsState; -const ControlsState = imports.ui.overviewControls.ControlsState; +var WindowPreviewModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -let opt; -let _firstRun = true; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('windowPreviewModule', true); - reset = reset || !moduleEnabled; + cleanGlobals() { + Me = null; + opt = null; + } - // don't even touch this module if disabled - if (_firstRun && reset) - return; + update(reset) { + this.moduleEnabled = opt.get('windowPreviewModule'); + const conflict = false; - _firstRun = false; + reset = reset || !this.moduleEnabled || conflict; - if (_overrides) - _overrides.removeAll(); + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WindowPreviewModule - Keeping untouched'); + } + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - if (reset) { - _overrides = null; - opt = null; - WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750; - return; + this._overrides.addOverride('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewCommon); + // A shorter timeout allows user to quickly cancel the selection by leaving the preview with the mouse pointer + if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 150; + console.debug(' WindowPreviewModule - Activated'); } - _overrides = new _Util.Overrides(); + _disableModule() { + // If WindowPreview._init was injected by another extension (like Burn My Windows) + // which enables/disables before V-Shell + // don't restore the original if it's not injected, + // because it would restore injected _init and recursion would freeze GS when extensions are enabled again. + // This can happen when all extension re-enabled, not only when screen is locked/unlocked + // If _init doesn't include "fn.apply(this, args)" when reset === true, some extension already restored the original + const skipReset = WindowPreview.WindowPreview.prototype._init.toString().includes('fn.apply(this, args)'); + if (this._overrides && skipReset) { + // skip restoring original _init() + this._overrides['_init'] = null; + } + + if (this._overrides) + this._overrides.removeAll(); - _overrides.addOverride('WindowPreview', WindowPreview.WindowPreview.prototype, WindowPreviewCommon); - // A shorter timeout allows user to quickly cancel the selection by leaving the preview with the mouse pointer - if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) - WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 150; -} + this._overrides = null; + + console.debug(' WindowPreviewModule - Disabled'); + } +}; const WindowPreviewCommon = { - // injection to _init() - after__init() { + _init(metaWindow, workspace, overviewAdjustment) { + this.metaWindow = metaWindow; + this.metaWindow._delegate = this; + this._windowActor = metaWindow.get_compositor_private(); + this._workspace = workspace; + this._overviewAdjustment = overviewAdjustment; + + const ICON_SIZE = opt.WIN_PREVIEW_ICON_SIZE; const ICON_OVERLAP = 0.7; - if (opt.WIN_PREVIEW_ICON_SIZE < 64) { - this.remove_child(this._icon); - this._icon.destroy(); - const tracker = Shell.WindowTracker.get_default(); - const app = tracker.get_window_app(this.metaWindow); - this._icon = app.create_icon_texture(opt.WIN_PREVIEW_ICON_SIZE); - this._icon.add_style_class_name('icon-dropshadow'); - this._icon.set({ - reactive: true, - pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), + Shell.WindowPreview.prototype._init.bind(this)({ + reactive: true, + can_focus: true, + accessible_role: Atk.Role.PUSH_BUTTON, + offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY, + }); + + const windowContainer = new Clutter.Actor({ + pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), + }); + this.window_container = windowContainer; + + windowContainer.connect('notify::scale-x', + () => this._adjustOverlayOffsets()); + // gjs currently can't handle setting an actors layout manager during + // the initialization of the actor if that layout manager keeps track + // of its container, so set the layout manager after creating the + // container + windowContainer.layout_manager = new Shell.WindowPreviewLayout(); + this.add_child(windowContainer); + + this._addWindow(metaWindow); + + this._delegate = this; + + this._stackAbove = null; + + this._cachedBoundingBox = { + x: windowContainer.layout_manager.bounding_box.x1, + y: windowContainer.layout_manager.bounding_box.y1, + width: windowContainer.layout_manager.bounding_box.get_width(), + height: windowContainer.layout_manager.bounding_box.get_height(), + }; + + windowContainer.layout_manager.connect( + 'notify::bounding-box', layout => { + this._cachedBoundingBox = { + x: layout.bounding_box.x1, + y: layout.bounding_box.y1, + width: layout.bounding_box.get_width(), + height: layout.bounding_box.get_height(), + }; + + // A bounding box of 0x0 means all windows were removed + if (layout.bounding_box.get_area() > 0) + this.emit('size-changed'); }); - this._icon.add_constraint(new Clutter.BindConstraint({ - source: this.windowContainer, - coordinate: Clutter.BindCoordinate.POSITION, - })); - this._icon.add_constraint(new Clutter.AlignConstraint({ - source: this.windowContainer, - align_axis: Clutter.AlignAxis.X_AXIS, - factor: 0.5, - })); - this._icon.add_constraint(new Clutter.AlignConstraint({ - source: this.windowContainer, - align_axis: Clutter.AlignAxis.Y_AXIS, - pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }), - factor: 1, - })); - this.add_child(this._icon); - if (opt.WIN_PREVIEW_ICON_SIZE < 22) { - // disable app icon - this._icon.hide(); + + this._windowActor.connectObject('destroy', () => this.destroy(), this); + + this._updateAttachedDialogs(); + + let clickAction = new Clutter.ClickAction(); + clickAction.connect('clicked', act => { + const button = act.get_button(); + if (button === Clutter.BUTTON_SECONDARY) { + if (opt.WIN_PREVIEW_SEC_BTN_ACTION === 1) { + this._closeWinAction(); + return Clutter.EVENT_STOP; + } else if (opt.WIN_PREVIEW_SEC_BTN_ACTION === 2) { + this._searchAppWindowsAction(); + return Clutter.EVENT_STOP; + } else if (opt.WIN_PREVIEW_SEC_BTN_ACTION === 3 && opt.WINDOW_THUMBNAIL_ENABLED) { + this._removeLaters(); + Me.Modules.winTmbModule.createThumbnail(metaWindow); + return Clutter.EVENT_STOP; + } + } else if (button === Clutter.BUTTON_MIDDLE) { + if (opt.WIN_PREVIEW_MID_BTN_ACTION === 1) { + this._closeWinAction(); + return Clutter.EVENT_STOP; + } else if (opt.WIN_PREVIEW_MID_BTN_ACTION === 2) { + this._searchAppWindowsAction(); + return Clutter.EVENT_STOP; + } else if (opt.WIN_PREVIEW_MID_BTN_ACTION === 3 && opt.WINDOW_THUMBNAIL_ENABLED) { + this._removeLaters(); + Me.Modules.winTmbModule.createThumbnail(metaWindow); + return Clutter.EVENT_STOP; + } } - this._iconSize = opt.WIN_PREVIEW_ICON_SIZE; + return this._activate(); + }); + + + if (this._onLongPress) { + clickAction.connect('long-press', this._onLongPress.bind(this)); + } else { + clickAction.connect('long-press', (action, actor, state) => { + if (state === Clutter.LongPressState.ACTIVATE) + this.showOverlay(true); + return true; + }); } + this.connect('destroy', this._onDestroy.bind(this)); + + this._draggable = DND.makeDraggable(this, { + restoreOnSuccess: true, + manualMode: !!this._onLongPress, + dragActorMaxSize: WINDOW_DND_SIZE, + dragActorOpacity: DRAGGING_WINDOW_OPACITY, + }); + + // _draggable.addClickAction is new in GS45 + if (this._draggable.addClickAction) + this._draggable.addClickAction(clickAction); + else + this.add_action(clickAction); + + this._draggable.connect('drag-begin', this._onDragBegin.bind(this)); + this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this)); + this._draggable.connect('drag-end', this._onDragEnd.bind(this)); + this.inDrag = false; + + this._selected = false; + this._overlayEnabled = true; + this._overlayShown = false; + this._closeRequested = false; + this._idleHideOverlayId = 0; + + const tracker = Shell.WindowTracker.get_default(); + const app = tracker.get_window_app(this.metaWindow); + this._icon = app.create_icon_texture(ICON_SIZE); + this._icon.add_style_class_name('icon-dropshadow'); + this._icon.set({ + reactive: true, + pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }), + }); + this._icon.add_constraint(new Clutter.BindConstraint({ + source: windowContainer, + coordinate: Clutter.BindCoordinate.POSITION, + })); + this._icon.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5, + })); + this._icon.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.Y_AXIS, + pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }), + factor: 1, + })); + + if (opt.WINDOW_ICON_CLICK_ACTION) { + const iconClickAction = new Clutter.ClickAction(); + iconClickAction.connect('clicked', act => { + if (act.get_button() === Clutter.BUTTON_PRIMARY) { + if (opt.WINDOW_ICON_CLICK_ACTION === 1) { + this._searchAppWindowsAction(); + return Clutter.EVENT_STOP; + } else if (opt.WINDOW_ICON_CLICK_ACTION === 2 && opt.WINDOW_THUMBNAIL_ENABLED) { + this._removeLaters(); + Me.Modules.winTmbModule.createThumbnail(metaWindow); + return Clutter.EVENT_STOP; + } + } /* else if (act.get_button() === Clutter.BUTTON_SECONDARY) { + return Clutter.EVENT_STOP; + }*/ + return Clutter.EVENT_PROPAGATE; + }); + this._icon.add_action(iconClickAction); + } const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); - const iconOverlap = opt.WIN_PREVIEW_ICON_SIZE * ICON_OVERLAP; - // we cannot get proper title height before it gets to the stage, so 35 is estimated height + spacing - this._title.get_constraints()[1].offset = scaleFactor * (-iconOverlap - 35); - this.set_child_above_sibling(this._title, null); + this._title = new St.Label({ + visible: false, + style_class: 'window-caption', + text: this._getCaption(), + reactive: true, + }); + this._title.clutter_text.single_line_mode = true; + this._title.add_constraint(new Clutter.BindConstraint({ + source: windowContainer, + coordinate: Clutter.BindCoordinate.X, + })); + + let offset; + if (opt.WIN_TITLES_POSITION < 2) { + // we cannot get proper title height before it gets to the stage, so 35 is estimated height + spacing + offset = -scaleFactor * (ICON_SIZE * ICON_OVERLAP + 35); + } else { + offset = scaleFactor * (ICON_SIZE * (1 - ICON_OVERLAP) + 4); + } + this._title.add_constraint(new Clutter.BindConstraint({ + source: windowContainer, + coordinate: Clutter.BindCoordinate.Y, + offset, + })); + this._title.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.X_AXIS, + factor: 0.5, + })); + this._title.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.Y_AXIS, + pivot_point: new Graphene.Point({ x: -1, y: 0 }), + factor: 1, + })); + this._title.clutter_text.ellipsize = Pango.EllipsizeMode.END; + this.label_actor = this._title; + this.metaWindow.connectObject( + 'notify::title', () => (this._title.text = this._getCaption()), + this); + + const layout = Meta.prefs_get_button_layout(); + this._closeButtonSide = + layout.left_buttons.includes(Meta.ButtonFunction.CLOSE) + ? St.Side.LEFT : St.Side.RIGHT; + if (Me.shellVersion < 43) { + this._closeButton = new St.Button({ + visible: false, + style_class: 'window-close', + child: new St.Icon({ icon_name: 'preview-close-symbolic' }), + }); + } else { + this._closeButton = new St.Button({ + visible: false, + style_class: 'window-close', + icon_name: 'preview-close-symbolic', + }); + } + this._closeButton.add_constraint(new Clutter.BindConstraint({ + source: windowContainer, + coordinate: Clutter.BindCoordinate.POSITION, + })); + this._closeButton.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.X_AXIS, + pivot_point: new Graphene.Point({ x: 0.5, y: -1 }), + factor: this._closeButtonSide === St.Side.LEFT ? 0 : 1, + })); + this._closeButton.add_constraint(new Clutter.AlignConstraint({ + source: windowContainer, + align_axis: Clutter.AlignAxis.Y_AXIS, + pivot_point: new Graphene.Point({ x: -1, y: 0.5 }), + factor: 0, + })); + this._closeButton.connect('clicked', () => this._deleteAll()); + + this.add_child(this._title); + this.add_child(this._icon); + this.add_child(this._closeButton); + + this._overviewAdjustment.connectObject( + 'notify::value', () => this._updateIconScale(), this); + this._updateIconScale(); + + this.connect('notify::realized', () => { + if (!this.realized) + return; + + this._title.ensure_style(); + this._icon.ensure_style(); + }); + + if (ICON_SIZE < 22) { + // disable app icon + this._icon.hide(); + } else { + this._updateIconScale(); + } + + + // if window is created while the overview is shown, icon and title should be visible immediately if (Main.overview._overview._controls._stateAdjustment.value < 1) { this._icon.scale_x = 0; @@ -124,9 +393,9 @@ const WindowPreviewCommon = { if (global.get_pointer()[0] === opt.showingPointerX || Main.overview._overview._controls._stateAdjustment.value < 1) return; - const adjustment = this._workspace._background._stateAdjustment; opt.WORKSPACE_MODE = 1; - _Util.exposeWindows(adjustment, false); + const view = this._workspace.get_parent(); + view.exposeWindows(this._workspace.metaWorkspace.index()); this.disconnect(this._wsStateConId); }); } @@ -136,53 +405,39 @@ const WindowPreviewCommon = { this._stateAdjustmentSigId = this._workspace.stateAdjustment.connect('notify::value', this._updateIconScale.bind(this)); } - // replace click action with custom one - const action = this.get_actions()[0]; - - const handlerId = GObject.signal_handler_find(action, { signalId: 'clicked' }); - if (handlerId) - action.disconnect(handlerId); + const metaWin = this.metaWindow; + if (opt.DASH_ISOLATE_WS && !metaWin._wsChangedConId) { + metaWin._wsChangedConId = metaWin.connect('workspace-changed', + () => Main.overview.dash._queueRedisplay()); + } else if (!opt.DASH_ISOLATE_WS && metaWin._wsChangedConId) { + metaWin.disconnect(metaWin._wsChangedConId); + } + }, - action.connect('clicked', act => { - const button = act.get_button(); - if (button === Clutter.BUTTON_PRIMARY) { - this._activate(); - return Clutter.EVENT_STOP; - } else if (button === Clutter.BUTTON_SECONDARY) { - // this action cancels long-press event and the 'long-press-cancel' event is used by the Shell to actually initiate DnD - // so the dnd initiation needs to be removed - if (this._longPressLater) { - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.remove(this._longPressLater); - } else { - Meta.later_remove(this._longPressLater); - delete this._longPressLater; - } - } - const tracker = Shell.WindowTracker.get_default(); - const appName = tracker.get_window_app(this.metaWindow).get_name(); - _Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`); - return Clutter.EVENT_STOP; - } - return Clutter.EVENT_PROPAGATE; - }); + _closeWinAction() { + this.hide(); + this._deleteAll(); + }, - if (opt.WINDOW_ICON_CLICK_SEARCH) { - const iconClickAction = new Clutter.ClickAction(); - iconClickAction.connect('clicked', act => { - if (act.get_button() === Clutter.BUTTON_PRIMARY) { - const tracker = Shell.WindowTracker.get_default(); - const appName = tracker.get_window_app(this.metaWindow).get_name(); - _Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`); - return Clutter.EVENT_STOP; - } - return Clutter.EVENT_PROPAGATE; - }); - this._icon.add_action(iconClickAction); + _removeLaters() { + // this action cancels long-press event and the 'long-press-cancel' event is used by the Shell to actually initiate DnD + // so the dnd initiation needs to be removed + if (this._longPressLater) { + if (Meta.later_remove) + Meta.later_remove(this._longPressLater); + else + global.compositor.get_laters().remove(this._longPressLater); + delete this._longPressLater; } }, + _searchAppWindowsAction() { + this._removeLaters(); + const tracker = Shell.WindowTracker.get_default(); + const appName = tracker.get_window_app(this.metaWindow).get_name(); + Me.Util.activateSearchProvider(`${Me.WSP_PREFIX} ${appName}`); + }, + _updateIconScale() { let { currentState, initialState, finalState } = this._overviewAdjustment.getStateTransitionParams(); @@ -192,7 +447,7 @@ const WindowPreviewCommon = { const visible = (initialState > ControlsState.HIDDEN || finalState > ControlsState.HIDDEN) && - !(finalState === ControlsState.APP_GRID && primaryMonitor); + !(finalState === ControlsState.APP_GRID && opt.WS_ANIMATION && primaryMonitor); let scale = 0; if (visible) @@ -210,7 +465,6 @@ const WindowPreviewCommon = { else if (primaryMonitor && currentState > ControlsState.WINDOW_PICKER) scale = 0; - // in static workspace mode show icon and title on windows expose if (opt.OVERVIEW_MODE) { if (currentState === 1) @@ -219,29 +473,32 @@ const WindowPreviewCommon = { return; } + if (!opt.WS_ANIMATION && (Main.overview._overview.controls._searchController.searchActive || + ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) || + (initialState === ControlsState.APP_GRID && finalState === ControlsState.WINDOW_PICKER))) + ) + return; + + // if titles are in 'always show' mode, we need to add transition between visible/invisible state + // but the transition is quite expensive, + // showing the titles at the end of the transition is good enough and workspace preview transition is much smoother if (scale === 1) { - this._icon.ease({ - duration: 50, - scale_x: scale, - scale_y: scale, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, + this._icon.set({ + scale_x: 1, + scale_y: 1, }); this._title.ease({ duration: 100, opacity: 255, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); - } else if (this._icon.scale_x !== 0) { + } else { + this._title.opacity = 0; this._icon.set({ - scale_x: 0, - scale_y: 0, + scale_x: scale, + scale_y: scale, }); - this._title.opacity = 0; } - - // if titles are in 'always show' mode, we need to add transition between visible/invisible state - // but the transition is quite expensive, - // showing the titles at the end of the transition is good enough and workspace preview transition is much smoother }, showOverlay(animate) { @@ -252,7 +509,7 @@ const WindowPreviewCommon = { return; this._overlayShown = true; - if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + if (opt.WIN_TITLES_POSITION === 2) this._restack(); // If we're supposed to animate and an animation in our direction @@ -263,7 +520,7 @@ const WindowPreviewCommon = { ongoingTransition.get_interval().peek_final_value() === 255) return; - const toShow = this._windowCanClose() + const toShow = this._windowCanClose() && opt.SHOW_CLOSE_BUTTON ? [this._closeButton] : []; @@ -301,12 +558,12 @@ const WindowPreviewCommon = { if (!this._overlayShown) return; this._overlayShown = false; - if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && Main.overview._overview.controls._stateAdjustment.value < 1) { - this.get_parent()?.set_child_above_sibling(this, null); + + if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && Main.overview._overview.controls._stateAdjustment.value < 1) this._activateSelected = true; - } - if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW) + + if (opt.WIN_TITLES_POSITION === 2) this._restack(); // If we're supposed to animate and an animation in our direction @@ -353,14 +610,11 @@ const WindowPreviewCommon = { this._delegate = null; if (this._longPressLater) { - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.remove(this._longPressLater); - delete this._longPressLater; - } else { - Meta.later_remove(this._longPressLater); - delete this._longPressLater; - } + if (Meta.later_add) + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, this._longPressLater); + else + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, this._longPressLater); + delete this._longPressLater; } if (this._idleHideOverlayId > 0) { diff --git a/extensions/44/vertical-workspaces/lib/windowSearchProvider.js b/extensions/44/vertical-workspaces/lib/windowSearchProvider.js index 5f90784..b82f365 100644 --- a/extensions/44/vertical-workspaces/lib/windowSearchProvider.js +++ b/extensions/44/vertical-workspaces/lib/windowSearchProvider.js @@ -9,30 +9,23 @@ 'use strict'; -const { GLib, Gio, Meta, St, Shell } = imports.gi; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; const Main = imports.ui.main; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); -const Settings = Me.imports.lib.settings; -const _Util = Me.imports.lib.util; +let Me; +let opt; // gettext -const _ = Settings._; - -const shellVersion = Settings.shellVersion; - -const ModifierType = imports.gi.Clutter.ModifierType; - -let windowSearchProvider; -let _enableTimeoutId = 0; +let _; // prefix helps to eliminate results from other search providers // so it needs to be something less common // needs to be accessible from vw module -var prefix = 'wq//'; - -let opt; +const PREFIX = 'wq//'; const Action = { NONE: 0, @@ -42,66 +35,77 @@ const Action = { MOVE_ALL_TO_WS: 4, }; -function getOverviewSearchResult() { - return Main.overview._overview.controls._searchController._searchResults; -} +var WindowSearchProviderModule = class { + // export for other modules + static _PREFIX = PREFIX; + constructor(me) { + Me = me; + opt = Me.opt; + _ = Me.gettext; + + this._firstActivation = true; + this.moduleEnabled = false; + // export for other modules -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - if (!reset && opt.WINDOW_SEARCH_PROVIDER_ENABLED && !windowSearchProvider) { - enable(); - } else if (reset || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) { - disable(); + this._windowSearchProvider = null; + this._enableTimeoutId = 0; + } + + cleanGlobals() { + Me = null; opt = null; + _ = null; } -} - -function enable() { - // delay because Fedora had problem to register a new provider soon after Shell restarts - _enableTimeoutId = GLib.timeout_add( - GLib.PRIORITY_DEFAULT, - 2000, - () => { - if (!windowSearchProvider) { - windowSearchProvider = new WindowSearchProvider(opt); - getOverviewSearchResult()._registerProvider( - windowSearchProvider - ); - } - _enableTimeoutId = 0; - return GLib.SOURCE_REMOVE; + + update(reset) { + this.moduleEnabled = opt.get('windowSearchProviderModule'); + + reset = reset || !this.moduleEnabled; + + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); } - ); -} + if (reset && this._firstActivation) + console.debug(' WindowSearchProviderModule - Keeping untouched'); + } -function disable() { - if (windowSearchProvider) { - getOverviewSearchResult()._unregisterProvider( - windowSearchProvider + _activateModule() { + // delay because Fedora had problem to register a new provider soon after Shell restarts + this._enableTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + 2000, + () => { + if (!this._windowSearchProvider) { + this._windowSearchProvider = new WindowSearchProvider(opt); + this._getOverviewSearchResult()._registerProvider(this._windowSearchProvider); + } + this._enableTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } ); - windowSearchProvider = null; + console.debug(' WindowSearchProviderModule - Activated'); } - if (_enableTimeoutId) { - GLib.source_remove(_enableTimeoutId); - _enableTimeoutId = 0; + + _disableModule() { + if (this._windowSearchProvider) { + this._getOverviewSearchResult()._unregisterProvider(this._windowSearchProvider); + this._windowSearchProvider = null; + } + if (this._enableTimeoutId) { + GLib.source_remove(this._enableTimeoutId); + this._enableTimeoutId = 0; + } + + console.debug(' WindowSearchProviderModule - Disabled'); } -} - -function makeResult(window, i) { - const app = Shell.WindowTracker.get_default().get_window_app(window); - const appName = app ? app.get_name() : 'Unknown'; - const windowTitle = window.get_title(); - const wsIndex = window.get_workspace().index(); - - return { - 'id': i, - // convert all accented chars to their basic form and lower case for search - 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), - appName, - windowTitle, - window, - }; -} + + _getOverviewSearchResult() { + return Main.overview._overview.controls._searchController._searchResults; + } +}; const closeSelectedRegex = /^\/x!$/; const closeAllResultsRegex = /^\/xa!$/; @@ -110,15 +114,22 @@ const moveAllToWsRegex = /^\/ma[0-9]+$/; const WindowSearchProvider = class WindowSearchProvider { constructor() { - this.id = `open-windows@${Me.metadata.uuid}`; - this.appInfo = Gio.AppInfo.create_from_commandline('true', _('Open Windows'), null); - this.appInfo.get_description = () => _('List of open windows'); - this.appInfo.get_name = () => _('Open Windows'); - this.appInfo.get_id = () => this.id; - this.appInfo.get_icon = () => Gio.icon_new_for_string('focus-windows-symbolic'); - this.appInfo.should_show = () => true; - - this.canLaunchSearch = true; + this.id = 'open-windows'; + const appSystem = Shell.AppSystem.get_default(); + // use arbitrary app to get complete appInfo object + let appInfo = appSystem.lookup_app('com.matjakeman.ExtensionManager.desktop')?.get_app_info(); + if (!appInfo) + appInfo = appSystem.lookup_app('org.gnome.Extensions.desktop')?.get_app_info(); + if (!appInfo) + appInfo = Gio.AppInfo.create_from_commandline('true', _('Open Windows'), null); + appInfo.get_description = () => _('Search open windows'); + appInfo.get_name = () => _('Open Windows'); + appInfo.get_id = () => this.id; + appInfo.get_icon = () => Gio.icon_new_for_string('focus-windows-symbolic'); + appInfo.should_show = () => true; + + this.appInfo = appInfo; + this.canLaunchSearch = false; this.isRemoteProvider = false; this.action = 0; @@ -128,7 +139,7 @@ const WindowSearchProvider = class WindowSearchProvider { // do not modify original terms let termsCopy = [...terms]; // search for terms without prefix - termsCopy[0] = termsCopy[0].replace(prefix, ''); + termsCopy[0] = termsCopy[0].replace(PREFIX, ''); /* if (gOptions.get('searchWindowsCommands')) { this.action = 0; @@ -167,9 +178,9 @@ const WindowSearchProvider = class WindowSearchProvider { let m; for (let key in candidates) { if (opt.SEARCH_FUZZY) - m = _Util.fuzzyMatch(term, candidates[key].name); + m = Me.Util.fuzzyMatch(term, candidates[key].name); else - m = _Util.strictMatch(term, candidates[key].name); + m = Me.Util.strictMatch(term, candidates[key].name); if (m !== -1) results.push({ weight: m, id: key }); @@ -178,7 +189,19 @@ const WindowSearchProvider = class WindowSearchProvider { results.sort((a, b) => a.weight > b.weight); const currentWs = global.workspace_manager.get_active_workspace_index(); // prefer current workspace - results.sort((a, b) => (this.windows[a.id].window.get_workspace().index() !== currentWs) && (this.windows[b.id].window.get_workspace().index() === currentWs)); + switch (opt.WINDOW_SEARCH_ORDER) { + case 1: // MRU - current ws first + results.sort((a, b) => (this.windows[a.id].window.get_workspace().index() !== currentWs) && (this.windows[b.id].window.get_workspace().index() === currentWs)); + break; + case 2: // MRU - by workspace + results.sort((a, b) => this.windows[a.id].window.get_workspace().index() > this.windows[b.id].window.get_workspace().index()); + break; + case 3: // Stable sequence - by workspace + results.sort((a, b) => this.windows[a.id].window.get_stable_sequence() > this.windows[b.id].window.get_stable_sequence()); + results.sort((a, b) => this.windows[a.id].window.get_workspace().index() > this.windows[b.id].window.get_workspace().index()); + break; + } + results.sort((a, b) => (_terms !== ' ') && (a.weight > 0 && b.weight === 0)); this.resultIds = results.map(item => item.id); @@ -187,7 +210,7 @@ const WindowSearchProvider = class WindowSearchProvider { getResultMetas(resultIds, callback = null) { const metas = resultIds.map(id => this.getResultMeta(id)); - if (shellVersion >= 43) + if (Me.shellVersion >= 43) return new Promise(resolve => resolve(metas)); else callback(metas); @@ -210,12 +233,29 @@ const WindowSearchProvider = class WindowSearchProvider { }; } + makeResult(window, i) { + const app = Shell.WindowTracker.get_default().get_window_app(window); + const appName = app ? app.get_name() : 'Unknown'; + const windowTitle = window.get_title(); + const wsIndex = window.get_workspace().index(); + + return { + 'id': i, + // convert all accented chars to their basic form and lower case for search + 'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(), + appName, + windowTitle, + window, + }; + } + launchSearch(/* terms, timeStamp*/) { + } activateResult(resultId/* , terms, timeStamp*/) { - const isCtrlPressed = _Util.isCtrlPressed(); - const isShiftPressed = _Util.isShiftPressed(); + const isCtrlPressed = Me.Util.isCtrlPressed(); + const isShiftPressed = Me.Util.isShiftPressed(); this.action = 0; this.targetWs = 0; @@ -276,15 +316,16 @@ const WindowSearchProvider = class WindowSearchProvider { this.windows = windows = {}; global.display.get_tab_list(Meta.TabList.NORMAL, null).filter(w => w.get_workspace() !== null).map( (v, i) => { - windows[`${i}-${v.get_id()}`] = makeResult(v, `${i}-${v.get_id()}`); + windows[`${i}-${v.get_id()}`] = this.makeResult(v, `${i}-${v.get_id()}`); return windows[`${i}-${v.get_id()}`]; } ); - if (shellVersion >= 43) + if (Me.shellVersion >= 43) return new Promise(resolve => resolve(this._getResultSet(terms))); else callback(this._getResultSet(terms)); + return null; } @@ -293,13 +334,15 @@ const WindowSearchProvider = class WindowSearchProvider { return results; } - getSubsearchResultSet(previousResults, terms, callback/* , cancellable*/) { - // if we return previous results, quick typers get non-actual results - callback(this._getResultSet(terms)); + getSubsearchResultSet(previousResults, terms, callback) { + if (Me.shellVersion < 43) { + this.getSubsearchResultSet42(terms, callback); + return null; + } + return this.getInitialResultSet(terms); } - /* createResultObject(resultMeta) { - const app = Shell.WindowTracker.get_default().get_window_app(resultMeta.id); - return new AppIcon(app); - }*/ + getSubsearchResultSet42(terms, callback) { + callback(this._getResultSet(terms)); + } }; diff --git a/extensions/44/vertical-workspaces/lib/workspace.js b/extensions/44/vertical-workspaces/lib/workspace.js index 3b61a6d..be60403 100644 --- a/extensions/44/vertical-workspaces/lib/workspace.js +++ b/extensions/44/vertical-workspaces/lib/workspace.js @@ -10,51 +10,68 @@ 'use strict'; -const { St, Graphene } = imports.gi; +const Graphene = imports.gi.Graphene; +const St = imports.gi.St; const Main = imports.ui.main; -const Util = imports.misc.util; const Workspace = imports.ui.workspace; +const Util = imports.misc.util; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); - -const _Util = Me.imports.lib.util; - -let _overrides; +let Me; let opt; -let _firstRun = true; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('workspaceModule', true); - reset = reset || !moduleEnabled; +var WorkspaceModule = class { + constructor(me) { + Me = me; + opt = Me.opt; - // don't even touch this module if disabled - if (_firstRun && reset) - return; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } - _firstRun = false; + cleanGlobals() { + Me = null; + opt = null; + } - if (_overrides) - _overrides.removeAll(); + update(reset) { + this.moduleEnabled = opt.get('workspaceModule'); + const conflict = false; + reset = reset || !this.moduleEnabled || conflict; - if (reset) { - Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; - _overrides = null; - opt = null; - return; + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WorkspaceModule - Keeping untouched'); } - _overrides = new _Util.Overrides(); + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - _overrides.addOverride('WorkspaceBackground', Workspace.WorkspaceBackground.prototype, WorkspaceBackground); + this._overrides.addOverride('WorkspaceBackground', Workspace.WorkspaceBackground.prototype, WorkspaceBackground); - // fix overlay base for Vertical Workspaces - _overrides.addOverride('WorkspaceLayout', Workspace.WorkspaceLayout.prototype, WorkspaceLayout); -} + // fix overlay base for Vertical Workspaces + this._overrides.addOverride('WorkspaceLayout', Workspace.WorkspaceLayout.prototype, WorkspaceLayout); + console.debug(' WorkspaceModule - Activated'); + } + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95; + + console.debug(' WorkspaceModule - Disabled'); + } +}; // workaround for upstream bug (that is not that invisible in default shell) // smaller window cannot be scaled below 0.95 (WINDOW_PREVIEW_MAXIMUM_SCALE) diff --git a/extensions/44/vertical-workspaces/lib/workspaceAnimation.js b/extensions/44/vertical-workspaces/lib/workspaceAnimation.js index 07008c6..d790895 100644 --- a/extensions/44/vertical-workspaces/lib/workspaceAnimation.js +++ b/extensions/44/vertical-workspaces/lib/workspaceAnimation.js @@ -9,81 +9,147 @@ */ 'use strict'; + const Main = imports.ui.main; -const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; const WorkspaceAnimation = imports.ui.workspaceAnimation; -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const _Util = Me.imports.lib.util; +const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; + +let Me; +let opt; -// first reference to constant defined using const in other module returns undefined, the MonitorGroup const will remain empty and unused -let MonitorGroupDummy = WorkspaceAnimation.MonitorGroup; -MonitorGroupDummy = null; +var WorkspaceAnimationModule = class { + constructor(me) { + Me = me; + opt = Me.opt; + + // first reference to constant defined using const in other module returns undefined, the MonitorGroup const will remain empty and unused + this.dummy = WorkspaceAnimation.MonitorGroup; + + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + this._origBaseDistance = null; + this._wsAnimationSwipeBeginId = 0; + this._wsAnimationSwipeUpdateId = 0; + this._wsAnimationSwipeEndId = 0; + } -let _origBaseDistance; -let _wsAnimationSwipeBeginId; -let _wsAnimationSwipeUpdateId; -let _wsAnimationSwipeEndId; + cleanGlobals() { + Me = null; + opt = null; + } -let _overrides; -let opt; -let _firstRun = true; + update(reset) { + this.moduleEnabled = opt.get('workspaceAnimationModule'); + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('workspaceAnimationModule', true); - reset = reset || !moduleEnabled; + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WorkspaceAnimationModule - Keeping untouched'); + } - // don't even touch this module if disabled - if (_firstRun && reset) - return; + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - _firstRun = false; + if (opt.STATIC_WS_SWITCHER_BG) { + this._overrideMonitorGroupProperty(); + this._overrides.addOverride('WorkspaceAnimationMonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroup); + } - if (_overrides) - _overrides.removeAll(); + this._connectWsAnimationSwipeTracker(); + console.debug(' WorkspaceAnimationModule - Activated'); + } - if (reset || !moduleEnabled) { - _connectWsAnimationSwipeTracker(true); - _overrideMonitorGroupProperty(true); - _overrides = null; - opt = null; - return; + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + const reset = true; + this._connectWsAnimationSwipeTracker(reset); + this._overrideMonitorGroupProperty(reset); + console.debug(' WorkspaceAnimationModule - Disabled'); } - if (opt.STATIC_WS_SWITCHER_BG) { - _overrides = new _Util.Overrides(); - _overrideMonitorGroupProperty(); - _overrides.addOverride('WorkspaceAnimationMonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroup); + _overrideMonitorGroupProperty(reset = false) { + if (!this._origBaseDistance) + this._origBaseDistance = Object.getOwnPropertyDescriptor(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance').get; + + let getter; + if (reset) { + if (this._origBaseDistance) + getter = { get: this._origBaseDistance }; + } else { + getter = { + get() { + // const spacing = 100 * imports.gi.St.ThemeContext.get_for_stage(global.stage).scale_factor; + const spacing = 0; + if (global.workspace_manager.layout_rows === -1) + return this._monitor.height + spacing + (opt.PANEL_MODE ? Main.panel.height : 0); // compensation for hidden panel + else + return this._monitor.width + spacing; + }, + }; + } + + if (getter) + Object.defineProperty(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance', getter); + } + + _connectWsAnimationSwipeTracker(reset = false) { + if (reset) { + if (this._wsAnimationSwipeBeginId) { + Main.wm._workspaceAnimation._swipeTracker.disconnect(this._wsAnimationSwipeBeginId); + this._wsAnimationSwipeBeginId = 0; + } + if (this._wsAnimationSwipeEndId) { + Main.wm._workspaceAnimation._swipeTracker.disconnect(this._wsAnimationSwipeEndId); + this._wsAnimationSwipeEndId = 0; + } + } else if (!this._wsAnimationSwipeBeginId) { + // display ws switcher popup when gesture begins and connect progress + this._wsAnimationSwipeBeginId = Main.wm._workspaceAnimation._swipeTracker.connect('begin', () => this._connectWsAnimationProgress(true)); + // we want to be sure that popup with the final ws index show up when gesture ends + this._wsAnimationSwipeEndId = Main.wm._workspaceAnimation._swipeTracker.connect('end', (tracker, duration, endProgress) => this._connectWsAnimationProgress(false, endProgress)); + } } - _connectWsAnimationSwipeTracker(); -} - -// remove spacing between workspaces during transition to remove flashing wallpaper between workspaces with maximized windows -function _overrideMonitorGroupProperty(reset = false) { - if (!_origBaseDistance) - _origBaseDistance = Object.getOwnPropertyDescriptor(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance').get; - - let getter; - if (reset) { - if (_origBaseDistance) - getter = { get: _origBaseDistance }; - } else { - getter = { - get() { - // const spacing = 100 * imports.gi.St.ThemeContext.get_for_stage(global.stage).scale_factor; - const spacing = 0; - if (global.workspace_manager.layout_rows === -1) - return this._monitor.height + spacing + (opt.PANEL_MODE ? Main.panel.height : 0); // compensation for hidden panel - else - return this._monitor.width + spacing; - }, - }; + _connectWsAnimationProgress(connect, endProgress = null) { + if (Main.overview.visible) + return; + + if (connect && !this._wsAnimationSwipeUpdateId) { + this._wsAnimationSwipeUpdateId = Main.wm._workspaceAnimation._swipeTracker.connect('update', (tracker, progress) => this._showWsSwitcherPopup(progress)); + } else if (!connect && this._wsAnimationSwipeUpdateId) { + Main.wm._workspaceAnimation._swipeTracker.disconnect(this._wsAnimationSwipeUpdateId); + this._wsAnimationSwipeUpdateId = 0; + this._showWsSwitcherPopup(Math.round(endProgress)); + } } - if (getter) - Object.defineProperty(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance', getter); -} + _showWsSwitcherPopup(progress) { + if (Main.overview.visible) + return; + + const wsIndex = Math.round(progress); + if (Main.wm._workspaceSwitcherPopup === null) { + Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); + Main.wm._workspaceSwitcherPopup.connect('destroy', () => { + Main.wm._workspaceSwitcherPopup = null; + }); + } + + Main.wm._workspaceSwitcherPopup.display(wsIndex); + } +}; const MonitorGroup = { // injection to _init() @@ -119,7 +185,9 @@ const MonitorGroup = { // hide (scale to 0) all non-sticky windows, their clones will be animated global.get_window_actors().forEach(actor => { const metaWin = actor.metaWindow; - if (metaWin?.get_monitor() === this._monitor.index && !(metaWin?.wm_class === 'conky' && metaWin?.is_on_all_workspaces())) { //* && !w.is_on_all_workspaces()*/) { + if (metaWin?.get_monitor() === this._monitor.index && + !(metaWin?.wm_class === 'conky' && metaWin?.is_on_all_workspaces()) && + !(metaWin?.wm_class === 'Gjs' && metaWin?.is_on_all_workspaces())) { // DING extension uses window with Gjs class // hide original window. we cannot use opacity since it also affects clones. // scaling them to 0 works well actor.scale_x = 0; @@ -136,49 +204,3 @@ const MonitorGroup = { }); }, }; - -function _connectWsAnimationSwipeTracker(reset = false) { - if (reset) { - if (_wsAnimationSwipeBeginId) { - Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeBeginId); - _wsAnimationSwipeBeginId = 0; - } - if (_wsAnimationSwipeEndId) { - Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeEndId); - _wsAnimationSwipeEndId = 0; - } - } else if (!_wsAnimationSwipeBeginId) { - // display ws switcher popup when gesture begins and connect progress - _wsAnimationSwipeBeginId = Main.wm._workspaceAnimation._swipeTracker.connect('begin', () => _connectWsAnimationProgress(true)); - // we want to be sure that popup with the final ws index show up when gesture ends - _wsAnimationSwipeEndId = Main.wm._workspaceAnimation._swipeTracker.connect('end', (tracker, duration, endProgress) => _connectWsAnimationProgress(false, endProgress)); - } -} - -function _connectWsAnimationProgress(connect, endProgress = null) { - if (Main.overview.visible) - return; - - if (connect && !_wsAnimationSwipeUpdateId) { - _wsAnimationSwipeUpdateId = Main.wm._workspaceAnimation._swipeTracker.connect('update', (tracker, progress) => _showWsSwitcherPopup(progress)); - } else if (!connect && _wsAnimationSwipeUpdateId) { - Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeUpdateId); - _wsAnimationSwipeUpdateId = 0; - _showWsSwitcherPopup(Math.round(endProgress)); - } -} - -function _showWsSwitcherPopup(progress) { - if (Main.overview.visible) - return; - - const wsIndex = Math.round(progress); - if (Main.wm._workspaceSwitcherPopup === null) { - Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); - Main.wm._workspaceSwitcherPopup.connect('destroy', () => { - Main.wm._workspaceSwitcherPopup = null; - }); - } - - Main.wm._workspaceSwitcherPopup.display(wsIndex); -} diff --git a/extensions/44/vertical-workspaces/lib/workspaceSwitcherPopup.js b/extensions/44/vertical-workspaces/lib/workspaceSwitcherPopup.js index 972f35e..5bde6d0 100644 --- a/extensions/44/vertical-workspaces/lib/workspaceSwitcherPopup.js +++ b/extensions/44/vertical-workspaces/lib/workspaceSwitcherPopup.js @@ -13,52 +13,71 @@ const Main = imports.ui.main; const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); +let Me; +let opt; -const _Util = Me.imports.lib.util; -let _overrides; +var WorkspaceSwitcherPopupModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -let opt; -let _firstRun = true; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - const moduleEnabled = opt.get('workspaceSwitcherPopupModule', true); - reset = reset || !moduleEnabled; + cleanGlobals() { + Me = null; + opt = null; + } - // don't even touch this module if disabled - if (_firstRun && reset) - return; + update(reset) { + this.moduleEnabled = opt.get('workspaceSwitcherPopupModule'); + const conflict = Me.Util.getEnabledExtensions('workspace-switcher-manager').length || + Me.Util.getEnabledExtensions('WsSwitcherPopupManager').length; - _firstRun = false; + if (conflict && !reset) + console.warn(`[${Me.metadata.name}] Warning: "WorkspaceSwitcherPopup" module disabled due to potential conflict with another extension`); - if (_overrides) - _overrides.removeAll(); + reset = reset || !this.moduleEnabled || conflict; - if (reset) { - _overrides = null; - opt = null; - return; + // don't touch original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WorkspaceSwitcherPopupModule - Keeping untouched'); } - _overrides = new _Util.Overrides(); + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - const enabled = global.settings.get_strv('enabled-extensions'); - const allowWsPopupInjection = !(enabled.includes('workspace-switcher-manager@G-dH.github.com') || enabled.includes('WsSwitcherPopupManager@G-dH.github.com-dev')); - if (allowWsPopupInjection) { // 1-VERTICAL, 0-HORIZONTAL - _overrides.addOverride('WorkspaceSwitcherPopup', WorkspaceSwitcherPopup.WorkspaceSwitcherPopup.prototype, WorkspaceSwitcherPopupOverride); + this._overrides.addOverride('WorkspaceSwitcherPopup', WorkspaceSwitcherPopup.WorkspaceSwitcherPopup.prototype, WorkspaceSwitcherPopupCommon); + console.debug(' WorkspaceSwitcherPopupModule - Activated'); } -} -const WorkspaceSwitcherPopupOverride = { + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + console.debug(' WorkspaceSwitcherPopupModule - Disabled'); + } +}; + +const WorkspaceSwitcherPopupCommon = { // injection to _init() after__init() { if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL this._list.vertical = true; } this._list.set_style('margin: 0;'); - this.remove_constraint(this.get_constraints()[0]); + if (this.get_constraints()[0]) + this.remove_constraint(this.get_constraints()[0]); }, // injection to display() diff --git a/extensions/44/vertical-workspaces/lib/workspaceThumbnail.js b/extensions/44/vertical-workspaces/lib/workspaceThumbnail.js index d0bc206..844c224 100644 --- a/extensions/44/vertical-workspaces/lib/workspaceThumbnail.js +++ b/extensions/44/vertical-workspaces/lib/workspaceThumbnail.js @@ -10,56 +10,90 @@ 'use strict'; -const { GLib, Clutter, Graphene, Meta, Shell, St } = imports.gi; +const Clutter = imports.gi.Clutter; +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Meta = imports.gi.Meta; +const Shell = imports.gi.Shell; +const St = imports.gi.St; + +const AppDisplay = imports.ui.appDisplay; +const Background = imports.ui.background; const DND = imports.ui.dnd; const Main = imports.ui.main; -const Background = imports.ui.background; +const OverviewControls = imports.ui.overviewControls; const WorkspaceThumbnail = imports.ui.workspaceThumbnail; + +let Me; +let opt; + const ThumbnailState = WorkspaceThumbnail.ThumbnailState; +const ControlsState = OverviewControls.ControlsState; -const ControlsState = imports.ui.overviewControls.ControlsState; +const WORKSPACE_CUT_SIZE = 10; -const ExtensionUtils = imports.misc.extensionUtils; -const Me = ExtensionUtils.getCurrentExtension(); +var WorkspaceThumbnailModule = class { + constructor(me) { + Me = me; + opt = Me.opt; -// gettext -const _ = Me.imports.lib.settings._; + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; + } -const _Util = Me.imports.lib.util; -const shellVersion = _Util.shellVersion; + cleanGlobals() { + Me = null; + opt = null; + } -let _overrides; + update(reset) { + this.moduleEnabled = true; + const conflict = false; -const WORKSPACE_CUT_SIZE = 10; -const _originalMaxThumbnailScale = WorkspaceThumbnail.MAX_THUMBNAIL_SCALE; + reset = reset || !this.moduleEnabled || conflict; -let opt = null; + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WorkspaceThumbnailModule - Keeping untouched'); + } -function update(reset = false) { - if (_overrides) - _overrides.removeAll(); + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); + if (!this._originalMaxThumbnailScale) + this._originalMaxThumbnailScale = WorkspaceThumbnail.MAX_THUMBNAIL_SCALE; - if (reset) { - if (_originalMaxThumbnailScale) - WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = _originalMaxThumbnailScale; - _overrides = null; - opt = null; - return; - } + // don't limit max thumbnail scale for other clients than overview, specifically AATWS. + WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = 1; + // WorkspaceThumbnail.ThumbnailsBox._MAX_THUMBNAIL_SCALE = 1; + + this._overrides.addOverride('WorkspaceThumbnail', WorkspaceThumbnail.WorkspaceThumbnail.prototype, WorkspaceThumbnailCommon); + this._overrides.addOverride('ThumbnailsBoxCommon', WorkspaceThumbnail.ThumbnailsBox.prototype, ThumbnailsBoxCommon); - opt = Me.imports.lib.settings.opt; - _overrides = new _Util.Overrides(); + // replacing opt.ORIENTATION local constant with boxOrientation internal variable allows external customers such as the AATWS extension to control the box orientation. + Main.overview._overview.controls._thumbnailsBox._boxOrientation = opt.ORIENTATION; + + console.debug(' WorkspaceThumbnailModule - Activated'); + } - // don't limit max thumbnail scale for other clients than overview, for example AATWS. - WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = 1; + _disableModule() { + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; - _overrides.addOverride('WorkspaceThumbnail', WorkspaceThumbnail.WorkspaceThumbnail.prototype, WorkspaceThumbnailCommon); - _overrides.addOverride('ThumbnailsBoxCommon', WorkspaceThumbnail.ThumbnailsBox.prototype, ThumbnailsBoxCommon); + WorkspaceThumbnail.MAX_THUMBNAIL_SCALE = this._originalMaxThumbnailScale; - // replacing opt.ORIENTATION local constant with boxOrientation internal variable allows external customers such as the AATWS extension to control the box orientation. - Main.overview._overview.controls._thumbnailsBox._boxOrientation = opt.ORIENTATION; -} + console.debug(' WorkspaceThumbnailModule - Disabled'); + } +}; const WorkspaceThumbnailCommon = { // injection to _init() @@ -70,6 +104,8 @@ const WorkspaceThumbnailCommon = { // unless border is removed if (opt.SHOW_WS_TMB_BG) this.add_style_class_name('ws-tmb-labeled'); + else + this.add_style_class_name('ws-tmb-transparent'); // add workspace thumbnails labels if enabled if (opt.SHOW_WST_LABELS) { // 0 - disable @@ -77,7 +113,7 @@ const WorkspaceThumbnailCommon = { const wsIndex = this.metaWorkspace.index(); let label = `${wsIndex + 1}`; if (opt.SHOW_WST_LABELS === 2) { // 2 - index + workspace name - const settings = ExtensionUtils.getSettings('org.gnome.desktop.wm.preferences'); + const settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); const wsLabels = settings.get_strv('workspace-names'); if (wsLabels.length > wsIndex && wsLabels[wsIndex]) label += `: ${wsLabels[wsIndex]}`; @@ -129,7 +165,9 @@ const WorkspaceThumbnailCommon = { } }); this._nWindowsConId = this.metaWorkspace.connect('notify::n-windows', () => { - // wait for new information + if (this._updateLabelTimeout) + return; + // wait for new data this._updateLabelTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { const newLabel = getLabel(); this._wsLabel.text = newLabel; @@ -168,7 +206,7 @@ const WorkspaceThumbnailCommon = { closeButton.opacity = 255; if (!Meta.prefs_get_dynamic_workspaces() || (Meta.prefs_get_dynamic_workspaces() && global.workspace_manager.get_n_workspaces() - 1 !== this.metaWorkspace.index())) { // color the button red if ready to react on clicks - if (opt.CLOSE_WS_BUTTON_MODE < 3 || (opt.CLOSE_WS_BUTTON_MODE === 3 && _Util.isCtrlPressed())) + if (opt.CLOSE_WS_BUTTON_MODE < 3 || (opt.CLOSE_WS_BUTTON_MODE === 3 && Me.Util.isCtrlPressed())) closeButton.add_style_class_name('workspace-close-button-hover'); } }); @@ -220,12 +258,6 @@ const WorkspaceThumbnailCommon = { this._viewport.set_child_below_sibling(this._bgManager.backgroundActor, null); - this.connect('destroy', () => { - if (this._bgManager) - this._bgManager.destroy(); - this._bgManager = null; - }); - // full brightness of the thumbnail bg draws unnecessary attention // there is a grey bg under the wallpaper this._bgManager.backgroundActor.opacity = 220; @@ -256,7 +288,7 @@ const WorkspaceThumbnailCommon = { this._lastCloseClickTime = Date.now(); return; } - } else if (opt.CLOSE_WS_BUTTON_MODE === 3 && !_Util.isCtrlPressed()) { + } else if (opt.CLOSE_WS_BUTTON_MODE === 3 && !Me.Util.isCtrlPressed()) { return; } @@ -335,7 +367,7 @@ const WorkspaceThumbnailCommon = { if (!source.app && source.shellWorkspaceLaunch) return DND.DragMotionResult.COPY_DROP; - if (source instanceof imports.ui.appDisplay.FolderIcon) + if (source instanceof AppDisplay.FolderIcon) return DND.DragMotionResult.COPY_DROP; @@ -369,8 +401,8 @@ const WorkspaceThumbnailCommon = { timestamp: time, }); return true; - } else if (source instanceof imports.ui.appDisplay.FolderIcon) { - if (shellVersion >= 44) { + } else if (source instanceof AppDisplay.FolderIcon) { + if (Me.shellVersion >= 44) { for (let app of source.view._apps) { // const app = Shell.AppSystem.get_default().lookup_app(id); app.open_new_window(this.metaWorkspace.index()); @@ -419,7 +451,7 @@ const ThumbnailsBoxCommon = { if (!source.metaWindow && (!source.app || !source.app.can_open_new_window()) && (source.app || !source.shellWorkspaceLaunch) && - !(source instanceof imports.ui.appDisplay.FolderIcon)) + !(source instanceof AppDisplay.FolderIcon)) return false; @@ -448,8 +480,8 @@ const ThumbnailsBoxCommon = { workspace: newWorkspaceIndex, timestamp: time, }); - } else if (source instanceof imports.ui.appDisplay.FolderIcon) { - if (shellVersion >= 44) { + } else if (source instanceof AppDisplay.FolderIcon) { + if (Me.shellVersion >= 44) { for (let app of source.view._apps) { // const app = Shell.AppSystem.get_default().lookup_app(id); app.open_new_window(newWorkspaceIndex); @@ -495,7 +527,7 @@ const ThumbnailsBoxCommon = { if (!source.metaWindow && (!source.app || !source.app.can_open_new_window()) && (source.app || !source.shellWorkspaceLaunch) && - source !== Main.xdndHandler && !(source instanceof imports.ui.appDisplay.FolderIcon)) + source !== Main.xdndHandler && !(source instanceof AppDisplay.FolderIcon)) return DND.DragMotionResult.CONTINUE; const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; @@ -551,18 +583,18 @@ const ThumbnailsBoxCommon = { return ThumbnailsBoxHorizontal._withinWorkspace.bind(this)(...args); }, - get_preferred_custom_width(...args) { + vfunc_get_preferred_width(...args) { if (this._boxOrientation) - return ThumbnailsBoxVertical.get_preferred_custom_width.bind(this)(...args); + return ThumbnailsBoxVertical.vfunc_get_preferred_width.bind(this)(...args); else - return ThumbnailsBoxHorizontal.get_preferred_custom_width.bind(this)(...args); + return ThumbnailsBoxHorizontal.vfunc_get_preferred_width.bind(this)(...args); }, - get_preferred_custom_height(...args) { + vfunc_get_preferred_height(...args) { if (this._boxOrientation) - return ThumbnailsBoxVertical.get_preferred_custom_height.bind(this)(...args); + return ThumbnailsBoxVertical.vfunc_get_preferred_height.bind(this)(...args); else - return ThumbnailsBoxHorizontal.get_preferred_custom_height.bind(this)(...args); + return ThumbnailsBoxHorizontal.vfunc_get_preferred_height.bind(this)(...args); }, vfunc_allocate(...args) { @@ -632,14 +664,9 @@ const ThumbnailsBoxVertical = { return y > workspaceY1 && y <= workspaceY2; }, - // vfunc_get_preferred_width: function(forHeight) { - // override of this vfunc doesn't work for some reason (tested on Ubuntu and Fedora), it's not reachable - get_preferred_custom_width(forHeight) { - if (!this.visible) - return [0, 0]; - - if (forHeight === -1) - return this.get_preferred_custom_height(forHeight); + vfunc_get_preferred_width(forHeight) { + if (forHeight < 10) + return [this._porthole.width, this._porthole.width]; let themeNode = this.get_theme_node(); @@ -652,19 +679,14 @@ const ThumbnailsBoxVertical = { const avail = forHeight - totalSpacing; let scale = (avail / nWorkspaces) / this._porthole.height; - // scale = Math.min(scale, opt.MAX_THUMBNAIL_SCALE); const width = Math.round(this._porthole.width * scale); return themeNode.adjust_preferred_height(width, width); }, - get_preferred_custom_height(_forWidth) { - if (!this.visible) - return [0, 0]; - - // Note that for getPreferredHeight/Width we cheat a bit and skip propagating - // the size request to our children because we know how big they are and know - // that the actors aren't depending on the virtual functions being called. + vfunc_get_preferred_height(forWidth) { + if (forWidth < 10) + return [0, this._porthole.height]; let themeNode = this.get_theme_node(); let spacing = themeNode.get_length('spacing'); @@ -674,15 +696,14 @@ const ThumbnailsBoxVertical = { let totalSpacing = (nWorkspaces - 3) * spacing; const ratio = this._porthole.width / this._porthole.height; - const tmbHeight = themeNode.adjust_for_width(_forWidth) / ratio; + const tmbHeight = themeNode.adjust_for_width(forWidth) / ratio; const naturalheight = this._thumbnails.reduce((accumulator, thumbnail/* , index*/) => { const progress = 1 - thumbnail.collapse_fraction; const height = tmbHeight * progress; return accumulator + height; }, 0); - - return themeNode.adjust_preferred_width(totalSpacing, naturalheight); + return themeNode.adjust_preferred_width(totalSpacing, Math.round(naturalheight)); }, // removes extra space (extraWidth in the original function), we need the box as accurate as possible @@ -759,16 +780,11 @@ const ThumbnailsBoxVertical = { this._dropPlaceholder.allocate_preferred_size( ...this._dropPlaceholder.get_position()); - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); - } else { - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); - } + const laterFunc = () => this._dropPlaceholder.hide(); + if (Meta.later_add) + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, laterFunc); + else + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, laterFunc); } let childBox = new Clutter.ActorBox(); @@ -796,16 +812,11 @@ const ThumbnailsBoxVertical = { this._dropPlaceholder.allocate(childBox); - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); - } else { - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); - } + const laterFunc = () => this._dropPlaceholder.show(); + if (Meta.later_add) + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, laterFunc); + else + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, laterFunc); y += placeholderHeight + spacing; } @@ -925,7 +936,10 @@ const ThumbnailsBoxHorizontal = { return x > workspaceX1 && x <= workspaceX2; }, - get_preferred_custom_height(forWidth) { + vfunc_get_preferred_height(forWidth) { + if (forWidth < 10) + return [this._porthole.height, this._porthole.height]; + let themeNode = this.get_theme_node(); forWidth = themeNode.adjust_for_width(forWidth); @@ -937,18 +951,15 @@ const ThumbnailsBoxHorizontal = { const avail = forWidth - totalSpacing; let scale = (avail / nWorkspaces) / this._porthole.width; - // scale = Math.min(scale, opt.MAX_THUMBNAIL_SCALE); const height = Math.round(this._porthole.height * scale); + return themeNode.adjust_preferred_height(height, height); }, - get_preferred_custom_width(_forHeight) { - // Note that for getPreferredHeight/Width we cheat a bit and skip propagating - // the size request to our children because we know how big they are and know - // that the actors aren't depending on the virtual functions being called. - if (!this.visible) - return [0, 0]; + vfunc_get_preferred_width(forHeight) { + if (forHeight < 10) + return [0, this._porthole.width]; let themeNode = this.get_theme_node(); @@ -959,13 +970,14 @@ const ThumbnailsBoxHorizontal = { const ratio = this._porthole.height / this._porthole.width; - const tmbWidth = themeNode.adjust_for_height(_forHeight) / ratio; + const tmbWidth = themeNode.adjust_for_height(forHeight) / ratio; const naturalWidth = this._thumbnails.reduce((accumulator, thumbnail) => { const progress = 1 - thumbnail.collapse_fraction; const width = tmbWidth * progress; return accumulator + width; }, 0); + return themeNode.adjust_preferred_width(totalSpacing, naturalWidth); }, @@ -1041,16 +1053,11 @@ const ThumbnailsBoxHorizontal = { this._dropPlaceholder.allocate_preferred_size( ...this._dropPlaceholder.get_position()); - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); - } else { - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.hide(); - }); - } + const laterFunc = () => this._dropPlaceholder.hide(); + if (Meta.later_add) + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, laterFunc); + else + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, laterFunc); } let childBox = new Clutter.ActorBox(); @@ -1078,16 +1085,11 @@ const ThumbnailsBoxHorizontal = { this._dropPlaceholder.allocate(childBox); - if (shellVersion >= 44) { - const laters = global.compositor.get_laters(); - laters.add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); - } else { - Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { - this._dropPlaceholder.show(); - }); - } + const laterFunc = () => this._dropPlaceholder.show(); + if (Meta.later_add) + Meta.later_add(Meta.LaterType.BEFORE_REDRAW, laterFunc); + else + global.compositor.get_laters().add(Meta.LaterType.BEFORE_REDRAW, laterFunc); x += placeholderWidth + spacing; } diff --git a/extensions/44/vertical-workspaces/lib/workspacesView.js b/extensions/44/vertical-workspaces/lib/workspacesView.js index e3575f1..3df7b22 100644 --- a/extensions/44/vertical-workspaces/lib/workspacesView.js +++ b/extensions/44/vertical-workspaces/lib/workspacesView.js @@ -10,62 +10,92 @@ 'use strict'; -const { GObject, Clutter, Meta, St } = imports.gi; +const Clutter = imports.gi.Clutter; +const GObject = imports.gi.GObject; +const Meta = imports.gi.Meta; +const St = imports.gi.St; const Main = imports.ui.main; -const Util = imports.misc.util; +const OverviewControls = imports.ui.overviewControls; const WorkspacesView = imports.ui.workspacesView; -// first reference to constant defined using const in other module returns undefined, the SecondaryMonitorDisplay const will remain empty and unused -const SecondaryMonitorDisplay = WorkspacesView.SecondaryMonitorDisplay; -const ControlsState = imports.ui.overviewControls.ControlsState; -const FitMode = imports.ui.workspacesView.FitMode; - -const SIDE_CONTROLS_ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME; - -const Me = imports.misc.extensionUtils.getCurrentExtension(); -const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix; -const SEARCH_RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix; - -const _Util = Me.imports.lib.util; -let _overrides; +const Util = imports.misc.util; +let Me; let opt; +const ControlsState = OverviewControls.ControlsState; +const FitMode = WorkspacesView.FitMode; -function update(reset = false) { - opt = Me.imports.lib.settings.opt; - opt.DESKTOP_CUBE_ENABLED = Main.extensionManager._enabledExtensions.includes('desktop-cube@schneegans.github.com'); - const cubeSupported = opt.DESKTOP_CUBE_ENABLED && !opt.ORIENTATION && !opt.OVERVIEW_MODE; +var WorkspacesViewModule = class { + constructor(me) { + // first reference to constant defined using const in other module returns undefined, the SecondaryMonitorDisplay const will remain empty and unused + this.dummy = WorkspacesView.SecondaryMonitorDisplay; - // if desktop cube extension is enabled while V-Shell is loaded, removeAll() would override its code - if (_overrides && !cubeSupported) { - _overrides.removeAll(); - global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1); + Me = me; + opt = Me.opt; + + this._firstActivation = true; + this.moduleEnabled = false; + this._overrides = null; } - if (reset) { - _overrides = null; + cleanGlobals() { + Me = null; opt = null; - return; } + update(reset) { + this.moduleEnabled = true; + const conflict = false; + + reset = reset || !this.moduleEnabled || conflict; + + // don't touch the original code if module disabled + if (reset && !this._firstActivation) { + this._disableModule(); + } else if (!reset) { + this._firstActivation = false; + this._activateModule(); + } + if (reset && this._firstActivation) + console.debug(' WorkspacesViewModule - Keeping untouched'); + } - _overrides = new _Util.Overrides(); + _activateModule() { + if (!this._overrides) + this._overrides = new Me.Util.Overrides(); - if (!cubeSupported) - _overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon); + const desktopCubeEnabled = Me.Util.getEnabledExtensions('desktop-cube@schneegans.github.com').length; + const desktopCubeConflict = desktopCubeEnabled && !opt.ORIENTATION && !opt.OVERVIEW_MODE; - _overrides.addOverride('WorkspacesDisplay', WorkspacesView.WorkspacesDisplay.prototype, WorkspacesDisplay); - _overrides.addOverride('ExtraWorkspaceView', WorkspacesView.ExtraWorkspaceView.prototype, ExtraWorkspaceView); + if (!desktopCubeConflict) + this._overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon); - if (opt.ORIENTATION) { - // switch internal workspace orientation in GS - global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, -1, 1); - _overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayVertical); - } else { - _overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayHorizontal); + this._overrides.addOverride('WorkspacesDisplay', WorkspacesView.WorkspacesDisplay.prototype, WorkspacesDisplayCommon); + this._overrides.addOverride('ExtraWorkspaceView', WorkspacesView.ExtraWorkspaceView.prototype, ExtraWorkspaceViewCommon); + this._overrides.addOverride('SecondaryMonitorDisplayCommon', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayCommon); + + if (opt.ORIENTATION) { + // switch internal workspace orientation in GS + global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, -1, 1); + this._overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayVertical); + } else { + global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1); + this._overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayHorizontal); + } + + console.debug(' WorkspacesViewModule - Activated'); } -} + + _disableModule() { + global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1); + if (this._overrides) + this._overrides.removeAll(); + this._overrides = null; + + console.debug(' WorkspacesViewModule - Disabled'); + } +}; const WorkspacesViewCommon = { _getFirstFitSingleWorkspaceBox(box, spacing, vertical) { @@ -91,7 +121,6 @@ const WorkspacesViewCommon = { } const fitSingleBox = new Clutter.ActorBox({ x1, y1 }); - fitSingleBox.set_size(workspaceWidth, workspaceHeight); return fitSingleBox; @@ -143,21 +172,7 @@ const WorkspacesViewCommon = { }, _updateVisibility() { - // replaced in _updateWorkspacesState - /* let workspaceManager = global.workspace_manager; - let active = workspaceManager.get_active_workspace_index(); - - const fitMode = this._fitModeAdjustment.value; - const singleFitMode = fitMode === FitMode.SINGLE; - - for (let w = 0; w < this._workspaces.length; w++) { - let workspace = this._workspaces[w]; - - if (this._animating || this._gestureActive || !singleFitMode) - workspace.show(); - else - workspace.visible = Math.abs(w - active) <= opt.NUMBER_OF_VISIBLE_NEIGHBORS; - }*/ + // visibility handles _updateWorkspacesState() }, // disable scaling and hide inactive workspaces @@ -175,27 +190,21 @@ const WorkspacesViewCommon = { const primaryMonitor = Main.layoutManager.primaryMonitor.index; - // define the transition values here to save time in each ws - let scaleX, scaleY; - if (opt.ORIENTATION) { // vertical 1 / horizontal 0 - scaleX = 1; - scaleY = 0.1; - } else { - scaleX = 0.1; - scaleY = 1; - } - const wsScrollProgress = adj.value % 1; const secondaryMonitor = this._monitorIndex !== global.display.get_primary_monitor(); const blockSecondaryAppGrid = opt.OVERVIEW_MODE && currentState > 1; + // Hide inactive workspaces this._workspaces.forEach((w, index) => { if (!(blockSecondaryAppGrid && secondaryMonitor)) w.stateAdjustment.value = workspaceMode; - const distanceToCurrentWorkspace = Math.abs(adj.value - index); + let distance = adj.value - index; + const distanceToCurrentWorkspace = Math.abs(distance); const scaleProgress = 1 - Math.clamp(distanceToCurrentWorkspace, 0, 1); + // const scale = Util.lerp(0.94, 1, scaleProgress); + // w.set_scale(scale, scale); // if we disable workspaces that we can't or don't need to see, transition animations will be noticeably smoother // only the current ws needs to be visible during overview transition animations @@ -210,22 +219,28 @@ const WorkspacesViewCommon = { // after transition from APP_GRID to WINDOW_PICKER state, // adjacent workspaces are hidden and we need them to show up // make them visible during animation can impact smoothness of the animation - // so we show them after the animation finished, scaling animation will make impression that they move in from outside the monitor + // so we show them after the animation finished, move them to their position from outside of the monitor if (!w.visible && distanceToCurrentWorkspace === 1 && initialState === ControlsState.APP_GRID && currentState === ControlsState.WINDOW_PICKER) { - w.scale_x = scaleX; - w.scale_y = scaleY; w.visible = true; + const directionNext = distance > 0; + if (!opt.ORIENTATION) { + const width = w.width * 0.6 * opt.WS_PREVIEW_SCALE; + w.translation_x = directionNext ? -width : width; + } + if (opt.ORIENTATION) { + const height = w.height * 0.6 * opt.WS_PREVIEW_SCALE; + w.translation_y = directionNext ? -height : height; + } + + w.opacity = 10; + w.get_parent().set_child_below_sibling(w, null); w.ease({ - duration: 100, - scale_x: 1, - scale_y: 1, + duration: 300, + translation_x: 0, + translation_y: 0, + opacity: 255, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); - } else if (!w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) { - w.set({ - scale_x: 1, - scale_y: 1, - }); } // force ws preview bg corner radiuses where GS doesn't do it @@ -238,9 +253,40 @@ const WorkspacesViewCommon = { w._background.opacity = 0; }); }, + + exposeWindows(workspaceIndex = null, callback) { + let adjustments = []; + if (workspaceIndex === null) { + this._workspaces.forEach(ws => { + adjustments.push(ws._background._stateAdjustment); + }); + } else { + adjustments.push(this._workspaces[workspaceIndex]._background._stateAdjustment); + } + + adjustments.forEach(adj => { + if (adj.value === 0) { + adj.value = 0; + adj.ease(1, { + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + opt.WORKSPACE_MODE = 1; + if (callback) + callback(); + }, + }); + } + }); + }, +}; + +const SecondaryMonitorDisplayCommon = { + exposeWindows(...args) { + this._workspacesView.exposeWindows(...args); + }, }; -// SecondaryMonitorDisplay Vertical const SecondaryMonitorDisplayVertical = { _getThumbnailParamsForState(state) { @@ -270,24 +316,6 @@ const SecondaryMonitorDisplayVertical = { return { opacity, scale, translationX }; }, - _getThumbnailsWidth(box, spacing) { - if (opt.SEC_WS_TMB_HIDDEN) - return 0; - - const [width, height] = box.get_size(); - const { expandFraction } = this._thumbnails; - const [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(height - 2 * spacing); - let scaledWidth; - if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) - scaledWidth = ((height - Main.panel.height) * opt.SEC_MAX_THUMBNAIL_SCALE) * (width / height); - else - scaledWidth = width * opt.SEC_MAX_THUMBNAIL_SCALE; - - return Math.min( - thumbnailsWidth * expandFraction, - Math.round(scaledWidth)); - }, - _getWorkspacesBoxForState(state, box, padding, thumbnailsWidth, spacing) { // const { ControlsState } = OverviewControls; const workspaceBox = box.copy(); @@ -303,7 +331,7 @@ const SecondaryMonitorDisplayVertical = { break; yShift = 0; - if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) { + if (opt.SEC_WS_PREVIEW_SHIFT && Main.panel.visible) { if (opt.PANEL_POSITION_TOP) yShift = Main.panel.height; else @@ -341,26 +369,40 @@ const SecondaryMonitorDisplayVertical = { const spacing = themeNode.get_length('spacing') * expandFraction; const padding = Math.round(0.1 * height); - let thumbnailsWidth = this._getThumbnailsWidth(contentBox, spacing); - let [, thumbnailsHeight] = this._thumbnails.get_preferred_custom_height(thumbnailsWidth); - thumbnailsHeight = Math.min(thumbnailsHeight, height - 2 * spacing); - + let thumbnailsWidth = 0; + let thumbnailsHeight = 0; this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible) { + const reduceBoxHeight = opt.SEC_WS_PREVIEW_SHIFT && Main.panel.visible ? Main.panel.height : 0; + + thumbnailsWidth = width * opt.SEC_MAX_THUMBNAIL_SCALE; + + let totalTmbSpacing; + [totalTmbSpacing, thumbnailsHeight] = this._thumbnails.get_preferred_height(thumbnailsWidth); + thumbnailsHeight = Math.round(thumbnailsHeight + totalTmbSpacing); + + const thumbnailsHeightMax = height - spacing - reduceBoxHeight; + + if (thumbnailsHeight > thumbnailsHeightMax) { + thumbnailsHeight = thumbnailsHeightMax; + thumbnailsWidth = Math.round(this._thumbnails.get_preferred_width(thumbnailsHeight)[1]); + } + let wsTmbX; if (opt.SEC_WS_TMB_LEFT) { // left - wsTmbX = Math.round(spacing / 4); + wsTmbX = spacing / 2; this._thumbnails._positionLeft = true; } else { - wsTmbX = Math.round(width - spacing / 4 - thumbnailsWidth); + wsTmbX = width - spacing / 2 - thumbnailsWidth; this._thumbnails._positionLeft = false; } const childBox = new Clutter.ActorBox(); - const availSpace = height - thumbnailsHeight - 2 * spacing; + const availSpace = height - thumbnailsHeight; let wsTmbY = availSpace / 2; - wsTmbY -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbY - spacing; + wsTmbY -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbY; + wsTmbY += opt.SEC_WS_PREVIEW_SHIFT && Main.panel.visible ? Main.panel.height : 0; childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY)); childBox.set_size(thumbnailsWidth, thumbnailsHeight); @@ -390,22 +432,14 @@ const SecondaryMonitorDisplayVertical = { if (opt.OVERVIEW_MODE2) this.set_child_above_sibling(this._thumbnails, null); - const visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible === visible) return; this._thumbnails.show(); + this._thumbnails.visible = visible; this._updateThumbnailParams(); - this._thumbnails.ease_property('expand-fraction', visible ? 1 : 0, { - duration: SIDE_CONTROLS_ANIMATION_TIME, - mode: Clutter.AnimationMode.EASE_OUT_QUAD, - onComplete: () => { - this._thumbnails.visible = visible; - this._thumbnails._indicator.visible = visible; - }, - }); }, _updateThumbnailParams() { @@ -417,7 +451,6 @@ const SecondaryMonitorDisplayVertical = { if (!this._thumbnails._thumbnails.length) this._thumbnails._createThumbnails(); - const { initialState, finalState, progress } = this._overviewAdjustment.getStateTransitionParams(); @@ -474,7 +507,6 @@ const SecondaryMonitorDisplayVertical = { }, }; -// SecondaryMonitorDisplay Horizontal const SecondaryMonitorDisplayHorizontal = { _getThumbnailParamsForState(state) { // const { ControlsState } = OverviewControls; @@ -520,7 +552,7 @@ const SecondaryMonitorDisplayHorizontal = { break; yShift = 0; - if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) { + if (opt.SEC_WS_PREVIEW_SHIFT && Main.panel.visible) { if (opt.PANEL_POSITION_TOP) yShift = Main.panel.height; else @@ -571,24 +603,36 @@ const SecondaryMonitorDisplayHorizontal = { const spacing = themeNode.get_length('spacing') * expandFraction; const padding = Math.round(0.1 * height); - let thumbnailsHeight = this._getThumbnailsHeight(contentBox); - let [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(thumbnailsHeight); - thumbnailsWidth = Math.min(thumbnailsWidth, width - 2 * spacing); - + let thumbnailsWidth = 0; + let thumbnailsHeight = 0; this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN; if (this._thumbnails.visible) { + const reservedHeight = opt.SEC_WS_PREVIEW_SHIFT && Main.panel.visible ? Main.panel.height : 0; + + thumbnailsHeight = height * opt.SEC_MAX_THUMBNAIL_SCALE; + + let totalTmbSpacing; + [totalTmbSpacing, thumbnailsWidth] = this._thumbnails.get_preferred_width(thumbnailsHeight); + thumbnailsWidth = Math.round(thumbnailsWidth + totalTmbSpacing); + + const thumbnailsWidthMax = width - spacing; + + if (thumbnailsWidth > thumbnailsWidthMax) { + thumbnailsWidth = thumbnailsWidthMax; + thumbnailsHeight = Math.round(this._thumbnails.get_preferred_height(thumbnailsWidth)[1]); + } + let wsTmbY; if (opt.SEC_WS_TMB_TOP) - wsTmbY = Math.round(spacing / 4); + wsTmbY = spacing / 2 + reservedHeight; else - wsTmbY = Math.round(height - spacing / 4 - thumbnailsHeight); - + wsTmbY = height - spacing / 2 - thumbnailsHeight; const childBox = new Clutter.ActorBox(); - const availSpace = width - thumbnailsWidth - 2 * spacing; + const availSpace = width - thumbnailsWidth; let wsTmbX = availSpace / 2; - wsTmbX -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbX - spacing; + wsTmbX -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbX; childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY)); childBox.set_size(thumbnailsWidth, thumbnailsHeight); @@ -625,7 +669,6 @@ const SecondaryMonitorDisplayHorizontal = { if (!this._thumbnails._thumbnails.length) this._thumbnails._createThumbnails(); - const { initialState, finalState, progress } = this._overviewAdjustment.getStateTransitionParams(); @@ -682,7 +725,7 @@ const SecondaryMonitorDisplayHorizontal = { }, }; -const ExtraWorkspaceView = { +const ExtraWorkspaceViewCommon = { _updateWorkspaceMode() { const overviewState = this._overviewAdjustment.value; @@ -701,9 +744,23 @@ const ExtraWorkspaceView = { if (!opt.SHOW_WS_PREVIEW_BG && this._workspace._background.opacity) this._workspace._background.opacity = 0; }, + + exposeWindows() { + const adjustment = this._workspace._background._stateAdjustment; + if (adjustment.value === 0) { + adjustment.value = 0; + adjustment.ease(1, { + duration: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + opt.WORKSPACE_MODE = 1; + }, + }); + } + }, }; -const WorkspacesDisplay = { +const WorkspacesDisplayCommon = { _updateWorkspacesViews() { for (let i = 0; i < this._workspacesViews.length; i++) this._workspacesViews[i].destroy(); @@ -761,8 +818,8 @@ const WorkspacesDisplay = { return Clutter.EVENT_STOP; } - if (_Util.isShiftPressed()) { - let direction = _Util.getScrollDirection(event); + if (Me.Util.isShiftPressed()) { + let direction = Me.Util.getScrollDirection(event); if (direction === null || (Date.now() - this._lastScrollTime) < 150) return Clutter.EVENT_STOP; this._lastScrollTime = Date.now(); @@ -775,11 +832,11 @@ const WorkspacesDisplay = { else direction = 0; - if (direction) { - _Util.reorderWorkspace(direction); + Me.Util.reorderWorkspace(direction); // make all workspaces on primary monitor visible for case the new position is hidden - Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => { + const primaryMonitorIndex = global.display.get_primary_monitor(); + Main.overview._overview._controls._workspacesDisplay._workspacesViews[primaryMonitorIndex]._workspaces.forEach(w => { w.visible = true; }); return Clutter.EVENT_STOP; @@ -806,7 +863,7 @@ const WorkspacesDisplay = { switch (symbol) { case Clutter.KEY_Return: case Clutter.KEY_KP_Enter: - if (_Util.isCtrlPressed()) { + if (Me.Util.isCtrlPressed()) { Main.ctrlAltTabManager._items.forEach(i => { if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i); @@ -836,17 +893,17 @@ const WorkspacesDisplay = { which = workspaceManager.n_workspaces - 1; break; case Clutter.KEY_space: - if (_Util.isCtrlPressed() && _Util.isShiftPressed()) { - _Util.openPreferences(); - } else if (_Util.isAltPressed()) { + if (Me.Util.isCtrlPressed() && Me.Util.isShiftPressed()) { + Me.Util.activateSearchProvider(Me.ESP_PREFIX); + } else if (Me.Util.isAltPressed()) { Main.ctrlAltTabManager._items.forEach(i => { if (i.sortGroup === 1 && i.name === 'Dash') Main.ctrlAltTabManager.focusGroup(i); }); - } else if (opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && _Util.isCtrlPressed()) { - _Util.activateSearchProvider(SEARCH_RECENT_FILES_PREFIX); - } else if (opt.WINDOW_SEARCH_PROVIDER_ENABLED) { - _Util.activateSearchProvider(SEARCH_WINDOWS_PREFIX); + } else if (opt.get('recentFilesSearchProviderModule') && Me.Util.isCtrlPressed()) { + Me.Util.activateSearchProvider(Me.RFSP_PREFIX); + } else if (opt.get('windowSearchProviderModule')) { + Me.Util.activateSearchProvider(Me.WSP_PREFIX); } return Clutter.EVENT_STOP; @@ -867,16 +924,18 @@ const WorkspacesDisplay = { }); } else if (opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE && state === 1) { // expose windows for OVERVIEW_MODE 1 - const adjustment = this._workspacesViews[0]._workspaces[global.workspace_manager.get_active_workspace().index()]._background._stateAdjustment; - opt.WORKSPACE_MODE = 1; - _Util.exposeWindows(adjustment, true); + const wsIndex = global.workspace_manager.get_active_workspace().index(); + // after expose animation activate keyboard for window selection + const callback = Me.Util.activateKeyboardForWorkspaceView; + this._workspacesViews.forEach( + view => { + view.exposeWindows(wsIndex, callback); + } + ); } else { if (state === 2) return Clutter.EVENT_PROPAGATE; - Main.ctrlAltTabManager._items.forEach(i => { - if (i.sortGroup === 1 && i.name === 'Windows') - Main.ctrlAltTabManager.focusGroup(i); - }); + Me.Util.activateKeyboardForWorkspaceView(); } return Clutter.EVENT_STOP; @@ -895,14 +954,14 @@ const WorkspacesDisplay = { // Otherwise it is a workspace index ws = workspaceManager.get_workspace_by_index(which); - if (_Util.isShiftPressed()) { + if (Me.Util.isShiftPressed()) { let direction; if (which === Meta.MotionDirection.UP || which === Meta.MotionDirection.LEFT) direction = -1; else if (which === Meta.MotionDirection.DOWN || which === Meta.MotionDirection.RIGHT) direction = 1; if (direction) - _Util.reorderWorkspace(direction); + Me.Util.reorderWorkspace(direction); // make all workspaces on primary monitor visible for case the new position is hidden Main.overview._overview._controls._workspacesDisplay._workspacesViews[0]._workspaces.forEach(w => { w.visible = true; |