/** * Vertical Workspaces * appDisplay.js * * @author GdH * @copyright 2022 - 2023 * @license GPL-3.0 * */ 'use strict'; const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi; const DND = imports.ui.dnd; const Main = imports.ui.main; const AppDisplay = imports.ui.appDisplay; const IconGrid = imports.ui.iconGrid; const { AppMenu } = imports.ui.appMenu; const PopupMenu = imports.ui.popupMenu; const BoxPointer = imports.ui.boxpointer; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const IconGridOverride = Me.imports.iconGrid; const _Util = Me.imports.util; let _overrides; let _appGridLayoutSettings; let _appDisplayScrollConId; let _appSystemStateConId; let _appGridLayoutConId; let _updateAppGridTimeoutId; let _origAppDisplayAcceptDrop; let _origAppViewItemAcceptDrop; let _origAppViewItemHandleDragOver; let opt; let shellVersion = _Util.shellVersion; function update(reset = false) { if (_overrides) { _overrides.removeAll(); } if (reset) { _setAppDisplayOrientation(false); _updateAppGridProperties(reset); _updateAppGridDND(reset); _restoreOverviewGroup(); _overrides = null; opt = null; return; } opt = Me.imports.settings.opt; _overrides = new _Util.Overrides(); if (opt.ORIENTATION === Clutter.Orientation.VERTICAL) { _overrides.addOverride('AppDisplayVertical', AppDisplay.AppDisplay.prototype, AppDisplayVertical); _overrides.addOverride('BaseAppViewVertical', AppDisplay.BaseAppView.prototype, BaseAppViewVertical); } _overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider); // 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); _overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridOverride.IconGrid); } _overrides.addOverride('FolderView', AppDisplay.FolderView.prototype, FolderView); _overrides.addInjection('AppFolderDialog', AppDisplay.AppFolderDialog.prototype, AppFolderDialogInjections); _overrides.addOverride('AppIcon', AppDisplay.AppIcon.prototype, AppIcon); _overrides.addOverride('BaseAppView', AppDisplay.BaseAppView.prototype, BaseAppView); _overrides.addOverride('AppDisplay', AppDisplay.AppDisplay.prototype, AppDisplayCommon); _setAppDisplayOrientation(opt.ORIENTATION === Clutter.Orientation.VERTICAL); _updateAppGridProperties(); _updateAppGridDND(); } //--------------------------------------------------------------------------------------------------------- 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 appDisplay._hintContainer && 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 { 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(); appDisplay._hintContainer && appDisplay._hintContainer.get_parent() == null && scrollContainer.add_child(appDisplay._hintContainer); appDisplay._nextPageArrow.scale_x = 1; appDisplay._prevPageArrow.scale_x = 1; appDisplay._nextPageIndicator.remove_style_class_name('nextPageIndicator'); appDisplay._prevPageIndicator.remove_style_class_name('prevPageIndicator'); } // 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; } // 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) { // columns, rows, icon size const appDisplay = Main.overview._overview._controls._appDisplay; appDisplay.visible = true; // replace isFavorite function to always return false to allow dnd with favorite apps if (!reset && opt.APP_GRID_INCLUDE_DASH) { if (!appDisplay._appFavorites._backupIsFavorite) { appDisplay._appFavorites._backupIsFavorite = appDisplay._appFavorites.isFavorite; } appDisplay._appFavorites.isFavorite = () => false; } else { if (appDisplay._appFavorites._backupIsFavorite) { appDisplay._appFavorites.isFavorite = appDisplay._appFavorites._backupIsFavorite; appDisplay._appFavorites._backupIsFavorite = undefined; } } if (reset) { appDisplay._grid.layout_manager.fixedIconSize = -1; appDisplay._grid.layoutManager.allow_incomplete_pages = true; appDisplay._grid.setGridModes(); if (_appGridLayoutSettings) { _appGridLayoutSettings.disconnect(_appGridLayoutConId); _appGridLayoutConId = 0; _appGridLayoutSettings = null; } appDisplay._redisplay(); // secondary call is necessary to properly update app grid appDisplay._redisplay(); } else { // update grid on layout reset if (!_appGridLayoutSettings) { _appGridLayoutSettings = ExtensionUtils.getSettings('org.gnome.shell'); _appGridLayoutConId = _appGridLayoutSettings.connect('changed::app-picker-layout', _resetAppGrid); } // remove icons from App Grid _resetAppGrid(); const updateGrid = function(rows, columns) { if (rows === -1 || columns === -1) { appDisplay._grid.setGridModes(); } else { appDisplay._grid.setGridModes( [{ rows, columns }] ); } appDisplay._grid._setGridMode(0); } appDisplay._grid._currentMode = -1; if (opt.APP_GRID_ALLOW_CUSTOM) { updateGrid(opt.APP_GRID_ROWS, opt.APP_GRID_COLUMNS); } else { appDisplay._grid.setGridModes(); updateGrid(-1, -1); } appDisplay._grid.layoutManager.fixedIconSize = opt.APP_GRID_ICON_SIZE; appDisplay._grid.layoutManager.allow_incomplete_pages = opt.APP_GRID_ALLOW_INCOMPLETE_PAGES; // force rebuild icons. size shouldn't be the same as the current one, otherwise can be arbitrary appDisplay._grid.layoutManager.adaptToSize(200, 200); appDisplay._redisplay(); _realizeAppDisplay(); } } //------ App Grid - DND ---------------------------------------------------------- function _updateAppGridDND(reset) { if (opt.APP_GRID_ORDER && !reset) { if (!_appSystemStateConId) _appSystemStateConId = Shell.AppSystem.get_default().connect('app-state-changed', () => Main.overview._overview._controls._appDisplay._redisplay()); // deny dnd from dash to app grid if (!_origAppDisplayAcceptDrop) _origAppDisplayAcceptDrop = AppDisplay.AppDisplay.prototype.acceptDrop; AppDisplay.AppDisplay.prototype.acceptDrop = function() { return false; }; // deny creating folders by dnd on other icon if (!_origAppViewItemHandleDragOver) _origAppViewItemHandleDragOver = AppDisplay.AppViewItem.prototype.handleDragOver; AppDisplay.AppViewItem.prototype.handleDragOver = () => DND.DragMotionResult.NO_DROP; if (!_origAppViewItemAcceptDrop) _origAppViewItemAcceptDrop = AppDisplay.AppViewItem.prototype.acceptDrop; AppDisplay.AppViewItem.prototype.acceptDrop = () => false; } else { if (_appSystemStateConId) { Shell.AppSystem.get_default().disconnect(_appSystemStateConId); _appSystemStateConId = 0; } if (_origAppDisplayAcceptDrop) AppDisplay.AppDisplay.prototype.acceptDrop = _origAppDisplayAcceptDrop; if (_origAppViewItemHandleDragOver) AppDisplay.AppViewItem.prototype.handleDragOver = _origAppViewItemHandleDragOver; if (_origAppViewItemAcceptDrop) AppDisplay.AppViewItem.prototype.acceptDrop = _origAppViewItemAcceptDrop; } } function _realizeAppDisplay() { // force app grid to build all icons before the first visible animation to remove possible stuttering // let the main loop realize previous changes before continuing // don't do this during shell startup if (Main.layoutManager._startingUp) return; if (_updateAppGridTimeoutId) { GLib.source_remove(_updateAppGridTimeoutId); } _updateAppGridTimeoutId = GLib.timeout_add( GLib.PRIORITY_DEFAULT, 1000, () => { Main.layoutManager.overviewGroup.opacity = 1; Main.layoutManager.overviewGroup.scale_x = 0.1; Main.layoutManager.overviewGroup.show(); Main.overview.dash.showAppsButton.checked = true; GLib.source_remove(_updateAppGridTimeoutId); _updateAppGridTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => { _restoreOverviewGroup(); _updateAppGridTimeoutId = 0; return GLib.SOURCE_REMOVE; }); } ); } function _restoreOverviewGroup() { Main.overview.dash.showAppsButton.checked = false; Main.layoutManager.overviewGroup.opacity = 255; Main.layoutManager.overviewGroup.scale_x = 1; Main.layoutManager.overviewGroup.hide(); } //------ appDisplay - Vertical -------------------------------------------------------------------------------- var AppDisplayVertical = { // correction of the appGrid size when page indicators were moved from the bottom to the right adaptToSize: function(width, height) { const [, indicatorWidth] = this._pageIndicators.get_preferred_width(-1); width -= indicatorWidth; this._grid.findBestModeForSize(width, height); const adaptToSize = AppDisplay.BaseAppView.prototype.adaptToSize.bind(this); adaptToSize(width, height); }, } //------ AppDisplay.AppSearchProvider ---------------------------------------------------------------------------------- // App search result size var AppSearchProvider = { createResultObject: function(resultMeta) { if (resultMeta.id.endsWith('.desktop')) { const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), { expandTitleOnHover: false, }); icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); return icon; } else { const icon = new AppDisplay.SystemActionIcon(this, resultMeta); icon.icon._setSizeManually = true; icon.icon.setIconSize(opt.SEARCH_ICON_SIZE); return icon; } } } var BaseAppViewVertical = { // this fixes dnd from appDisplay to the workspace thumbnail on the left if appDisplay is on page 1 because of appgrid left overshoot _pageForCoords: function(x, y) { return AppDisplay.SidePages.NONE; }, } // ------ AppDisplay - Custom App Grid ------------------------------------------------------------------------ var AppDisplayCommon = { _ensureDefaultFolders: function() { // disable creation of default folders if user deleted them }, // apps load adapted for custom sorting and including dash items _loadApps: function() { let appIcons = []; this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => { try { appInfo.get_id(); // catch invalid file encodings } catch (e) { return false; } return (opt.APP_GRID_INCLUDE_DASH || !this._appFavorites.isFavorite(appInfo.get_id())) && this._parentalControlsManager.shouldShowApp(appInfo); }); let apps = this._appInfoList.map(app => app.get_id()); let appSys = Shell.AppSystem.get_default(); const appsInsideFolders = new Set(); this._folderIcons = []; if (!opt.APP_GRID_ORDER) { let folders = this._folderSettings.get_strv('folder-children'); folders.forEach(id => { let path = `${this._folderSettings.path}folders/${id}/`; let icon = this._items.get(id); if (!icon) { icon = new AppDisplay.FolderIcon(id, path, this); icon.connect('apps-changed', () => { this._redisplay(); this._savePages(); }); icon.connect('notify::pressed', () => { if (icon.pressed) this.updateDragFocus(icon); }); } // Don't try to display empty folders if (!icon.visible) { icon.destroy(); return; } appIcons.push(icon); this._folderIcons.push(icon); icon.getAppIds().forEach(appId => appsInsideFolders.add(appId)); }); } // 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 // the app picker, so there's no other need for a drag to start, // at least on single-monitor setups. // This also disables drag-to-launch on multi-monitor setups, // but we hope that is not used much. const isDraggable = global.settings.is_writable('favorite-apps') || global.settings.is_writable('app-picker-layout'); apps.forEach(appId => { if (!opt.APP_GRID_ORDER && appsInsideFolders.has(appId)) return; let icon = this._items.get(appId); if (!icon) { let app = appSys.lookup_app(appId); icon = new AppDisplay.AppIcon(app, { isDraggable }); icon.connect('notify::pressed', () => { if (icon.pressed) this.updateDragFocus(icon); }); } appIcons.push(icon); }); // At last, if there's a placeholder available, add it if (this._placeholder) appIcons.push(this._placeholder); //const runningIDs = Shell.AppSystem.get_default().get_running().map(app => app.get_id()); // remove running apps /*if (!APP_GRID_INCLUDE_DASH) { // !icon.app means folder appIcons = appIcons.filter((icon) => this._folderIcons.includes(icon) || !(runningIDs.includes(icon.app.id) || this._appFavorites.isFavorite(icon.id))); }*/ return appIcons; }, } var BaseAppView = { // adds sorting options and option to add favorites and running apps _redisplay: function() { let oldApps = this._orderedItems.slice(); let oldAppIds = oldApps.map(icon => icon.id); let newApps = this._loadApps().sort(this._compareItems.bind(this)); let newAppIds = newApps.map(icon => icon.id); let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id)); let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id)); // Remove old app icons removedApps.forEach(icon => { this._removeItem(icon); icon.destroy(); }); // Add new app icons, or move existing ones newApps.forEach(icon => { const [page, position] = this._getItemPosition(icon); if (addedApps.includes(icon)) this._addItem(icon, page, position); else if (page !== -1 && position !== -1) { this._moveItem(icon, page, position); } }); // Reorder App Grid by usage // sort all alphabetically if(opt.APP_GRID_ORDER > 0) { const { itemsPerPage } = this._grid; let appIcons = this._orderedItems; appIcons.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); // then sort used apps by usage if (opt.APP_GRID_ORDER === 2) { appIcons.sort((a, b) => Shell.AppUsage.get_default().compare(a.app.id, b.app.id)); } // sort favorites first if (opt.APP_GRID_INCLUDE_DASH === 2) { const fav = Object.keys(this._appFavorites._favorites); appIcons.sort((a, b) => { let aFav = fav.indexOf(a.id); if (aFav < 0) aFav = 999; let bFav = fav.indexOf(b.id); if (bFav < 0) bFav = 999; return bFav < aFav; }); } // sort running first if (opt.APP_GRID_INCLUDE_DASH === 2) { appIcons.sort((a, b) => a.app.get_state() !== Shell.AppState.RUNNING && b.app.get_state() === Shell.AppState.RUNNING); } appIcons.forEach((icon, i) => { const page = Math.floor(i / itemsPerPage); const position = i % itemsPerPage; this._moveItem(icon, page, position); }); this._orderedItems = appIcons; } this.emit('view-loaded'); }, _canAccept: function(source) { return (opt.APP_GRID_ORDER ? false : source instanceof AppDisplay.AppViewItem); }, // GS <= 42 only, Adapt app grid so it can use all available space adaptToSize(width, height) { let box = new Clutter.ActorBox({ x2: width, y2: height, }); box = this.get_theme_node().get_content_box(box); box = this._scrollView.get_theme_node().get_content_box(box); box = this._grid.get_theme_node().get_content_box(box); const availWidth = box.get_width(); const availHeight = box.get_height(); const gridRatio = this._grid.layout_manager.columnsPerPage / this._grid.layout_manager.rowsPerPage; const spaceRatio = availWidth / availHeight; let pageWidth, pageHeight; if (spaceRatio > gridRatio * 1.1) { // Enough room for some preview pageHeight = availHeight; pageWidth = Math.ceil(availHeight * gridRatio); if (spaceRatio > gridRatio * 1.5) { // Ultra-wide layout, give some extra space for // the page area, but up to an extent. const extraPageSpace = Math.min( Math.floor((availWidth - pageWidth) / 2), 200); // AppDisplay.MAX_PAGE_PADDING == 200 pageWidth += extraPageSpace; this._grid.layout_manager.pagePadding.left = Math.floor(extraPageSpace / 2); this._grid.layout_manager.pagePadding.right = Math.ceil(extraPageSpace / 2); } } else { // Not enough room, needs to shrink horizontally pageWidth = Math.ceil(availWidth * 0.95); // width limiter, original is 0.8 pageHeight = availHeight; this._grid.layout_manager.pagePadding.left = Math.floor(availWidth * 0.02); this._grid.layout_manager.pagePadding.right = Math.ceil(availWidth * 0.02); } this._grid.adaptToSize(pageWidth, pageHeight); const leftPadding = Math.floor( (availWidth - this._grid.layout_manager.pageWidth) / 2); const rightPadding = Math.ceil( (availWidth - this._grid.layout_manager.pageWidth) / 2); const topPadding = Math.floor( (availHeight - this._grid.layout_manager.pageHeight) / 2); const bottomPadding = Math.ceil( (availHeight - this._grid.layout_manager.pageHeight) / 2); this._scrollView.content_padding = new Clutter.Margin({ left: leftPadding, right: rightPadding, top: topPadding, bottom: bottomPadding, }); this._availWidth = availWidth; this._availHeight = availHeight; this._pageIndicatorOffset = leftPadding; this._pageArrowOffset = Math.max( leftPadding - 80, 0); // 80 is AppDisplay.PAGE_PREVIEW_MAX_ARROW_OFFSET } } var BaseAppViewGridLayout = { _getIndicatorsWidth(box) { const [width, height] = box.get_size(); const arrows = [ this._nextPageArrow, this._previousPageArrow, ]; const minArrowsWidth = arrows.reduce( (previousWidth, accessory) => { const [min] = accessory.get_preferred_width(height); return Math.max(previousWidth, min); }, 0); const idealIndicatorWidth = (width * 0.1/*PAGE_PREVIEW_RATIO*/) / 2; return Math.max(idealIndicatorWidth, minArrowsWidth); } } // ------------------ AppDisplay.AppFolderDialog - injection -------------------------------------------------------------- var AppFolderDialogInjections = { _init: function() { const iconSize = opt.APP_GRID_FOLDER_ICON_SIZE < 0 ? 96 : opt.APP_GRID_FOLDER_ICON_SIZE; let width = opt.APP_GRID_FOLDER_COLUMNS * (iconSize + 64); width = Math.max(640, Math.round(width + width / 10)); let height = opt.APP_GRID_FOLDER_ROWS * (iconSize + 64) + 150; opt.APP_GRID_ALLOW_CUSTOM && this.child.set_style(` width: ${width}px; height: ${height}px; padding: 30px; `); } } // ------------------ AppDisplay.FolderGrid ----------------------------------------------------------------------- var FolderView = { _createGrid: function() { let grid; if (shellVersion < 43) { grid = new FolderGrid(); } else { grid = new FolderGrid43(); } return grid; } } // folder columns and rows var FolderGrid = GObject.registerClass( class FolderGrid extends IconGrid.IconGrid { _init() { super._init({ allow_incomplete_pages: false, columns_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, page_halign: Clutter.ActorAlign.CENTER, page_valign: Clutter.ActorAlign.CENTER, }); opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;'); this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; } adaptToSize(width, height) { this.layout_manager.adaptToSize(width, height); } }); // only the first access to the const AppDisplay.AppGrid throws an error, so touch it before it's really needed let FolderGrid43; 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_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, rows_per_page: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, page_halign: Clutter.ActorAlign.CENTER, page_valign: Clutter.ActorAlign.CENTER, }); opt.APP_GRID_ALLOW_CUSTOM && this.set_style('column-spacing: 10px; row-spacing: 10px;'); this.layout_manager.fixedIconSize = opt.APP_GRID_FOLDER_ICON_SIZE; this.setGridModes([ { rows: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_ROWS : 3, columns: opt.APP_GRID_ALLOW_CUSTOM ? opt.APP_GRID_FOLDER_COLUMNS : 3, }, ]); } adaptToSize(width, height) { this.layout_manager.adaptToSize(width, height); } }); } var FOLDER_DIALOG_ANIMATION_TIME = 200; // AppDisplay.FOLDER_DIALOG_ANIMATION_TIME var AppFolderDialog = { _zoomAndFadeIn: function() { let [sourceX, sourceY] = this._source.get_transformed_position(); let [dialogX, dialogY] = this.child.get_transformed_position(); this.child.set({ translation_x: sourceX - dialogX, translation_y: sourceY - dialogY, scale_x: this._source.width / this.child.width, scale_y: this._source.height / this.child.height, opacity: 0, }); this.ease({ background_color: Clutter.Color.from_pixel(0x00000033), // DIALOG_SHADE_NORMAL duration: FOLDER_DIALOG_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); this.child.ease({ translation_x: 0, translation_y: 0, scale_x: 1, scale_y: 1, opacity: 255, duration: FOLDER_DIALOG_ANIMATION_TIME, mode: Clutter.AnimationMode.EASE_OUT_QUAD, }); this._needsZoomAndFade = false; if (this._sourceMappedId === 0) { this._sourceMappedId = this._source.connect( 'notify::mapped', this._zoomAndFadeOut.bind(this)); } }, } function _resetAppGrid(settings = null, key = null) { if (settings) { const currentValue = JSON.stringify(settings.get_value('app-picker-layout').deep_unpack()); const emptyValue = JSON.stringify([]); if (key === 'app-picker-layout' && currentValue != emptyValue) return; } const appDisplay = Main.overview._overview._controls._appDisplay; const items = appDisplay._orderedItems; for (let i = items.length - 1; i > -1; i--) { Main.overview._overview._controls._appDisplay._removeItem(items[i]); } // redisplay only from callback if (settings) appDisplay._redisplay(); } // ------------------ AppDisplay.AppIcon - override --------------------------------------------------------------- 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; } var AppIcon = { activate: function(button) { const event = Clutter.get_current_event(); const modifiers = event ? event.get_state() : 0; const isMiddleButton = button && button == Clutter.BUTTON_MIDDLE; const isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0; const isShiftPressed = (modifiers & Clutter.ModifierType.SHIFT_MASK) != 0; const openNewWindow = this.app.can_open_new_window() && this.app.state == Shell.AppState.RUNNING && (isCtrlPressed || isMiddleButton); const currentWS = global.workspace_manager.get_active_workspace(); const appRecentWorkspace = _getAppRecentWorkspace(this.app); let targetWindowOnCurrentWs = false; if (opt.DASH_FOLLOW_RECENT_WIN) { targetWindowOnCurrentWs = appRecentWorkspace === currentWS; } else { this.app.get_windows().forEach( w => targetWindowOnCurrentWs = targetWindowOnCurrentWs || (w.get_workspace() === currentWS) ); } if ((this.app.state == Shell.AppState.STOPPED || openNewWindow) && !isShiftPressed) this.animateLaunch(); if (openNewWindow) { this.app.open_new_window(-1); // if the app has more than one window (option: and has no window on the current workspace), // don't activate the app, only move the overview to the workspace with the app's recent window } else if (opt.DASH_SHOW_WINS_BEFORE && !isShiftPressed && this.app.get_n_windows() > 1 && !targetWindowOnCurrentWs) { this._scroll = true; this._scrollTime = Date.now(); //const appWS = this.app.get_windows()[0].get_workspace(); Main.wm.actionMoveWorkspace(appRecentWorkspace); Main.overview.dash.showAppsButton.checked = false; return; } else if (opt.DASH_SHIFT_CLICK_MV && isShiftPressed && this.app.get_windows().length) { this._moveAppToCurrentWorkspace(); return; } else if (isShiftPressed) { return; } else { this.app.activate(); } Main.overview.hide(); }, _moveAppToCurrentWorkspace: function() { this.app.get_windows().forEach(w => w.change_workspace(global.workspace_manager.get_active_workspace())); }, popupMenu: function(side = St.Side.LEFT) { if (shellVersion >= 42) this.setForcedHighlight(true); this._removeMenuTimeout(); this.fake_release(); if (!this._getWindowsOnCurrentWs) { this._getWindowsOnCurrentWs = function() { const winList = []; this.app.get_windows().forEach(w => { if(w.get_workspace() === global.workspace_manager.get_active_workspace()) winList.push(w) }); return winList; }; this._windowsOnOtherWs = function() { return (this.app.get_windows().length - this._getWindowsOnCurrentWs().length) > 0; }; } if (!this._menu) { this._menu = new AppMenu(this, side, { favoritesSection: true, showSingleWindows: true, }); this._menu.setApp(this.app); this._openSigId = this._menu.connect('open-state-changed', (menu, isPoppedUp) => { if (!isPoppedUp) this._onMenuPoppedDown(); }); //Main.overview.connectObject('hiding', this._hidingSigId = Main.overview.connect('hiding', () => this._menu.close(), this); Main.uiGroup.add_actor(this._menu.actor); this._menuManager.addMenu(this._menu); } // once the menu is created, it stays unchanged and we need to modify our items based on current situation if (this._addedMenuItems && this._addedMenuItems.length) { this._addedMenuItems.forEach(i => i.destroy()); } const popupItems =[]; const separator = new PopupMenu.PopupSeparatorMenuItem(); this._menu.addMenuItem(separator); if (this.app.get_n_windows()) { if (/*opt.APP_MENU_FORCE_QUIT*/true) { popupItems.push([_('Force Quit'), () => { this.app.get_windows()[0].kill(); }]); } if (/*opt.APP_MENU_CLOSE_WS*/true) { const nWin = this._getWindowsOnCurrentWs().length; if (nWin) { popupItems.push([_(`Close ${nWin} Windows on Current Workspace`), () => { const windows = this._getWindowsOnCurrentWs(); let time = global.get_current_time(); for (let win of windows) { // increase time by 1 ms for each window to avoid errors from GS win.delete(time++); } }]); } } if (/*opt.APP_MENU_MV_TO_WS && */this._windowsOnOtherWs()) { popupItems.push([_('Move App to Current Workspace'), this._moveAppToCurrentWorkspace]); } } this._addedMenuItems = []; this._addedMenuItems.push(separator); popupItems.forEach(i => { let item = new PopupMenu.PopupMenuItem(i[0]); this._menu.addMenuItem(item); item.connect('activate', i[1].bind(this)); this._addedMenuItems.push(item); }); this.emit('menu-state-changed', true); this._menu.open(BoxPointer.PopupAnimation.FULL); this._menuManager.ignoreRelease(); this.emit('sync-tooltip'); return false; } }