945 lines
35 KiB
JavaScript
945 lines
35 KiB
JavaScript
|
/**
|
||
|
* Vertical Workspaces
|
||
|
* appDisplay.js
|
||
|
*
|
||
|
* @author GdH <G-dH@github.com>
|
||
|
* @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;
|
||
|
}
|
||
|
}
|