Renaming extensions subdirectory for GNOME 48.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
8ed6608e27
commit
d13dfd08e5
189 changed files with 1 additions and 1 deletions
2039
extensions/48/vertical-workspaces/lib/appDisplay.js
Normal file
2039
extensions/48/vertical-workspaces/lib/appDisplay.js
Normal file
File diff suppressed because it is too large
Load diff
79
extensions/48/vertical-workspaces/lib/appFavorites.js
Normal file
79
extensions/48/vertical-workspaces/lib/appFavorites.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* appFavorites.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as AppFavorites from 'resource:///org/gnome/shell/ui/appFavorites.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const AppFavoritesModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('appFavoritesModule');
|
||||
|
||||
// if notifications are enabled no override is needed
|
||||
reset = reset || !this.moduleEnabled || opt.SHOW_FAV_NOTIFICATION;
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
// use actual instance instead of prototype
|
||||
this._overrides.addOverride('AppFavorites', AppFavorites.getAppFavorites(), AppFavoritesCommon);
|
||||
|
||||
console.debug(' AppFavoritesModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._overrides)
|
||||
this._overrides.removeAll();
|
||||
this._overrides = null;
|
||||
|
||||
console.debug(' AppFavoritesModule - Deactivated');
|
||||
}
|
||||
};
|
||||
|
||||
const AppFavoritesCommon = {
|
||||
addFavoriteAtPos(appId, pos) {
|
||||
this._addFavorite(appId, pos);
|
||||
},
|
||||
|
||||
removeFavorite(appId) {
|
||||
this._removeFavorite(appId);
|
||||
},
|
||||
};
|
1333
extensions/48/vertical-workspaces/lib/dash.js
Normal file
1333
extensions/48/vertical-workspaces/lib/dash.js
Normal file
File diff suppressed because it is too large
Load diff
429
extensions/48/vertical-workspaces/lib/iconGrid.js
Normal file
429
extensions/48/vertical-workspaces/lib/iconGrid.js
Normal file
|
@ -0,0 +1,429 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* iconGrid.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import St from 'gi://St';
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as IconGrid from 'resource:///org/gnome/shell/ui/iconGrid.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
// added sizes for better scaling
|
||||
export const IconSize = {
|
||||
LARGEST: 256,
|
||||
224: 224,
|
||||
208: 208,
|
||||
192: 192,
|
||||
176: 176,
|
||||
160: 160,
|
||||
144: 144,
|
||||
128: 128,
|
||||
112: 112,
|
||||
LARGE: 96,
|
||||
80: 80,
|
||||
64: 64,
|
||||
TINY: 48,
|
||||
};
|
||||
|
||||
export const IconGridModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
this._overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon);
|
||||
this._overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon);
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._overrides)
|
||||
this._overrides.removeAll();
|
||||
this._overrides = null;
|
||||
}
|
||||
};
|
||||
|
||||
const IconGridCommon = {
|
||||
getItemsAtPage(page) {
|
||||
if (page < 0 || page >= this.nPages)
|
||||
return [];
|
||||
// throw new Error(`Page ${page} does not exist at IconGrid`);
|
||||
|
||||
const layoutManager = this.layout_manager;
|
||||
return layoutManager.getItemsAtPage(page);
|
||||
},
|
||||
|
||||
_shouldUpdateGrid(width, height) {
|
||||
if (this.layoutManager._isFolder)
|
||||
return false;
|
||||
else if (this._currentMode === -1)
|
||||
return true;
|
||||
|
||||
// Update if page size changed
|
||||
// Page dimensions may change within a small range
|
||||
const range = 5;
|
||||
return (Math.abs(width - (this._gridForWidth ?? 0)) > range) ||
|
||||
(Math.abs(height - (this._gridForHeight ?? 0)) > range);
|
||||
},
|
||||
|
||||
_findBestModeForSize(width, height) {
|
||||
// this function is for main grid only, folder grid calculation is in appDisplay.AppFolderDialog class
|
||||
if (!this._shouldUpdateGrid(width, height))
|
||||
return;
|
||||
|
||||
this._gridForWidth = width;
|
||||
this._gridForHeight = height;
|
||||
|
||||
this._updateDefaultIconSize();
|
||||
const { pagePadding } = this.layout_manager;
|
||||
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
||||
const itemPadding = 55;
|
||||
|
||||
// pagePadding is already affected by the scaleFactor
|
||||
width -= pagePadding.left + pagePadding.right;
|
||||
height -= pagePadding.top + pagePadding.bottom;
|
||||
|
||||
// Sync with _findBestIconSize()
|
||||
this.layoutManager._gridSizeChanged = true;
|
||||
this.layoutManager._gridWidth = width;
|
||||
this.layoutManager._gridHeight = height;
|
||||
|
||||
// All widgets are affected by the scaleFactor so we need to apply it also on the page size
|
||||
width /= scaleFactor;
|
||||
height /= scaleFactor;
|
||||
|
||||
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;
|
||||
const itemSize = iconSize + itemPadding;
|
||||
let columns = opt.APP_GRID_COLUMNS;
|
||||
let rows = opt.APP_GRID_ROWS;
|
||||
// 0 means adaptive size
|
||||
let unusedSpaceH = -1;
|
||||
if (!columns) {
|
||||
// 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 * itemSize - (columns - 1) * spacing;
|
||||
}
|
||||
}
|
||||
let unusedSpaceV = -1;
|
||||
if (!rows) {
|
||||
rows = Math.floor(height / itemSize) + 1;
|
||||
while (unusedSpaceV < 0) {
|
||||
rows -= 1;
|
||||
unusedSpaceV = height - rows * itemSize - ((rows - 1) * spacing);
|
||||
}
|
||||
}
|
||||
|
||||
this._gridModes = [{ columns, rows }];
|
||||
this._currentMode = -1;
|
||||
this._setGridMode(0);
|
||||
this.layoutManager.updateIconSize();
|
||||
// Call _redisplay() from timeout to avoid allocation errors
|
||||
GLib.idle_add(GLib.PRIORITY_LOW, () =>
|
||||
Main.overview._overview.controls.appDisplay._redisplay()
|
||||
);
|
||||
},
|
||||
|
||||
_updateDefaultIconSize() {
|
||||
// Reduce default icon size for low resolution screens and high screen scales
|
||||
if (Me.Util.monitorHasLowResolution()) {
|
||||
opt.APP_GRID_ICON_SIZE_DEFAULT = opt.APP_GRID_ACTIVE_PREVIEW && !opt.APP_GRID_USAGE ? 128 : 64;
|
||||
opt.APP_GRID_FOLDER_ICON_SIZE_DEFAULT = 64;
|
||||
} else {
|
||||
opt.APP_GRID_ICON_SIZE_DEFAULT = opt.APP_GRID_ACTIVE_PREVIEW && !opt.APP_GRID_USAGE ? 192 : 96;
|
||||
}
|
||||
},
|
||||
|
||||
// Workaround for the upstream bug
|
||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5753
|
||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5240
|
||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/6892
|
||||
// The appGridLayout._currentPage is not updated when the page is changed in the grid
|
||||
// For example, when user navigates app icons using a keyboard
|
||||
// Related issues open on GNOME's gitlab:
|
||||
after_goToPage() {
|
||||
if (this._delegate._appGridLayout._currentPage !== this._currentPage)
|
||||
this._delegate._appGridLayout.goToPage(this._currentPage);
|
||||
},
|
||||
|
||||
// Workaround for the upstream bug
|
||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7700
|
||||
// Return INVALID target if x or y is out of the grid view to prevent pages[page] undefined error (horizontal orientation only)
|
||||
getDropTarget(x, y) {
|
||||
if (x < 0 || y < 0)
|
||||
return [0, 0, 0]; // [0, 0, DragLocation.INVALID]
|
||||
const layoutManager = this.layout_manager;
|
||||
return layoutManager.getDropTarget(x, y, this._currentPage);
|
||||
},
|
||||
};
|
||||
|
||||
const IconGridLayoutCommon = {
|
||||
_findBestIconSize() {
|
||||
if (this.fixedIconSize !== -1)
|
||||
return this.fixedIconSize;
|
||||
|
||||
if (!this._isFolder && !this._gridSizeChanged)
|
||||
return this._iconSize;
|
||||
this._gridSizeChanged = false;
|
||||
|
||||
|
||||
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 spacing = this._isFolder
|
||||
? opt.APP_GRID_FOLDER_SPACING
|
||||
: opt.APP_GRID_SPACING;
|
||||
|
||||
const columnSpacingPerPage = spacing * (nColumns - 1);
|
||||
const rowSpacingPerPage = spacing * (nRows - 1);
|
||||
const itemPadding = 55;
|
||||
|
||||
const width = (this._gridWidth ? this._gridWidth : this._pageWidth) / scaleFactor;
|
||||
let height = (this._gridHeight ? this._gridHeight : this._pageHeight) / scaleFactor;
|
||||
|
||||
if (!width || !height)
|
||||
return opt.APP_GRID_ICON_SIZE_DEFAULT;
|
||||
|
||||
const [firstItem] = this._container;
|
||||
|
||||
let iconSizes = Object.values(IconSize).sort((a, b) => b - a);
|
||||
// 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_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_ICON_SIZE < 0)
|
||||
iconSizes = iconSizes.slice(iconSizes.indexOf(opt.APP_GRID_ICON_SIZE_DEFAULT), -1);
|
||||
|
||||
let sizeInvalid = false;
|
||||
for (const size of iconSizes) {
|
||||
let usedWidth, usedHeight;
|
||||
|
||||
if (firstItem) {
|
||||
firstItem.icon.setIconSize(size);
|
||||
const [firstItemWidth] = firstItem.get_preferred_size();
|
||||
|
||||
const itemSize = firstItemWidth / scaleFactor;
|
||||
if (itemSize < size)
|
||||
sizeInvalid = true;
|
||||
|
||||
usedWidth = itemSize * nColumns;
|
||||
usedHeight = itemSize * nRows;
|
||||
}
|
||||
|
||||
if (!firstItem || sizeInvalid) {
|
||||
usedWidth = (size + itemPadding) * nColumns;
|
||||
usedHeight = (size + itemPadding) * nRows;
|
||||
}
|
||||
const emptyHSpace =
|
||||
width - usedWidth - columnSpacingPerPage;
|
||||
const emptyVSpace =
|
||||
height - usedHeight - rowSpacingPerPage;
|
||||
|
||||
if (emptyHSpace >= 0 && emptyVSpace >= 0)
|
||||
return size;
|
||||
}
|
||||
|
||||
return IconSize.TINY;
|
||||
},
|
||||
|
||||
removeItem(item) {
|
||||
if (!this._items.has(item)) {
|
||||
console.error(`iconGrid: Item ${item} is not part of the IconGridLayout`);
|
||||
return;
|
||||
// throw new Error(`Item ${item} is not part of the IconGridLayout`);
|
||||
}
|
||||
|
||||
if (!this._container)
|
||||
return;
|
||||
|
||||
this._shouldEaseItems = true;
|
||||
|
||||
this._container.remove_child(item);
|
||||
this._removeItemData(item);
|
||||
},
|
||||
|
||||
addItem(item, page = -1, index = -1) {
|
||||
if (this._items.has(item)) {
|
||||
console.error(`iconGrid: Item ${item} already added to IconGridLayout`);
|
||||
return;
|
||||
// throw new Error(`Item ${item} already added to IconGridLayout`);
|
||||
}
|
||||
|
||||
if (page > this._pages.length) {
|
||||
console.error(`iconGrid: Cannot add ${item} to page ${page}`);
|
||||
page = -1;
|
||||
index = -1;
|
||||
// throw new Error(`Cannot add ${item} to page ${page}`);
|
||||
}
|
||||
|
||||
if (!this._container)
|
||||
return;
|
||||
|
||||
if (page !== -1 && index === -1)
|
||||
page = this._findBestPageToAppend(page);
|
||||
|
||||
this._shouldEaseItems = true;
|
||||
|
||||
if (!this._container.get_children().includes(item))
|
||||
this._container.add_child(item);
|
||||
this._addItemToPage(item, page, index);
|
||||
},
|
||||
|
||||
moveItem(item, newPage, newPosition) {
|
||||
if (!this._items.has(item)) {
|
||||
console.error(`iconGrid: Item ${item} is not part of the IconGridLayout`);
|
||||
return;
|
||||
// throw new Error(`Item ${item} is not part of the IconGridLayout`);
|
||||
}
|
||||
|
||||
this._shouldEaseItems = true;
|
||||
|
||||
this._removeItemData(item);
|
||||
|
||||
if (newPage !== -1 && newPosition === -1)
|
||||
newPage = this._findBestPageToAppend(newPage);
|
||||
this._addItemToPage(item, newPage, newPosition);
|
||||
},
|
||||
|
||||
_addItemToPage(item, pageIndex, index) {
|
||||
// Ensure we have at least one page
|
||||
if (this._pages.length === 0)
|
||||
this._appendPage();
|
||||
|
||||
// Append a new page if necessary
|
||||
if (pageIndex === this._pages.length)
|
||||
this._appendPage();
|
||||
|
||||
if (pageIndex >= this._pages.length) {
|
||||
pageIndex = -1;
|
||||
index = -1;
|
||||
}
|
||||
|
||||
if (pageIndex === -1)
|
||||
pageIndex = this._pages.length - 1;
|
||||
|
||||
if (index === -1)
|
||||
index = this._pages[pageIndex].children.length;
|
||||
|
||||
this._items.set(item, {
|
||||
actor: item,
|
||||
pageIndex,
|
||||
destroyId: item.connect('destroy', () => this._removeItemData(item)),
|
||||
visibleId: item.connect('notify::visible', () => {
|
||||
const itemData = this._items.get(item);
|
||||
|
||||
this._updateVisibleChildrenForPage(itemData.pageIndex);
|
||||
|
||||
if (item.visible)
|
||||
this._relocateSurplusItems(itemData.pageIndex);
|
||||
else if (!this.allowIncompletePages)
|
||||
this._fillItemVacancies(itemData.pageIndex);
|
||||
}),
|
||||
queueRelayoutId: item.connect('queue-relayout', () => {
|
||||
this._childrenMaxSize = -1;
|
||||
}),
|
||||
});
|
||||
|
||||
item.icon.setIconSize(this._iconSize);
|
||||
this._pages[pageIndex].children.splice(index, 0, item);
|
||||
this._updateVisibleChildrenForPage(pageIndex);
|
||||
this._relocateSurplusItems(pageIndex);
|
||||
},
|
||||
|
||||
_relocateSurplusItems(pageIndex) {
|
||||
// Avoid recursion during relocations in _redisplay()
|
||||
if (this._skipRelocateSurplusItems)
|
||||
return;
|
||||
|
||||
const visiblePageItems = this._pages[pageIndex].visibleChildren;
|
||||
const itemsPerPage = this.columnsPerPage * this.rowsPerPage;
|
||||
|
||||
// No overflow
|
||||
if (visiblePageItems.length <= itemsPerPage)
|
||||
return;
|
||||
|
||||
const nExtraItems = visiblePageItems.length - itemsPerPage;
|
||||
for (let i = 0; i < nExtraItems; i++) {
|
||||
const overflowIndex = visiblePageItems.length - i - 1;
|
||||
const overflowItem = visiblePageItems[overflowIndex];
|
||||
|
||||
this._removeItemData(overflowItem);
|
||||
this._addItemToPage(overflowItem, pageIndex + 1, 0);
|
||||
}
|
||||
},
|
||||
|
||||
_findBestPageToAppend(startPage) {
|
||||
const itemsPerPage = this.columnsPerPage * this.rowsPerPage;
|
||||
|
||||
for (let i = startPage; i < this._pages.length; i++) {
|
||||
const visibleItems = this._pages[i].visibleChildren;
|
||||
|
||||
if (visibleItems.length < itemsPerPage)
|
||||
return i;
|
||||
}
|
||||
|
||||
return this._pages.length;
|
||||
},
|
||||
|
||||
updateIconSize() {
|
||||
const iconSize = this._findBestIconSize();
|
||||
if (this._iconSize !== iconSize) {
|
||||
this._iconSize = iconSize;
|
||||
|
||||
for (const child of this._container)
|
||||
child.icon.setIconSize(iconSize);
|
||||
|
||||
this.notify('icon-size');
|
||||
}
|
||||
},
|
||||
};
|
473
extensions/48/vertical-workspaces/lib/layout.js
Normal file
473
extensions/48/vertical-workspaces/lib/layout.js
Normal file
|
@ -0,0 +1,473 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* layout.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import Meta from 'gi://Meta';
|
||||
import Gio from 'gi://Gio';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
let _timeouts;
|
||||
|
||||
export const LayoutModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
_timeouts = {};
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
this._originalUpdateHotCorners = null;
|
||||
}
|
||||
|
||||
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 (conflict && !reset)
|
||||
console.warn(`[${Me.metadata.name}] Warning: "Layout" module disabled due to potential conflict with another extension`);
|
||||
|
||||
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(' LayoutModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_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();
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._overrides)
|
||||
this._overrides.removeAll();
|
||||
this._overrides = null;
|
||||
|
||||
Main.layoutManager._updateHotCorners();
|
||||
|
||||
if (this._hotCornersEnabledConId) {
|
||||
this._interfaceSettings.disconnect(this._hotCornersEnabledConId);
|
||||
this._hotCornersEnabledConId = 0;
|
||||
this._interfaceSettings = null;
|
||||
}
|
||||
|
||||
console.debug(' LayoutModule - Disabled');
|
||||
}
|
||||
|
||||
_removeTimeouts() {
|
||||
if (_timeouts) {
|
||||
Object.values(_timeouts).forEach(t => {
|
||||
if (t)
|
||||
GLib.source_remove(t);
|
||||
});
|
||||
_timeouts = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const LayoutManagerCommon = {
|
||||
_updatePanelBarrier() {
|
||||
if (this._rightPanelBarrier) {
|
||||
this._rightPanelBarrier.destroy();
|
||||
this._rightPanelBarrier = null;
|
||||
}
|
||||
|
||||
if (this._leftPanelBarrier) {
|
||||
this._leftPanelBarrier.destroy();
|
||||
this._leftPanelBarrier = null;
|
||||
}
|
||||
|
||||
if (!this.primaryMonitor || !opt || Me.Util.getEnabledExtensions('hidetopbar'))
|
||||
return;
|
||||
|
||||
if (this.panelBox.height) {
|
||||
const backend = !!Meta.Barrier.prototype.backend;
|
||||
let params = {};
|
||||
if (backend)
|
||||
params['backend'] = global.backend;
|
||||
else
|
||||
params['display'] = global.display;
|
||||
|
||||
let primary = this.primaryMonitor;
|
||||
if ([0, 1, 3].includes(opt.HOT_CORNER_POSITION)) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: primary.x + primary.width, y1: this.panelBox.allocation.y1,
|
||||
x2: primary.x + primary.width, y2: this.panelBox.allocation.y2,
|
||||
directions: Meta.BarrierDirection.NEGATIVE_X,
|
||||
});
|
||||
this._rightPanelBarrier = new Meta.Barrier(params);
|
||||
}
|
||||
|
||||
if ([2, 4].includes(opt.HOT_CORNER_POSITION)) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: primary.x, y1: this.panelBox.allocation.y1,
|
||||
x2: primary.x, y2: this.panelBox.allocation.y2,
|
||||
directions: Meta.BarrierDirection.POSITIVE_X,
|
||||
});
|
||||
this._leftPanelBarrier = new Meta.Barrier(params);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_updateHotCorners() {
|
||||
// avoid errors if called from foreign override
|
||||
if (!opt)
|
||||
return;
|
||||
|
||||
// destroy old hot corners
|
||||
this.hotCorners.forEach(corner => corner?.destroy());
|
||||
this.hotCorners = [];
|
||||
|
||||
if (!this._interfaceSettings.get_boolean('enable-hot-corners')) {
|
||||
this.emit('hot-corners-changed');
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// build new hot corners
|
||||
for (let i = 0; i < this.monitors.length; i++) {
|
||||
let monitor = this.monitors[i];
|
||||
let cornerX, cornerY;
|
||||
|
||||
if (position === 0) {
|
||||
cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
|
||||
cornerY = monitor.y;
|
||||
} else if (position === 1) {
|
||||
cornerX = monitor.x;
|
||||
cornerY = monitor.y;
|
||||
} else if (position === 2) {
|
||||
cornerX = monitor.x + monitor.width;
|
||||
cornerY = monitor.y;
|
||||
} else if (position === 3) {
|
||||
cornerX = monitor.x;
|
||||
cornerY = monitor.y + monitor.height;
|
||||
} else {
|
||||
cornerX = monitor.x + monitor.width;
|
||||
cornerY = monitor.y + monitor.height;
|
||||
}
|
||||
|
||||
let haveCorner = true;
|
||||
|
||||
if (i !== this.primaryIndex) {
|
||||
// Check if we have a top left (right for RTL) corner.
|
||||
// I.e. if there is no monitor directly above or to the left(right)
|
||||
let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
|
||||
let besideY = cornerY;
|
||||
let aboveX = cornerX;
|
||||
let aboveY = cornerY - 1;
|
||||
|
||||
for (let j = 0; j < this.monitors.length; j++) {
|
||||
if (i === j)
|
||||
continue;
|
||||
let otherMonitor = this.monitors[j];
|
||||
if (besideX >= otherMonitor.x &&
|
||||
besideX < otherMonitor.x + otherMonitor.width &&
|
||||
besideY >= otherMonitor.y &&
|
||||
besideY < otherMonitor.y + otherMonitor.height) {
|
||||
haveCorner = false;
|
||||
break;
|
||||
}
|
||||
if (aboveX >= otherMonitor.x &&
|
||||
aboveX < otherMonitor.x + otherMonitor.width &&
|
||||
aboveY >= otherMonitor.y &&
|
||||
aboveY < otherMonitor.y + otherMonitor.height) {
|
||||
haveCorner = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (haveCorner) {
|
||||
let corner = new Layout.HotCorner(this, monitor, cornerX, cornerY);
|
||||
corner.setBarrierSize(size, false);
|
||||
this.hotCorners.push(corner);
|
||||
} else {
|
||||
this.hotCorners.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('hot-corners-changed');
|
||||
},
|
||||
};
|
||||
|
||||
const HotCornerCommon = {
|
||||
after__init() {
|
||||
let angle = 0;
|
||||
switch (opt.HOT_CORNER_POSITION) {
|
||||
case 2:
|
||||
angle = 90;
|
||||
break;
|
||||
case 3:
|
||||
angle = 270;
|
||||
break;
|
||||
case 4:
|
||||
angle = 180;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (this._verticalBarrier) {
|
||||
this._pressureBarrier.removeBarrier(this._verticalBarrier);
|
||||
this._verticalBarrier.destroy();
|
||||
this._verticalBarrier = null;
|
||||
}
|
||||
|
||||
if (this._horizontalBarrier) {
|
||||
this._pressureBarrier.removeBarrier(this._horizontalBarrier);
|
||||
this._horizontalBarrier.destroy();
|
||||
this._horizontalBarrier = null;
|
||||
}
|
||||
|
||||
if (size > 0) {
|
||||
const primaryMonitor = global.display.get_primary_monitor();
|
||||
const monitor = this._monitor;
|
||||
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;
|
||||
|
||||
const backend = !!Meta.Barrier.prototype.backend;
|
||||
let params = {};
|
||||
if (backend)
|
||||
params['backend'] = global.backend;
|
||||
else
|
||||
params['display'] = global.display;
|
||||
|
||||
if (opt.HOT_CORNER_POSITION <= 1) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x,
|
||||
y1: this._y, y2: this._y + (extendV ? monitor.height : size),
|
||||
directions: Meta.BarrierDirection.POSITIVE_X,
|
||||
});
|
||||
this._verticalBarrier = new Meta.Barrier(params);
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x + (extendH ? monitor.width : size),
|
||||
y1: this._y, y2: this._y,
|
||||
directions: Meta.BarrierDirection.POSITIVE_Y,
|
||||
});
|
||||
this._horizontalBarrier = new Meta.Barrier(params);
|
||||
} else if (opt.HOT_CORNER_POSITION === 2) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x,
|
||||
y1: this._y, y2: this._y + (extendV ? monitor.height : size),
|
||||
directions: Meta.BarrierDirection.NEGATIVE_X,
|
||||
});
|
||||
this._verticalBarrier = new Meta.Barrier(params);
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x - size, x2: this._x,
|
||||
y1: this._y, y2: this._y,
|
||||
directions: Meta.BarrierDirection.POSITIVE_Y,
|
||||
});
|
||||
this._horizontalBarrier = new Meta.Barrier(params);
|
||||
} else if (opt.HOT_CORNER_POSITION === 3) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x,
|
||||
y1: this._y, y2: this._y - size,
|
||||
directions: Meta.BarrierDirection.POSITIVE_X,
|
||||
});
|
||||
this._verticalBarrier = new Meta.Barrier(params);
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x + (extendH ? monitor.width : size),
|
||||
y1: this._y, y2: this._y,
|
||||
directions: Meta.BarrierDirection.NEGATIVE_Y,
|
||||
});
|
||||
this._horizontalBarrier = new Meta.Barrier(params);
|
||||
} else if (opt.HOT_CORNER_POSITION === 4) {
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x,
|
||||
y1: this._y, y2: this._y - size,
|
||||
directions: Meta.BarrierDirection.NEGATIVE_X,
|
||||
});
|
||||
this._verticalBarrier = new Meta.Barrier(params);
|
||||
params = Object.assign({}, params, {
|
||||
x1: this._x, x2: this._x - size,
|
||||
y1: this._y, y2: this._y,
|
||||
directions: Meta.BarrierDirection.NEGATIVE_Y,
|
||||
});
|
||||
this._horizontalBarrier = new Meta.Barrier(params);
|
||||
}
|
||||
|
||||
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 (Main.overview._shown) {
|
||||
this._toggleWindowPicker(true);
|
||||
} 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 === 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, customOverviewMode = false) {
|
||||
if (Main.overview._shown && (leaveOverview || !Main.overview.dash.showAppsButton.checked)) {
|
||||
Main.overview.hide();
|
||||
} else if (Main.overview.dash.showAppsButton.checked) {
|
||||
Main.overview.dash.showAppsButton.checked = false;
|
||||
} else {
|
||||
const focusWindow = global.display.get_focus_window();
|
||||
// at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard
|
||||
if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) {
|
||||
// following should help when windowed VBox Machine has focus.
|
||||
global.stage.set_key_focus(Main.panel);
|
||||
// key focus doesn't take the effect immediately, we must wait for it
|
||||
// still looking for better solution!
|
||||
_timeouts.releaseKeyboardTimeoutId = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
// delay cannot be too short
|
||||
200,
|
||||
() => {
|
||||
Main.overview.show(1, customOverviewMode);
|
||||
|
||||
_timeouts.releaseKeyboardTimeoutId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Main.overview.show(1, customOverviewMode);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_toggleApplications(leaveOverview = false) {
|
||||
if ((leaveOverview && Main.overview._shown) || Main.overview.dash.showAppsButton.checked) {
|
||||
Main.overview.hide();
|
||||
} else {
|
||||
const focusWindow = global.display.get_focus_window();
|
||||
// at least GS 42 is unable to show overview in X11 session if VirtualBox Machine window grabbed keyboard
|
||||
if (!Meta.is_wayland_compositor() && focusWindow && focusWindow.wm_class.includes('VirtualBox Machine')) {
|
||||
// following should help when windowed VBox Machine has focus.
|
||||
global.stage.set_key_focus(Main.panel);
|
||||
// key focus doesn't take the effect immediately, we must wait for it
|
||||
// still looking for better solution!
|
||||
_timeouts.releaseKeyboardTimeoutId = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
// delay cannot be too short
|
||||
200,
|
||||
() => {
|
||||
Main.overview.show(2);
|
||||
|
||||
_timeouts.releaseKeyboardTimeoutId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
);
|
||||
} else if (Main.overview._shown) {
|
||||
Main.overview.dash.showAppsButton.checked = true;
|
||||
} else {
|
||||
Main.overview.show(2); // 2 for App Grid
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_toggleWindowSearchProvider() {
|
||||
if (!Main.overview.searchController._searchActive) {
|
||||
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);
|
||||
// searchEntry.grab_key_focus();
|
||||
searchEntry.get_first_child().set_cursor_position(position);
|
||||
searchEntry.get_first_child().set_selection(position, position);
|
||||
} else {
|
||||
// Main.overview.searchEntry.text = '';
|
||||
Main.overview.hide();
|
||||
}
|
||||
},
|
||||
};
|
91
extensions/48/vertical-workspaces/lib/messageTray.js
Normal file
91
extensions/48/vertical-workspaces/lib/messageTray.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* messageTray.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const MessageTrayModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
};
|
496
extensions/48/vertical-workspaces/lib/optionsFactory.js
Normal file
496
extensions/48/vertical-workspaces/lib/optionsFactory.js
Normal file
|
@ -0,0 +1,496 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* optionsFactory.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Adw from 'gi://Adw';
|
||||
import Gio from 'gi://Gio';
|
||||
import GObject from 'gi://GObject';
|
||||
import Gtk from 'gi://Gtk';
|
||||
|
||||
let Me;
|
||||
|
||||
// gettext
|
||||
let _;
|
||||
|
||||
export function init(me) {
|
||||
Me = me;
|
||||
_ = Me.gettext;
|
||||
}
|
||||
|
||||
export const ItemFactory = class ItemFactory {
|
||||
constructor() {
|
||||
this._settings = Me.Opt._gsettings;
|
||||
}
|
||||
|
||||
getRowWidget(text, caption, widget, variable, options = [], dependsOn) {
|
||||
let item = [];
|
||||
let label;
|
||||
if (widget) {
|
||||
label = new Gtk.Box({
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
spacing: 4,
|
||||
halign: Gtk.Align.START,
|
||||
valign: Gtk.Align.CENTER,
|
||||
});
|
||||
const option = new Gtk.Label({
|
||||
halign: Gtk.Align.START,
|
||||
});
|
||||
option.set_text(text);
|
||||
label.append(option);
|
||||
|
||||
if (caption) {
|
||||
const captionLabel = new Gtk.Label({
|
||||
halign: Gtk.Align.START,
|
||||
wrap: true,
|
||||
/* width_chars: 80, */
|
||||
xalign: 0,
|
||||
});
|
||||
const context = captionLabel.get_style_context();
|
||||
context.add_class('dim-label');
|
||||
context.add_class('caption');
|
||||
captionLabel.set_text(caption);
|
||||
label.append(captionLabel);
|
||||
}
|
||||
label._title = text;
|
||||
} else {
|
||||
label = text;
|
||||
}
|
||||
item.push(label);
|
||||
item.push(widget);
|
||||
|
||||
let key;
|
||||
|
||||
if (variable && Me.Opt.options[variable]) {
|
||||
const opt = Me.Opt.options[variable];
|
||||
key = opt[1];
|
||||
}
|
||||
|
||||
if (widget) {
|
||||
if (widget._isSwitch)
|
||||
this._connectSwitch(widget, key, variable);
|
||||
else if (widget._isSpinButton || widget._isScale)
|
||||
this._connectSpinButton(widget, key, variable);
|
||||
else if (widget._isComboBox)
|
||||
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;
|
||||
}
|
||||
|
||||
_connectSwitch(widget, key /* , variable */) {
|
||||
this._settings.bind(key, widget, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
|
||||
_connectSpinButton(widget, key /* , variable */) {
|
||||
this._settings.bind(key, widget.adjustment, 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
|
||||
_connectComboBox(widget, key, variable, options) {
|
||||
let model = widget.get_model();
|
||||
widget._comboMap = {};
|
||||
const currentValue = Me.Opt.get(variable);
|
||||
for (const [label, value] of options) {
|
||||
let iter;
|
||||
model.set(iter = model.append(), [0, 1], [label, value]);
|
||||
if (value === currentValue)
|
||||
widget.set_active_iter(iter);
|
||||
|
||||
widget._comboMap[value] = iter;
|
||||
}
|
||||
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();
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
Me.Opt.set(variable, model.get_value(iter, 1));
|
||||
});
|
||||
}
|
||||
|
||||
_connectDropDown(widget, key, variable, options) {
|
||||
const model = widget.get_model();
|
||||
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 }));
|
||||
if (id === currentValue)
|
||||
widget.set_selected(i);
|
||||
}
|
||||
|
||||
const factory = new Gtk.SignalListItemFactory();
|
||||
factory.connect('setup', (fact, listItem) => {
|
||||
const label = new Gtk.Label({ xalign: 0 });
|
||||
listItem.set_child(label);
|
||||
});
|
||||
factory.connect('bind', (fact, listItem) => {
|
||||
const label = listItem.get_child();
|
||||
const item = listItem.get_item();
|
||||
label.set_text(item.text);
|
||||
});
|
||||
|
||||
widget.connect('notify::selected-item', dropDown => {
|
||||
const item = dropDown.get_selected_item();
|
||||
Me.Opt.set(variable, item.id);
|
||||
});
|
||||
|
||||
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)
|
||||
widget.set_selected(i);
|
||||
}
|
||||
});
|
||||
|
||||
widget.set_factory(factory);
|
||||
}
|
||||
|
||||
newSwitch() {
|
||||
let sw = new Gtk.Switch({
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
});
|
||||
sw._isSwitch = true;
|
||||
return sw;
|
||||
}
|
||||
|
||||
newSpinButton(adjustment) {
|
||||
let spinButton = new Gtk.SpinButton({
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
xalign: 0.5,
|
||||
});
|
||||
spinButton.set_adjustment(adjustment);
|
||||
spinButton._isSpinButton = true;
|
||||
return spinButton;
|
||||
}
|
||||
|
||||
newComboBox() {
|
||||
const model = new Gtk.ListStore();
|
||||
model.set_column_types([GObject.TYPE_STRING, GObject.TYPE_INT]);
|
||||
const comboBox = new Gtk.ComboBox({
|
||||
model,
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
});
|
||||
const renderer = new Gtk.CellRendererText();
|
||||
comboBox.pack_start(renderer, true);
|
||||
comboBox.add_attribute(renderer, 'text', 0);
|
||||
comboBox._isComboBox = true;
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
newDropDown() {
|
||||
const dropDown = new Gtk.DropDown({
|
||||
model: new Gio.ListStore({
|
||||
item_type: DropDownItem,
|
||||
}),
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
});
|
||||
dropDown._isDropDown = true;
|
||||
return dropDown;
|
||||
}
|
||||
|
||||
newScale(adjustment) {
|
||||
const scale = new Gtk.Scale({
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
draw_value: true,
|
||||
has_origin: false,
|
||||
value_pos: Gtk.PositionType.LEFT,
|
||||
digits: 0,
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
});
|
||||
scale.set_size_request(300, -1);
|
||||
scale.set_adjustment(adjustment);
|
||||
scale._isScale = true;
|
||||
return scale;
|
||||
}
|
||||
|
||||
newLabel(text = '') {
|
||||
const label = new Gtk.Label({
|
||||
label: text,
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
});
|
||||
label._activatable = false;
|
||||
return label;
|
||||
}
|
||||
|
||||
newLinkButton(uri) {
|
||||
const linkBtn = new Gtk.LinkButton({
|
||||
uri,
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
icon_name: 'emblem-symbolic-link',
|
||||
});
|
||||
return linkBtn;
|
||||
}
|
||||
|
||||
newButton() {
|
||||
const btn = new Gtk.Button({
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
});
|
||||
|
||||
btn._activatable = true;
|
||||
return btn;
|
||||
}
|
||||
|
||||
newPresetButton(opt, profileIndex) {
|
||||
const load = opt.loadProfile.bind(opt);
|
||||
const save = opt.storeProfile.bind(opt);
|
||||
const reset = opt.resetProfile.bind(opt);
|
||||
|
||||
const box = new Gtk.Box({
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
spacing: 8,
|
||||
});
|
||||
box.is_profile_box = true;
|
||||
|
||||
const entry = new Gtk.Entry({
|
||||
width_chars: 45,
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
xalign: 0,
|
||||
});
|
||||
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);
|
||||
|
||||
const resetProfile = this.newButton();
|
||||
resetProfile.set({
|
||||
tooltip_text: _('Reset profile to defaults'),
|
||||
icon_name: 'document-revert-symbolic',
|
||||
hexpand: false,
|
||||
css_classes: ['destructive-action'],
|
||||
});
|
||||
|
||||
function setName() {
|
||||
const ProfileNames = [
|
||||
_('GNOME 3 Layout (Vertical WS)'),
|
||||
_('GNOME 4x Layout, Bottom Hot Edge (Horizontal WS)'),
|
||||
_('Top Left Hot Corner Centric (Vertical WS)'),
|
||||
_('Dock-Like Overview, Bottom Hot Edge (Horizontal WS)'),
|
||||
];
|
||||
|
||||
let name = opt.get(`profileName${profileIndex}`, true);
|
||||
if (!name)
|
||||
name = ProfileNames[profileIndex - 1];
|
||||
entry.set_text(name);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
resetProfile._activatable = false;
|
||||
|
||||
const loadProfile = this.newButton();
|
||||
loadProfile.set({
|
||||
tooltip_text: _('Load profile'),
|
||||
icon_name: 'view-refresh-symbolic',
|
||||
hexpand: false,
|
||||
});
|
||||
loadProfile.connect('clicked', () => load(profileIndex));
|
||||
loadProfile._activatable = false;
|
||||
|
||||
const saveProfile = this.newButton();
|
||||
saveProfile.set({
|
||||
tooltip_text: _('Save current settings into this profile'),
|
||||
icon_name: 'document-save-symbolic',
|
||||
hexpand: false,
|
||||
});
|
||||
saveProfile.connect('clicked', () => save(profileIndex));
|
||||
saveProfile._activatable = false;
|
||||
|
||||
box.append(resetProfile);
|
||||
box.append(entry);
|
||||
box.append(saveProfile);
|
||||
box.append(loadProfile);
|
||||
return box;
|
||||
}
|
||||
|
||||
newResetButton(callback) {
|
||||
const btn = this.newButton();
|
||||
btn.set({
|
||||
css_classes: ['destructive-action'],
|
||||
icon_name: 'edit-delete-symbolic',
|
||||
});
|
||||
|
||||
btn.connect('clicked', callback);
|
||||
btn._activatable = false;
|
||||
return btn;
|
||||
}
|
||||
|
||||
newOptionsResetButton() {
|
||||
const btn = new Gtk.Button({
|
||||
halign: Gtk.Align.END,
|
||||
valign: Gtk.Align.CENTER,
|
||||
hexpand: true,
|
||||
css_classes: ['destructive-action'],
|
||||
icon_name: 'document-revert-symbolic',
|
||||
});
|
||||
|
||||
btn.connect('clicked', () => {
|
||||
const settings = this._settings;
|
||||
settings.list_keys().forEach(
|
||||
key => settings.reset(key)
|
||||
);
|
||||
});
|
||||
btn._activatable = false;
|
||||
return btn;
|
||||
}
|
||||
};
|
||||
|
||||
export const AdwPrefs = class {
|
||||
constructor(gOptions) {
|
||||
Me.Opt = gOptions;
|
||||
}
|
||||
|
||||
getFilledWindow(window, pages) {
|
||||
for (let page of pages) {
|
||||
const title = page.title;
|
||||
const iconName = page.iconName;
|
||||
const optionList = page.optionList;
|
||||
|
||||
window.add(
|
||||
this._getAdwPage(optionList, {
|
||||
title,
|
||||
icon_name: iconName,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
window.set_search_enabled(true);
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
_getAdwPage(optionList, pageProperties = {}) {
|
||||
// pageProperties.width_request = 740;
|
||||
const page = new Adw.PreferencesPage(pageProperties);
|
||||
let group;
|
||||
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) {
|
||||
if (group)
|
||||
page.add(group);
|
||||
|
||||
group = new Adw.PreferencesGroup({
|
||||
title: option,
|
||||
hexpand: true,
|
||||
width_request: 700,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const row = new Adw.ActionRow({
|
||||
title: option._title,
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
/* for (let i of item) {
|
||||
box.append(i);*/
|
||||
grid.attach(option, 0, 0, 1, 1);
|
||||
if (widget)
|
||||
grid.attach(widget, 1, 0, 1, 1);
|
||||
|
||||
row.set_child(grid);
|
||||
if (widget._activatable === false)
|
||||
row.activatable = false;
|
||||
else
|
||||
row.activatable_widget = widget;
|
||||
|
||||
group.add(row);
|
||||
}
|
||||
page.add(group);
|
||||
return page;
|
||||
}
|
||||
};
|
||||
|
||||
const DropDownItem = GObject.registerClass({
|
||||
// Registered name should be unique
|
||||
GTypeName: `DropDownItem${Math.floor(Math.random() * 1000)}`,
|
||||
Properties: {
|
||||
'text': GObject.ParamSpec.string(
|
||||
'text',
|
||||
'Text',
|
||||
'DropDown item text',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
''
|
||||
),
|
||||
'id': GObject.ParamSpec.int(
|
||||
'id',
|
||||
'Id',
|
||||
'Item id stored in settings',
|
||||
GObject.ParamFlags.READWRITE,
|
||||
// min, max, default
|
||||
-2147483648, 2147483647, 0
|
||||
),
|
||||
},
|
||||
}, class DropDownItem extends GObject.Object {
|
||||
get text() {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
set text(text) {
|
||||
this._text = text;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
set id(id) {
|
||||
this._id = id;
|
||||
}
|
||||
});
|
118
extensions/48/vertical-workspaces/lib/osdWindow.js
Normal file
118
extensions/48/vertical-workspaces/lib/osdWindow.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* osdWindow.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as OsdWindow from 'resource:///org/gnome/shell/ui/osdWindow.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
let OsdPositions;
|
||||
|
||||
export const OsdWindowModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
OsdPositions = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('osdWindowModule');
|
||||
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(' 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() {
|
||||
if (!opt.OSD_POSITION)
|
||||
this.opacity = 0;
|
||||
this.set(OsdPositions[opt.OSD_POSITION]);
|
||||
},
|
||||
};
|
170
extensions/48/vertical-workspaces/lib/overlayKey.js
Normal file
170
extensions/48/vertical-workspaces/lib/overlayKey.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* overlayKey.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import St from 'gi://St';
|
||||
import Meta from 'gi://Meta';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const OverlayKeyModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._originalOverlayKeyHandlerId = 0;
|
||||
this._overlayKeyHandlerId = 0;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('overlayKeyModule');
|
||||
const conflict = false;
|
||||
// Avoid modifying the overlay key if its configuration is consistent with the GNOME default
|
||||
const defaultConfig = opt.OVERVIEW_MODE === 0 && opt.OVERLAY_KEY_PRIMARY === 2 && opt.OVERLAY_KEY_SECONDARY === 1;
|
||||
|
||||
reset = reset || !this.moduleEnabled || conflict || defaultConfig;
|
||||
|
||||
if (reset && !this._firstActivation) {
|
||||
this._disableModule();
|
||||
} else if (!reset) {
|
||||
this._firstActivation = false;
|
||||
this._activateModule();
|
||||
}
|
||||
if (reset && this._firstActivation)
|
||||
console.debug(' OverlayKeyModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_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');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
this._restoreOverlayKeyHandler();
|
||||
|
||||
console.debug(' OverlayKeyModule - Disabled');
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
_connectOverlayKey() {
|
||||
if (this._overlayKeyHandlerId)
|
||||
return;
|
||||
|
||||
this._overlayKeyHandlerId = global.display.connect('overlay-key', this._onOverlayKeyPressed.bind(Main.overview._overview.controls));
|
||||
}
|
||||
|
||||
_onOverlayKeyPressed() {
|
||||
if (this._a11ySettings.get_boolean('stickykeys-enable'))
|
||||
return;
|
||||
|
||||
const { initialState, finalState, transitioning } =
|
||||
this._stateAdjustment.getStateTransitionParams();
|
||||
|
||||
const time = GLib.get_monotonic_time() / 1000;
|
||||
const timeDiff = time - this._lastOverlayKeyTime;
|
||||
this._lastOverlayKeyTime = time;
|
||||
|
||||
const shouldShift = St.Settings.get().enable_animations
|
||||
? transitioning && finalState > initialState
|
||||
: Main.overview.visible && timeDiff < Overview.ANIMATION_TIME;
|
||||
|
||||
const mode = opt.OVERLAY_KEY_SECONDARY;
|
||||
if (shouldShift) {
|
||||
Me.Util.activateSearchProvider('');
|
||||
if (mode === 1) {
|
||||
this._shiftState(Meta.MotionDirection.UP);
|
||||
} 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 {
|
||||
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: // App grid
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
167
extensions/48/vertical-workspaces/lib/overview.js
Normal file
167
extensions/48/vertical-workspaces/lib/overview.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* overview.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';
|
||||
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const OverviewModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
if (this._shownState !== 'SHOWN')
|
||||
this._changeShownState('SHOWN');
|
||||
|
||||
// Handle any calls to hide* while we were showing
|
||||
if (!this._shown)
|
||||
this._animateNotVisible();
|
||||
|
||||
// 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) {
|
||||
if (opt.SEARCH_VIEW_ANIMATION)
|
||||
controls._onSearchChanged();
|
||||
else if (!opt.OVERVIEW_MODE2)
|
||||
controls._stateAdjustment.value = 2;
|
||||
}
|
||||
|
||||
this._syncGrab();
|
||||
if (controls._stateAdjustment.value <= 1 && !controls._searchController.searchActive)
|
||||
Me.Util.activateKeyboardForWorkspaceView();
|
||||
},
|
||||
|
||||
after__hideDone() {
|
||||
this.resetOverviewMode();
|
||||
|
||||
if (!opt.FIX_NEW_WINDOW_FOCUS)
|
||||
return;
|
||||
|
||||
// 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
|
||||
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());
|
||||
},
|
||||
};
|
||||
|
1747
extensions/48/vertical-workspaces/lib/overviewControls.js
Normal file
1747
extensions/48/vertical-workspaces/lib/overviewControls.js
Normal file
File diff suppressed because it is too large
Load diff
272
extensions/48/vertical-workspaces/lib/panel.js
Normal file
272
extensions/48/vertical-workspaces/lib/panel.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* panel.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2025
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
const ANIMATION_TIME = Overview.ANIMATION_TIME;
|
||||
|
||||
export const PanelModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
|
||||
this._showingOverviewConId = 0;
|
||||
this._hidingOverviewConId = 0;
|
||||
this._styleChangedConId = 0;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('panelModule');
|
||||
const conflict = Me.Util.getEnabledExtensions('dash-to-panel').length ||
|
||||
Me.Util.getEnabledExtensions('hidetopbar').length;
|
||||
|
||||
if (conflict && !reset)
|
||||
console.warn(`[${Me.metadata.name}] Warning: "Panel" module disabled due to potential conflict with another extension`);
|
||||
|
||||
reset = reset || !this.moduleEnabled || conflict;
|
||||
|
||||
this.moduleEnabled = !reset;
|
||||
|
||||
// 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;
|
||||
panelBox.scale_y = 1;
|
||||
|
||||
this._setPanelPosition();
|
||||
this._updateStyleChangedConnection();
|
||||
|
||||
if (!opt.PANEL_MODE) {
|
||||
this._updateOverviewConnection(true);
|
||||
this._reparentPanel(false);
|
||||
panelBox.translation_y = 0;
|
||||
Main.panel.opacity = 255;
|
||||
this._setPanelStructs(true);
|
||||
} else if (opt.PANEL_OVERVIEW_ONLY) {
|
||||
if (opt.SHOW_WS_PREVIEW_BG) {
|
||||
this._reparentPanel(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_DISABLED) {
|
||||
this._updateOverviewConnection(true);
|
||||
this._reparentPanel(false);
|
||||
panelBox.scale_y = 0;
|
||||
// _connectPanel();
|
||||
}
|
||||
this._setPanelStructs(!opt.PANEL_MODE);
|
||||
Main.layoutManager._updateHotCorners();
|
||||
Main.overview._overview.controls.layoutManager._updateWorkAreaBox();
|
||||
|
||||
this._overrides.addOverride('ActivitiesButton', Main.panel.statusArea.activities, ActivitiesButton);
|
||||
|
||||
console.debug(' PanelModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
const reset = true;
|
||||
this._setPanelPosition(reset);
|
||||
this._updateOverviewConnection(reset);
|
||||
this._reparentPanel(false);
|
||||
|
||||
this._updateStyleChangedConnection(reset);
|
||||
|
||||
const panelBox = Main.layoutManager.panelBox;
|
||||
panelBox.scale_y = 1;
|
||||
panelBox.translation_y = 0;
|
||||
Main.panel.opacity = 255;
|
||||
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', () => {
|
||||
this._updateStyle();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_updateStyle() {
|
||||
if (opt.OVERVIEW_MODE2 || !opt.PANEL_OVERVIEW_STYLE)
|
||||
Main.panel.remove_style_pseudo_class('overview');
|
||||
else if (opt.PANEL_OVERVIEW_ONLY && !opt.OVERVIEW_MODE2)
|
||||
Main.panel.add_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;
|
||||
}
|
||||
} else {
|
||||
if (!this._hidingOverviewConId) {
|
||||
this._hidingOverviewConId = Main.overview.connect('hiding', () => {
|
||||
this._showPanel(false);
|
||||
});
|
||||
}
|
||||
if (!this._showingOverviewConId) {
|
||||
this._showingOverviewConId = Main.overview.connect('showing', () => {
|
||||
if (Main.layoutManager._startingUp)
|
||||
return;
|
||||
this._updateStyle();
|
||||
this._showPanel(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_reparentPanel(reparent = false) {
|
||||
const panel = Main.layoutManager.panelBox;
|
||||
if (reparent && panel.get_parent() === Main.layoutManager.uiGroup && !Main.sessionMode.isLocked) {
|
||||
Main.layoutManager.uiGroup.remove_child(panel);
|
||||
Main.layoutManager.overviewGroup.add_child(panel);
|
||||
} else if ((!reparent || Main.sessionMode.isLocked) && 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);
|
||||
}
|
||||
}
|
||||
|
||||
_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));*/
|
||||
}
|
||||
|
||||
_showPanel(show = true) {
|
||||
const panelBox = Main.layoutManager.panelBox;
|
||||
const panelHeight = Main.panel.height;
|
||||
const overviewGroup = Main.layoutManager.overviewGroup;
|
||||
|
||||
if (panelBox.get_parent() === overviewGroup) {
|
||||
if (opt.OVERVIEW_MODE2)
|
||||
overviewGroup.set_child_above_sibling(panelBox, null);
|
||||
else
|
||||
overviewGroup.set_child_below_sibling(panelBox, Main.overview._overview);
|
||||
}
|
||||
|
||||
if (opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2 && !Main.layoutManager.panelBox.translation_y)
|
||||
return;
|
||||
|
||||
if (show) {
|
||||
panelBox.translation_y = opt.PANEL_POSITION_TOP ? -panelHeight : panelHeight;
|
||||
Main.panel.opacity = 255;
|
||||
let delay = 0;
|
||||
// Panel animation needs to wait until overview is visible
|
||||
if (opt.DELAY_OVERVIEW_ANIMATION)
|
||||
delay = global.display.get_tab_list(0, null).length * opt.DELAY_PER_WINDOW + 50;
|
||||
panelBox.ease({
|
||||
delay,
|
||||
duration: ANIMATION_TIME,
|
||||
translation_y: 0,
|
||||
onComplete: () => {
|
||||
this._setPanelStructs(!opt.PANEL_MODE);
|
||||
},
|
||||
});
|
||||
} else if (!Main.layoutManager._startingUp) {
|
||||
panelBox.translation_y = 0;
|
||||
panelBox.ease({
|
||||
duration: ANIMATION_TIME,
|
||||
translation_y: opt.PANEL_POSITION_TOP ? -panelHeight : panelHeight,
|
||||
onComplete: () => {
|
||||
Main.panel.opacity = 0;
|
||||
this._setPanelStructs(!opt.PANEL_MODE);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Main.wm.handleWorkspaceScroll(event);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,316 @@
|
|||
/**
|
||||
* Vertical Workspaces
|
||||
* recentFilesSearchProvider.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import St from 'gi://St';
|
||||
import Gio from 'gi://Gio';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
// gettext
|
||||
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
|
||||
export const PREFIX = 'fq//';
|
||||
const ID = 'recent-files';
|
||||
|
||||
export const 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;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
_ = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('recentFilesSearchProviderModule');
|
||||
|
||||
reset = reset || !this.moduleEnabled;
|
||||
|
||||
if (reset && !this._firstActivation) {
|
||||
this._disableModule();
|
||||
} else if (!reset) {
|
||||
this._firstActivation = false;
|
||||
this._activateModule();
|
||||
}
|
||||
if (reset && this._firstActivation)
|
||||
console.debug(' RecentFilesSearchProviderModule - 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._recentFilesSearchProvider) {
|
||||
this._recentFilesSearchProvider = new RecentFilesSearchProvider();
|
||||
this._registerProvider(this._recentFilesSearchProvider);
|
||||
}
|
||||
this._enableTimeoutId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
);
|
||||
|
||||
console.debug(' RecentFilesSearchProviderModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._recentFilesSearchProvider) {
|
||||
this._unregisterProvider(this._recentFilesSearchProvider);
|
||||
this._recentFilesSearchProvider = null;
|
||||
}
|
||||
if (this._enableTimeoutId) {
|
||||
GLib.source_remove(this._enableTimeoutId);
|
||||
this._enableTimeoutId = 0;
|
||||
}
|
||||
|
||||
console.debug(' RecentFilesSearchProviderModule - Disabled');
|
||||
}
|
||||
|
||||
_registerProvider(provider) {
|
||||
const searchResults = Main.overview.searchController._searchResults;
|
||||
provider.searchInProgress = false;
|
||||
|
||||
searchResults._providers.push(provider);
|
||||
|
||||
// create results display and add it to the _content
|
||||
searchResults._ensureProviderDisplay.bind(searchResults)(provider);
|
||||
}
|
||||
|
||||
_unregisterProvider(provider) {
|
||||
const searchResults = Main.overview.searchController._searchResults;
|
||||
searchResults._unregisterProvider(provider);
|
||||
}
|
||||
};
|
||||
|
||||
class RecentFilesSearchProvider {
|
||||
constructor() {
|
||||
this.id = ID;
|
||||
const appId = 'org.gnome.Nautilus.desktop';
|
||||
|
||||
// A real appInfo created from a commandline has often issues with overriding get_id() method, so we use dict instead
|
||||
this.appInfo = {
|
||||
get_id: () => appId,
|
||||
get_name: () => _('Recent Files'),
|
||||
get_icon: () => Gio.icon_new_for_string('focus-windows-symbolic'),
|
||||
should_show: () => true,
|
||||
get_commandline: () => '/usr/bin/nautilus -w recent:///',
|
||||
launch: () => {},
|
||||
};
|
||||
|
||||
this.canLaunchSearch = true;
|
||||
this.isRemoteProvider = false;
|
||||
|
||||
this._recentFilesManager = new RecentFilesManager();
|
||||
}
|
||||
|
||||
getInitialResultSet(terms/* , cancellable*/) {
|
||||
const rfm = this._recentFilesManager;
|
||||
rfm.loadFromFile();
|
||||
|
||||
const uris = rfm.getUris();
|
||||
const dict = {};
|
||||
for (let uri of uris) {
|
||||
dict[uri] = {};
|
||||
dict[uri]['uri'] = uri;
|
||||
dict[uri]['path'] = rfm.getPath(uri);
|
||||
dict[uri]['filename'] = rfm.getDisplayName(uri);
|
||||
dict[uri]['dir'] = rfm.getDirPath(uri);
|
||||
dict[uri]['age'] = rfm.getAge(uri);
|
||||
dict[uri]['appInfo'] = rfm.getDefaultAppAppInfo(uri);
|
||||
}
|
||||
this.files = dict;
|
||||
|
||||
return new Promise(resolve => resolve(this._getResultSet(terms)));
|
||||
}
|
||||
|
||||
_getResultSet(terms) {
|
||||
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, '');
|
||||
|
||||
const candidates = Object.values(this.files);
|
||||
const _terms = [].concat(termsCopy);
|
||||
|
||||
const term = _terms.join(' ');
|
||||
|
||||
const results = [];
|
||||
let m;
|
||||
for (let file of candidates) {
|
||||
if (opt.SEARCH_FUZZY)
|
||||
m = Me.Util.fuzzyMatch(term, file.filename);
|
||||
else
|
||||
m = Me.Util.strictMatch(term, file.filename);
|
||||
|
||||
if (m !== -1)
|
||||
results.push(file);
|
||||
}
|
||||
|
||||
results.sort((a, b) => a.age > b.age);
|
||||
|
||||
const resultIds = results.map(item => item.uri);
|
||||
return resultIds;
|
||||
}
|
||||
|
||||
getResultMetas(resultIds/* , callback = null*/) {
|
||||
const metas = resultIds.map(id => this.getResultMeta(id));
|
||||
return new Promise(resolve => resolve(metas));
|
||||
}
|
||||
|
||||
getResultMeta(resultId) {
|
||||
const result = this.files[resultId];
|
||||
return {
|
||||
'id': resultId,
|
||||
'name': `${Math.floor(result.age)}: ${result.filename}`,
|
||||
'description': `${result.dir}`,
|
||||
'createIcon': size =>
|
||||
this._recentFilesManager.getDefaultAppIcon(resultId, size),
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// unlike on 42, on 44 if a window with the same uri is already open it will not get focus/activation
|
||||
// Gio.app_info_launch_default_for_uri('recent:///', global.create_app_launch_context(timeStamp, -1));
|
||||
|
||||
// following solution for some reason ignores the recent:/// uri
|
||||
// this.appInfo.launch_uris(['recent:///'], global.create_app_launch_context(timeStamp, -1));
|
||||
}
|
||||
|
||||
activateResult(resultId, terms, timeStamp) {
|
||||
const uri = resultId;
|
||||
const context = global.create_app_launch_context(timeStamp, -1);
|
||||
if (Me.Util.isShiftPressed()) {
|
||||
Main.overview.toggle();
|
||||
this.appInfo.launch_uris([uri], context);
|
||||
} else if (Gio.app_info_launch_default_for_uri(uri, context)) {
|
||||
// update recent list after successful activation
|
||||
this._recentFilesManager.updateAdded(resultId);
|
||||
this._recentFilesManager.saveToFile();
|
||||
} else {
|
||||
this.appInfo.launch_uris([uri], context);
|
||||
}
|
||||
}
|
||||
|
||||
filterResults(results /* , maxResults*/) {
|
||||
// return results.slice(0, maxResults);
|
||||
return results.slice(0, 20);
|
||||
}
|
||||
|
||||
getSubsearchResultSet(previousResults, terms/* , cancellable*/) {
|
||||
return this.getInitialResultSet(terms);
|
||||
}
|
||||
}
|
||||
|
||||
class RecentFilesManager {
|
||||
constructor(path) {
|
||||
path = path ?? GLib.build_filenamev([GLib.get_user_data_dir(), 'recently-used.xbel']);
|
||||
this._recentlyUsedPath = path;
|
||||
this._bookmarks = new GLib.BookmarkFile();
|
||||
}
|
||||
|
||||
loadFromFile() {
|
||||
try {
|
||||
this._bookmarks.load_from_file(this._recentlyUsedPath);
|
||||
} catch (e) {
|
||||
if (!e.matches(GLib.BookmarkFileError, GLib.BookmarkFileError.FILE_NOT_FOUND))
|
||||
console.error(`Could not open recent files: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
saveToFile() {
|
||||
try {
|
||||
this._bookmarks.to_file(this._recentlyUsedPath);
|
||||
} catch (e) {
|
||||
if (!e.matches(GLib.BookmarkFileError, GLib.BookmarkFileError.FILE_NOT_FOUND))
|
||||
console.error(`Could not open recent files to save data: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
getUris() {
|
||||
return this._bookmarks.get_uris();
|
||||
}
|
||||
|
||||
getPath(uri) {
|
||||
// GLib.filename_from_uri() removes uri schema and converts string to utf-8
|
||||
return GLib.filename_from_uri(uri)[0]; // result is array
|
||||
}
|
||||
|
||||
getDisplayName(uri) {
|
||||
const path = this.getPath(uri);
|
||||
return GLib.filename_display_basename(path);
|
||||
}
|
||||
|
||||
getDirPath(uri) {
|
||||
const path = this.getPath(uri);
|
||||
const filename = this.getDisplayName(uri);
|
||||
return path.replace(`${filename}`, '');
|
||||
}
|
||||
|
||||
getMimeType(uri) {
|
||||
return this._bookmarks.get_mime_type(uri);
|
||||
}
|
||||
|
||||
getAdded(uri) {
|
||||
return this._bookmarks.get_added(uri);
|
||||
}
|
||||
|
||||
updateAdded(uri) {
|
||||
this._bookmarks.set_added_date_time(uri, GLib.DateTime.new_now_local());
|
||||
}
|
||||
|
||||
// age in days (float)
|
||||
getAge(uri) {
|
||||
return (Date.now() / 1000 - this._bookmarks.get_added(uri)) / 60 / 60 / 24;
|
||||
}
|
||||
|
||||
getDefaultAppAppInfo(uri) {
|
||||
const mimeType = this.getMimeType(uri);
|
||||
return Gio.AppInfo.get_default_for_type(mimeType, false);
|
||||
}
|
||||
|
||||
getDefaultAppIcon(uri, size) {
|
||||
let icon, gicon;
|
||||
|
||||
const appInfo = this.getDefaultAppAppInfo(uri);
|
||||
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;
|
||||
}
|
||||
}
|
478
extensions/48/vertical-workspaces/lib/search.js
Normal file
478
extensions/48/vertical-workspaces/lib/search.js
Normal file
|
@ -0,0 +1,478 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* search.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import Clutter from 'gi://Clutter';
|
||||
import St from 'gi://St';
|
||||
import Shell from 'gi://Shell';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Search from 'resource:///org/gnome/shell/ui/search.js';
|
||||
import * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js';
|
||||
|
||||
import * as SystemActions from 'resource:///org/gnome/shell/misc/systemActions.js';
|
||||
import { Highlighter } from 'resource:///org/gnome/shell/misc/util.js';
|
||||
|
||||
let Me;
|
||||
// gettext
|
||||
let _;
|
||||
let opt;
|
||||
|
||||
const SEARCH_MAX_WIDTH = 1092;
|
||||
|
||||
export const SearchModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
_ = Me.gettext;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
_ = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('searchModule');
|
||||
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(' 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('ListSearchResults', Search.ListSearchResults.prototype, ListSearchResults);
|
||||
this._overrides.addOverride('ListSearchResult', Search.ListSearchResult.prototype, ListSearchResultOverride);
|
||||
this._overrides.addOverride('Highlighter', Highlighter.prototype, HighlighterOverride);
|
||||
|
||||
// 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.searchController.y_align = Clutter.ActorAlign.START;
|
||||
// Increase the maxResults for app search so that it can show more results in case the user decreases the size of the result icon
|
||||
const appSearchDisplay = Main.overview.searchController._searchResults._providers.filter(p => p.id === 'applications')[0]?.display;
|
||||
if (appSearchDisplay)
|
||||
appSearchDisplay._maxResults = 12;
|
||||
console.debug(' SearchModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
const reset = true;
|
||||
|
||||
const searchResults = Main.overview.searchController._searchResults;
|
||||
if (searchResults?._searchTimeoutId) {
|
||||
GLib.source_remove(searchResults._searchTimeoutId);
|
||||
searchResults._searchTimeoutId = 0;
|
||||
}
|
||||
|
||||
this._updateSearchViewWidth(reset);
|
||||
|
||||
if (this._overrides)
|
||||
this._overrides.removeAll();
|
||||
this._overrides = null;
|
||||
|
||||
Main.overview.searchController.y_align = Clutter.ActorAlign.FILL;
|
||||
|
||||
console.debug(' WorkspaceSwitcherPopupModule - Disabled');
|
||||
}
|
||||
|
||||
_updateSearchViewWidth(reset = false) {
|
||||
const searchContent = Main.overview.searchController._searchResults._content;
|
||||
|
||||
if (reset) {
|
||||
searchContent.set_style('');
|
||||
} else {
|
||||
let width = SEARCH_MAX_WIDTH;
|
||||
if (Me.Util.monitorHasLowResolution())
|
||||
width = Math.round(width * 0.8);
|
||||
width = Math.round(width * opt.SEARCH_VIEW_SCALE);
|
||||
searchContent.set_style(`max-width: ${width}px;`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ListSearchResults = {
|
||||
_getMaxDisplayedResults() {
|
||||
return opt.SEARCH_MAX_ROWS;
|
||||
},
|
||||
};
|
||||
|
||||
// AppDisplay.AppSearchProvider
|
||||
const AppSearchProvider = {
|
||||
getInitialResultSet(terms, cancellable) {
|
||||
// Defer until the parental controls manager is initialized, so the
|
||||
// results can be filtered correctly.
|
||||
if (!this._parentalControlsManager.initialized) {
|
||||
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 = {};
|
||||
appInfoList = appInfoList.filter(appInfo => {
|
||||
try {
|
||||
appInfo.get_id(); // catch invalid file encodings
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let string = '';
|
||||
let name;
|
||||
let shouldShow = false;
|
||||
if (appInfo.get_display_name) {
|
||||
// show only launchers that should be visible in this DE
|
||||
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')?.replace(/;/g, ' ') || '';
|
||||
let keywords = appInfo.get_string('Keywords')?.replace(/;/g, ' ') || '';
|
||||
name = `${dispName} ${id}`;
|
||||
string = `${dispName} ${gName} ${baseName} ${description} ${categories} ${keywords} ${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
let m = -1;
|
||||
if (shouldShow && opt.SEARCH_FUZZY) {
|
||||
m = Me.Util.fuzzyMatch(pattern, name);
|
||||
m = (m + Me.Util.strictMatch(pattern, string)) / 2;
|
||||
} else if (shouldShow) {
|
||||
m = Me.Util.strictMatch(pattern, string);
|
||||
}
|
||||
|
||||
if (m !== -1)
|
||||
weightList[appInfo.get_id()] = m;
|
||||
|
||||
return shouldShow && (m !== -1);
|
||||
});
|
||||
|
||||
appInfoList.sort((a, b) => weightList[a.get_id()] > weightList[b.get_id()]);
|
||||
|
||||
const usage = Shell.AppUsage.get_default();
|
||||
// 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) => Me.Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern));
|
||||
|
||||
let results = appInfoList.map(app => app.get_id());
|
||||
|
||||
if (opt.SEARCH_APP_GRID_MODE && Main.overview.dash.showAppsButton.checked)
|
||||
this._filterAppGrid(results);
|
||||
|
||||
results = results.concat(this._systemActions.getMatchingActions(terms));
|
||||
|
||||
return new Promise(resolve => resolve(results));
|
||||
},
|
||||
|
||||
_filterAppGrid(results) {
|
||||
const appDisplay = Main.overview._overview.controls._appDisplay;
|
||||
let icons = appDisplay._orderedItems;
|
||||
icons.forEach(icon => {
|
||||
icon.visible = true;
|
||||
});
|
||||
appDisplay._redisplay(results);
|
||||
icons = appDisplay._orderedItems;
|
||||
icons.forEach(icon => {
|
||||
icon.visible = results.includes(icon.id);
|
||||
});
|
||||
},
|
||||
|
||||
// App search result size
|
||||
createResultObject(resultMeta) {
|
||||
let iconSize = opt.SEARCH_ICON_SIZE;
|
||||
if (!iconSize) {
|
||||
iconSize = Me.Util.monitorHasLowResolution()
|
||||
? 64
|
||||
: 96;
|
||||
}
|
||||
|
||||
if (resultMeta.id.endsWith('.desktop')) {
|
||||
const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), {
|
||||
expandTitleOnHover: false,
|
||||
});
|
||||
icon.icon.setIconSize(iconSize);
|
||||
return icon;
|
||||
} else {
|
||||
this._iconSize = iconSize;
|
||||
return new SystemActionIcon(this, resultMeta);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const SystemActionIcon = GObject.registerClass({
|
||||
// Registered name should be unique
|
||||
GTypeName: `SystemAction${Math.floor(Math.random() * 1000)}`,
|
||||
}, class SystemActionIcon extends Search.GridSearchResult {
|
||||
_init(provider, metaInfo, resultsView) {
|
||||
super._init(provider, metaInfo, resultsView);
|
||||
this.icon._setSizeManually = true;
|
||||
this.icon.setIconSize(provider._iconSize);
|
||||
}
|
||||
|
||||
activate() {
|
||||
SystemActions.getDefault().activateAction(this.metaInfo['id']);
|
||||
Main.overview.hide();
|
||||
}
|
||||
});
|
||||
|
||||
const SearchResult = {
|
||||
activate() {
|
||||
this.provider.activateResult(this.metaInfo.id, this._resultsView.terms);
|
||||
|
||||
if (this.metaInfo.clipboardText) {
|
||||
St.Clipboard.get_default().set_text(
|
||||
St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText);
|
||||
}
|
||||
// don't close overview if Shift key is pressed - Shift moves windows to the workspace
|
||||
if (!Me.Util.isShiftPressed())
|
||||
Main.overview.toggle();
|
||||
},
|
||||
};
|
||||
|
||||
const SearchResultsView = {
|
||||
setTerms(terms) {
|
||||
// Check for the case of making a duplicate previous search before
|
||||
// setting state of the current search or cancelling the search.
|
||||
// This will prevent incorrect state being as a result of a duplicate
|
||||
// search while the previous search is still active.
|
||||
let searchString = terms.join(' ');
|
||||
let previousSearchString = this._terms.join(' ');
|
||||
if (searchString === previousSearchString)
|
||||
return;
|
||||
|
||||
this._startingSearch = true;
|
||||
|
||||
this._cancellable.cancel();
|
||||
this._cancellable.reset();
|
||||
|
||||
if (terms.length === 0) {
|
||||
this._reset();
|
||||
return;
|
||||
}
|
||||
|
||||
let isSubSearch = false;
|
||||
if (this._terms.length > 0)
|
||||
isSubSearch = searchString.indexOf(previousSearchString) === 0;
|
||||
|
||||
this._terms = terms;
|
||||
this._isSubSearch = isSubSearch;
|
||||
this._updateSearchProgress();
|
||||
|
||||
if (!this._searchTimeoutId)
|
||||
this._searchTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, opt.SEARCH_DELAY, this._onSearchTimeout.bind(this));
|
||||
|
||||
this._highlighter = new Highlighter(this._terms);
|
||||
|
||||
this.emit('terms-changed');
|
||||
},
|
||||
|
||||
_doSearch() {
|
||||
this._startingSearch = false;
|
||||
|
||||
let previousResults = this._results;
|
||||
this._results = {};
|
||||
|
||||
const term0 = this._terms[0];
|
||||
const onlySupportedProviders = term0.startsWith(Me.WSP_PREFIX) || term0.startsWith(Me.ESP_PREFIX) || term0.startsWith(Me.RFSP_PREFIX);
|
||||
|
||||
this._providers.forEach(provider => {
|
||||
const supportedProvider = ['open-windows', 'extensions', 'recent-files'].includes(provider.id);
|
||||
if (!onlySupportedProviders || (onlySupportedProviders && supportedProvider)) {
|
||||
let previousProviderResults = previousResults[provider.id];
|
||||
this._doProviderSearch(provider, previousProviderResults);
|
||||
} else {
|
||||
// hide unwanted providers, they will show() automatically when needed
|
||||
provider.display.visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
this._updateSearchProgress();
|
||||
this._clearSearchTimeout();
|
||||
},
|
||||
|
||||
_updateSearchProgress() {
|
||||
let haveResults = this._providers.some(provider => {
|
||||
let display = provider.display;
|
||||
return display.getFirstResult() !== null;
|
||||
});
|
||||
|
||||
this._scrollView.visible = haveResults;
|
||||
this._statusBin.visible = !haveResults;
|
||||
|
||||
if (!haveResults) {
|
||||
if (this.searchInProgress)
|
||||
this._statusText.set_text(_('Searching…'));
|
||||
else
|
||||
this._statusText.set_text(_('No results.'));
|
||||
}
|
||||
},
|
||||
|
||||
_highlightFirstVisibleAppGridIcon() {
|
||||
const appDisplay = Main.overview._overview.controls._appDisplay;
|
||||
// appDisplay.grab_key_focus();
|
||||
for (const icon of appDisplay._orderedItems) {
|
||||
if (icon.visible) {
|
||||
appDisplay.selectApp(icon.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_maybeSetInitialSelection() {
|
||||
if (opt.SEARCH_APP_GRID_MODE && Main.overview.dash.showAppsButton.checked) {
|
||||
this._highlightFirstVisibleAppGridIcon();
|
||||
return;
|
||||
}
|
||||
|
||||
let newDefaultResult = null;
|
||||
|
||||
let providers = this._providers;
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
let provider = providers[i];
|
||||
let display = provider.display;
|
||||
|
||||
if (!display.visible)
|
||||
continue;
|
||||
|
||||
let firstResult = display.getFirstResult();
|
||||
if (firstResult) {
|
||||
newDefaultResult = firstResult;
|
||||
break; // select this one!
|
||||
}
|
||||
}
|
||||
|
||||
if (newDefaultResult !== this._defaultResult) {
|
||||
this._setSelected(this._defaultResult, false);
|
||||
this._setSelected(newDefaultResult, this._highlightDefault);
|
||||
|
||||
this._defaultResult = newDefaultResult;
|
||||
}
|
||||
},
|
||||
|
||||
highlightDefault(highlight) {
|
||||
if (opt.SEARCH_APP_GRID_MODE && Main.overview.dash.showAppsButton.checked) {
|
||||
if (highlight)
|
||||
this._highlightFirstVisibleAppGridIcon();
|
||||
} else {
|
||||
this._highlightDefault = highlight;
|
||||
this._setSelected(this._defaultResult, highlight);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Add highlighting of the "name" part of the result for all providers
|
||||
const ListSearchResultOverride = {
|
||||
_highlightTerms() {
|
||||
let markup = this._resultsView.highlightTerms(this.metaInfo['name']);
|
||||
this.label_actor.clutter_text.set_markup(markup);
|
||||
markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]);
|
||||
this._descriptionLabel.clutter_text.set_markup(markup);
|
||||
},
|
||||
};
|
||||
|
||||
const HighlighterOverride = {
|
||||
/**
|
||||
* @param {?string[]} terms - list of terms to highlight
|
||||
*/
|
||||
/* constructor(terms) {
|
||||
if (!terms)
|
||||
return;
|
||||
|
||||
const escapedTerms = terms
|
||||
.map(term => Shell.util_regex_escape(term))
|
||||
.filter(term => term.length > 0);
|
||||
|
||||
if (escapedTerms.length === 0)
|
||||
return;
|
||||
|
||||
this._highlightRegex = new RegExp(
|
||||
`(${escapedTerms.join('|')})`, 'gi');
|
||||
},*/
|
||||
|
||||
/**
|
||||
* Highlight all occurences of the terms defined for this
|
||||
* highlighter in the provided text using markup.
|
||||
*
|
||||
* @param {string} text - text to highlight the defined terms in
|
||||
* @returns {string}
|
||||
*/
|
||||
highlight(text, options) {
|
||||
if (!this._highlightRegex)
|
||||
return GLib.markup_escape_text(text, -1);
|
||||
|
||||
// force use local settings if the class is overridden by another extension (WSP, ESP)
|
||||
const o = options || opt;
|
||||
let escaped = [];
|
||||
let lastMatchEnd = 0;
|
||||
let match;
|
||||
let style = ['', ''];
|
||||
if (o.HIGHLIGHT_DEFAULT)
|
||||
style = ['<b>', '</b>'];
|
||||
// The default highlighting by the bold style causes text to be "randomly" ellipsized in cases where it's not necessary
|
||||
// and also blurry
|
||||
// Underscore doesn't affect label size and all looks better
|
||||
else if (o.HIGHLIGHT_UNDERLINE)
|
||||
style = ['<u>', '</u>'];
|
||||
|
||||
while ((match = this._highlightRegex.exec(text))) {
|
||||
if (match.index > lastMatchEnd) {
|
||||
let unmatched = GLib.markup_escape_text(
|
||||
text.slice(lastMatchEnd, match.index), -1);
|
||||
escaped.push(unmatched);
|
||||
}
|
||||
let matched = GLib.markup_escape_text(match[0], -1);
|
||||
escaped.push(`${style[0]}${matched}${style[1]}`);
|
||||
lastMatchEnd = match.index + match[0].length;
|
||||
}
|
||||
let unmatched = GLib.markup_escape_text(
|
||||
text.slice(lastMatchEnd), -1);
|
||||
escaped.push(unmatched);
|
||||
return escaped.join('');
|
||||
},
|
||||
};
|
94
extensions/48/vertical-workspaces/lib/searchController.js
Normal file
94
extensions/48/vertical-workspaces/lib/searchController.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* searchController.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const 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.searchController._onStageKeyPress;
|
||||
|
||||
Main.overview.searchController._onStageKeyPress = SearchControllerCommon._onStageKeyPress;
|
||||
console.debug(' SearchControllerModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._originalOnStageKeyPress)
|
||||
Main.overview.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;
|
||||
},
|
||||
};
|
552
extensions/48/vertical-workspaces/lib/settings.js
Normal file
552
extensions/48/vertical-workspaces/lib/settings.js
Normal file
|
@ -0,0 +1,552 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* settings.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
let Me;
|
||||
|
||||
export const Options = class Options {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
|
||||
this._gsettings = Me.gSettings;
|
||||
this._connectionIds = [];
|
||||
this._writeTimeoutId = 0;
|
||||
this._gsettings.delay();
|
||||
this.connect('changed', () => {
|
||||
if (this._writeTimeoutId)
|
||||
GLib.Source.remove(this._writeTimeoutId);
|
||||
|
||||
this._writeTimeoutId = GLib.timeout_add(
|
||||
GLib.PRIORITY_DEFAULT,
|
||||
400,
|
||||
() => {
|
||||
this._gsettings.apply();
|
||||
this._updateSettings();
|
||||
this._writeTimeoutId = 0;
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
);
|
||||
});
|
||||
this.options = {
|
||||
workspaceThumbnailsPosition: ['int', 'ws-thumbnails-position'],
|
||||
wsMaxSpacing: ['int', 'ws-max-spacing'],
|
||||
wsPreviewScale: ['int', 'ws-preview-scale'],
|
||||
secWsPreviewScale: ['int', 'secondary-ws-preview-scale'],
|
||||
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'],
|
||||
wsTmbPositionAdjust: ['int', 'wst-position-adjust'],
|
||||
showWsTmbLabels: ['int', 'show-wst-labels'],
|
||||
showWsTmbLabelsOnHover: ['boolean', 'show-wst-labels-on-hover'],
|
||||
closeWsButtonMode: ['int', 'close-ws-button-mode'],
|
||||
secWsTmbPositionAdjust: ['int', 'sec-wst-position-adjust'],
|
||||
dashMaxIconSize: ['int', 'dash-max-icon-size'],
|
||||
centerDashToWs: ['boolean', 'center-dash-to-ws'],
|
||||
showAppsIconPosition: ['int', 'show-app-icon-position'],
|
||||
wsThumbnailScale: ['int', 'ws-thumbnail-scale'],
|
||||
wsThumbnailScaleAppGrid: ['int', 'ws-thumbnail-scale-appgrid'],
|
||||
secWsThumbnailScale: ['int', 'secondary-ws-thumbnail-scale'],
|
||||
showSearchEntry: ['boolean', 'show-search-entry'],
|
||||
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'],
|
||||
appGridAnimation: ['int', 'app-grid-animation'],
|
||||
searchViewAnimation: ['int', 'search-view-animation'],
|
||||
workspaceAnimation: ['int', 'workspace-animation'],
|
||||
animationSpeedFactor: ['int', 'animation-speed-factor'],
|
||||
winPreviewIconSize: ['int', 'win-preview-icon-size'],
|
||||
winTitlePosition: ['int', 'win-title-position'],
|
||||
startupState: ['int', 'startup-state'],
|
||||
overviewMode: ['int', 'overview-mode'],
|
||||
workspaceSwitcherAnimation: ['int', 'workspace-switcher-animation'],
|
||||
wsSwitcherMode: ['int', 'ws-switcher-mode'],
|
||||
searchIconSize: ['int', 'search-icon-size'],
|
||||
searchViewScale: ['int', 'search-width-scale'],
|
||||
appGridIconSize: ['int', 'app-grid-icon-size'],
|
||||
appGridColumns: ['int', 'app-grid-columns'],
|
||||
appGridRows: ['int', 'app-grid-rows'],
|
||||
appGridFolderIconSize: ['int', 'app-grid-folder-icon-size'],
|
||||
appGridFolderColumns: ['int', 'app-grid-folder-columns'],
|
||||
appGridFolderRows: ['int', 'app-grid-folder-rows'],
|
||||
appGridFolderIconGrid: ['int', 'app-grid-folder-icon-grid'],
|
||||
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'],
|
||||
appGridPageHeightScale: ['int', 'app-grid-page-height-scale'],
|
||||
appGridSpacing: ['int', 'app-grid-spacing'],
|
||||
appGridFolderSpacing: ['int', 'app-grid-folder-spacing'],
|
||||
appGridShowPageArrows: ['boolean', 'app-grid-show-page-arrows'],
|
||||
searchWindowsOrder: ['int', 'search-windows-order'],
|
||||
searchFuzzy: ['boolean', 'search-fuzzy'],
|
||||
searchMaxResultsRows: ['int', 'search-max-results-rows'],
|
||||
searchAppGridMode: ['int', 'search-app-grid-mode'],
|
||||
dashShowWindowsBeforeActivation: ['int', 'dash-show-windows-before-activation'],
|
||||
dashIconScroll: ['int', 'dash-icon-scroll'],
|
||||
dashIsolateWorkspaces: ['boolean', 'dash-isolate-workspaces'],
|
||||
appMenuForceQuit: ['boolean', 'app-menu-force-quit'],
|
||||
appMenuCloseWinsWs: ['boolean', 'app-menu-close-wins-ws'],
|
||||
appMenuMoveApp: ['boolean', 'app-menu-move-app'],
|
||||
appMenuWindowTmb: ['boolean', 'app-menu-window-tmb'],
|
||||
searchWindowsIconScroll: ['int', 'search-windows-icon-scroll'],
|
||||
panelVisibility: ['int', 'panel-visibility'],
|
||||
panelPosition: ['int', 'panel-position'],
|
||||
panelOverviewStyle: ['int', 'panel-overview-style'],
|
||||
windowAttentionMode: ['int', 'window-attention-mode'],
|
||||
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'],
|
||||
hotCornerAction: ['int', 'hot-corner-action'],
|
||||
hotCornerPosition: ['int', 'hot-corner-position'],
|
||||
hotCornerFullscreen: ['boolean', 'hot-corner-fullscreen'],
|
||||
hotCornerRipples: ['boolean', 'hot-corner-ripples'],
|
||||
alwaysActivateSelectedWindow: ['boolean', 'always-activate-selected-window'],
|
||||
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'],
|
||||
clickEmptyClose: ['boolean', 'click-empty-close'],
|
||||
newWindowFocusFix: ['boolean', 'new-window-focus-fix'],
|
||||
newWindowMonitorFix: ['boolean', 'new-window-monitor-fix'],
|
||||
appGridPerformance: ['boolean', 'app-grid-performance'],
|
||||
highlightingStyle: ['int', 'highlighting-style'],
|
||||
delayStartup: ['boolean', 'delay-startup'],
|
||||
|
||||
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'],
|
||||
windowAttentionHandlerModule: ['boolean', 'win-attention-handler-module'],
|
||||
swipeTrackerModule: ['boolean', 'swipe-tracker-module'],
|
||||
searchControllerModule: ['boolean', 'search-controller-module'],
|
||||
searchModule: ['boolean', 'search-module'],
|
||||
panelModule: ['boolean', 'panel-module'],
|
||||
overlayKeyModule: ['boolean', 'overlay-key-module'],
|
||||
osdWindowModule: ['boolean', 'osd-window-module'],
|
||||
messageTrayModule: ['boolean', 'message-tray-module'],
|
||||
layoutModule: ['boolean', 'layout-module'],
|
||||
dashModule: ['boolean', 'dash-module'],
|
||||
appFavoritesModule: ['boolean', 'app-favorites-module'],
|
||||
appDisplayModule: ['boolean', 'app-display-module'],
|
||||
|
||||
profileName1: ['string', 'profile-name-1'],
|
||||
profileName2: ['string', 'profile-name-2'],
|
||||
profileName3: ['string', 'profile-name-3'],
|
||||
profileName4: ['string', 'profile-name-4'],
|
||||
};
|
||||
this.cachedOptions = {};
|
||||
this._updateSettings();
|
||||
}
|
||||
|
||||
connect(name, callback) {
|
||||
const id = this._gsettings.connect(name, callback);
|
||||
this._connectionIds.push(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._connectionIds.forEach(id => this._gsettings.disconnect(id));
|
||||
if (this._writeTimeoutId) {
|
||||
GLib.source_remove(this._writeTimeoutId);
|
||||
this._writeTimeoutId = 0;
|
||||
}
|
||||
|
||||
Me = null;
|
||||
}
|
||||
|
||||
_updateCachedSettings() {
|
||||
Object.keys(this.options).forEach(v => this.get(v, true));
|
||||
}
|
||||
|
||||
get(option, updateCache = false) {
|
||||
if (!this.options[option]) {
|
||||
console.error(`[${Me.metadata.name}] Error: Option ${option} is undefined.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (updateCache || this.cachedOptions[option] === undefined) {
|
||||
const [, key, settings] = this.options[option];
|
||||
let gSettings;
|
||||
if (settings !== undefined)
|
||||
gSettings = settings();
|
||||
else
|
||||
gSettings = this._gsettings;
|
||||
|
||||
this.cachedOptions[option] = gSettings.get_value(key).deep_unpack();
|
||||
}
|
||||
|
||||
return this.cachedOptions[option];
|
||||
}
|
||||
|
||||
set(option, value) {
|
||||
const [format, key, settings] = this.options[option];
|
||||
|
||||
let gSettings = this._gsettings;
|
||||
|
||||
if (settings !== undefined)
|
||||
gSettings = settings();
|
||||
|
||||
|
||||
switch (format) {
|
||||
case 'boolean':
|
||||
gSettings.set_boolean(key, value);
|
||||
break;
|
||||
case 'int':
|
||||
gSettings.set_int(key, value);
|
||||
break;
|
||||
case 'string':
|
||||
gSettings.set_string(key, value);
|
||||
break;
|
||||
case 'strv':
|
||||
gSettings.set_strv(key, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getDefault(option) {
|
||||
const [, key, settings] = this.options[option];
|
||||
|
||||
let gSettings = this._gsettings;
|
||||
|
||||
if (settings !== undefined)
|
||||
gSettings = settings();
|
||||
|
||||
|
||||
return gSettings.get_default_value(key).deep_unpack();
|
||||
}
|
||||
|
||||
storeProfile(index) {
|
||||
const profile = {};
|
||||
Object.keys(this.options).forEach(v => {
|
||||
if (!v.startsWith('profileName'))
|
||||
profile[v] = this.get(v).toString();
|
||||
});
|
||||
|
||||
this._gsettings.set_value(`profile-data-${index}`, new GLib.Variant('a{ss}', profile));
|
||||
}
|
||||
|
||||
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) {
|
||||
case 'string':
|
||||
break;
|
||||
case 'boolean':
|
||||
value = value === 'true';
|
||||
break;
|
||||
case 'int':
|
||||
value = parseInt(value);
|
||||
break;
|
||||
}
|
||||
|
||||
this.set(o, value);
|
||||
}
|
||||
}
|
||||
|
||||
resetProfile(index) {
|
||||
this._gsettings.reset(`profile-data-${index}`);
|
||||
this._gsettings.reset(`profile-name-${index}`);
|
||||
}
|
||||
|
||||
_updateSettings() {
|
||||
this._updateCachedSettings();
|
||||
|
||||
// Basic spacing of the overview elements
|
||||
this.SPACING = 12;
|
||||
|
||||
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;
|
||||
this.DASH_LEFT = this.DASH_POSITION === 3;
|
||||
this.DASH_VERTICAL = this.DASH_LEFT || this.DASH_RIGHT;
|
||||
this.DASH_VISIBLE = this.DASH_POSITION !== 4; // 4 - disable
|
||||
this.DASH_FOLLOW_RECENT_WIN = false;
|
||||
|
||||
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.RUNNING_DOT_STYLE = this.get('runningDotStyle');
|
||||
|
||||
this.SEARCH_WINDOWS_ICON_SCROLL = this.get('searchWindowsIconScroll');
|
||||
|
||||
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');
|
||||
|
||||
this.MAX_ICON_SIZE = this.get('dashMaxIconSize');
|
||||
|
||||
this.APP_MENU_FORCE_QUIT = this.get('appMenuForceQuit');
|
||||
this.APP_MENU_CLOSE_WINS_WS = this.get('appMenuCloseWinsWs');
|
||||
this.APP_MENU_MOVE_APP = this.get('appMenuMoveApp');
|
||||
this.APP_MENU_WINDOW_TMB = this.get('appMenuWindowTmb');
|
||||
|
||||
this.WS_TMB_POSITION = this.get('workspaceThumbnailsPosition');
|
||||
this.ORIENTATION = this.WS_TMB_POSITION > 4 ? 0 : 1;
|
||||
this.WORKSPACE_MAX_SPACING = this.get('wsMaxSpacing');
|
||||
this.WS_MAX_SPACING_OFF_SCREEN = 350;
|
||||
// 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');
|
||||
// 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];
|
||||
this.WS_TMB_TOP = this.WS_TMB_POSITION === 0;
|
||||
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') * -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') * -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') / 100 + 0.01;
|
||||
this.MAX_THUMBNAIL_SCALE_APPGRID = this.get('wsThumbnailScaleAppGrid') / 100 + 0.01;
|
||||
this.MAX_THUMBNAIL_SCALE_STABLE = this.MAX_THUMBNAIL_SCALE === this.MAX_THUMBNAIL_SCALE_APPGRID;
|
||||
this.SEC_MAX_THUMBNAIL_SCALE = this.get('secWsThumbnailScale') / 100 + 0.01;
|
||||
|
||||
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(2 + (1 - this.WS_PREVIEW_SCALE));
|
||||
|
||||
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');
|
||||
|
||||
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');
|
||||
if (this.SEARCH_VIEW_ANIMATION === 4)
|
||||
this.SEARCH_VIEW_ANIMATION = 3;
|
||||
|
||||
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');
|
||||
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');
|
||||
this.OVERVIEW_MODE2 = this.OVERVIEW_MODE === 2;
|
||||
this.WORKSPACE_MODE = this.OVERVIEW_MODE ? 0 : 1;
|
||||
|
||||
this.STATIC_WS_SWITCHER_BG = this.get('workspaceSwitcherAnimation');
|
||||
|
||||
this.ANIMATION_TIME_FACTOR = this.get('animationSpeedFactor') / 100;
|
||||
|
||||
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.SEARCH_DELAY = this.SEARCH_VIEW_ANIMATION ? 100 : 0;
|
||||
this.SEARCH_APP_GRID_MODE = this.get('searchAppGridMode') && this.get('appDisplayModule');
|
||||
|
||||
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');
|
||||
this.APP_GRID_ALPHABET = [1, 2, 4].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
|
||||
2 - Exclude Favorites (Default)
|
||||
3 - Exclude Running
|
||||
4 - Exclude Favorites and Running
|
||||
*/
|
||||
this.APP_GRID_EXCLUDE_FAVORITES = this.APP_GRID_INCLUDE_DASH === 2 || this.APP_GRID_INCLUDE_DASH === 4;
|
||||
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');
|
||||
|
||||
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_SPACING = this.get('appGridFolderSpacing');
|
||||
this.APP_GRID_FOLDER_DEFAULT = this.APP_GRID_FOLDER_ROWS === 3 && this.APP_GRID_FOLDER_COLUMNS === 3;
|
||||
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_PAGE_HEIGHT_SCALE = this.get('appGridPageHeightScale') / 100;
|
||||
this.APP_GRID_SHOW_PAGE_ARROWS = this.get('appGridShowPageArrows');
|
||||
|
||||
// Default icon sizes updates in the IconGrid._findBestModeForSize()
|
||||
this.APP_GRID_ICON_SIZE_DEFAULT = this.APP_GRID_ACTIVE_PREVIEW && !this.APP_GRID_USAGE ? 192 : 96;
|
||||
this.APP_GRID_FOLDER_ICON_SIZE_DEFAULT = 96;
|
||||
|
||||
this.APP_GRID_PERFORMANCE = this.get('appGridPerformance');
|
||||
|
||||
this.PANEL_POSITION_TOP = this.get('panelPosition') === 0;
|
||||
this.PANEL_POSITION_BOTTOM = this.get('panelPosition') === 1;
|
||||
this.PANEL_MODE = this.get('panelVisibility');
|
||||
this.PANEL_DISABLED = this.PANEL_MODE === 2;
|
||||
this.PANEL_OVERVIEW_ONLY = this.PANEL_MODE === 1;
|
||||
this.PANEL_OVERVIEW_STYLE = this.get('panelOverviewStyle');
|
||||
|
||||
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') / 100;
|
||||
this.WS_SW_POPUP_V_POSITION = this.get('wsSwPopupVPosition') / 100;
|
||||
this.WS_SW_POPUP_MODE = this.get('wsSwPopupMode');
|
||||
|
||||
this.WS_ANIMATION = this.get('workspaceAnimation');
|
||||
this.WS_ANIMATION_SINGLE = this.WS_ANIMATION === 1;
|
||||
this.WS_ANIMATION_ALL = this.WS_ANIMATION === 2;
|
||||
this.WS_WRAPAROUND = this.get('wsSwitcherWraparound');
|
||||
this.WS_IGNORE_LAST = this.get('wsSwitcherIgnoreLast');
|
||||
this.WS_SWITCHER_CURRENT_MONITOR = this.get('wsSwitcherMode') === 1;
|
||||
|
||||
this.SHOW_FAV_NOTIFICATION = this.get('favoritesNotify');
|
||||
this.NOTIFICATION_POSITION = this.get('notificationPosition');
|
||||
|
||||
this.OSD_POSITION = this.get('osdPosition');
|
||||
|
||||
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
|
||||
this.HOT_CORNER_EDGE = false;
|
||||
if ([5, 6].includes(this.HOT_CORNER_POSITION)) {
|
||||
if (this.DASH_TOP || this.DASH_LEFT)
|
||||
this.HOT_CORNER_POSITION = 1;
|
||||
else if (this.DASH_RIGHT)
|
||||
this.HOT_CORNER_POSITION = 2;
|
||||
else if (this.DASH_BOTTOM)
|
||||
this.HOT_CORNER_POSITION = 3;
|
||||
else
|
||||
this.HOT_CORNER_POSITION = 0;
|
||||
}
|
||||
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.CLICK_EMPTY_CLOSE = this.get('clickEmptyClose');
|
||||
|
||||
this.FIX_NEW_WINDOW_FOCUS = this.get('newWindowFocusFix');
|
||||
this.FIX_NEW_WINDOW_MONITOR = this.get('newWindowMonitorFix');
|
||||
|
||||
this.HIGHLIGHTING_STYLE = this.get('highlightingStyle');
|
||||
this.HIGHLIGHT_DEFAULT = this.HIGHLIGHTING_STYLE === 0;
|
||||
this.HIGHLIGHT_UNDERLINE = this.HIGHLIGHTING_STYLE === 1;
|
||||
this.HIGHLIGHT_NONE = this.HIGHLIGHTING_STYLE === 2;
|
||||
|
||||
this.DELAY_STARTUP = this.get('delayStartup');
|
||||
this.DELAY_OVERVIEW_ANIMATION = true;
|
||||
this.DELAY_PER_WINDOW = 5;
|
||||
}
|
||||
|
||||
_getAnimationDirection() {
|
||||
if (this.ORIENTATION)
|
||||
return this.WS_TMB_LEFT || !this.SHOW_WS_TMB ? 1 : 2; // 1 right, 2 left
|
||||
else
|
||||
return this.WS_TMB_TOP || !this.SHOW_WS_TMB ? 3 : 5; // 3 bottom, 5 top
|
||||
}
|
||||
};
|
116
extensions/48/vertical-workspaces/lib/swipeTracker.js
Normal file
116
extensions/48/vertical-workspaces/lib/swipeTracker.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* swipeTracker.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as SwipeTracker from 'resource:///org/gnome/shell/ui/swipeTracker.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const SwipeTrackerModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('swipeTrackerModule');
|
||||
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(' SwipeTrackerModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL
|
||||
this._setVertical();
|
||||
} else {
|
||||
this._setHorizontal();
|
||||
}
|
||||
console.debug(' SwipeTrackerModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
this._setHorizontal();
|
||||
|
||||
console.debug(' SwipeTrackerModule - Disabled');
|
||||
}
|
||||
|
||||
_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 (!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;
|
||||
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) {
|
||||
if (this._state !== 1) // State.SCROLLING)
|
||||
return;
|
||||
|
||||
if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
|
||||
this._interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
if (opt.WS_TMB_RIGHT)
|
||||
delta = -delta;
|
||||
this._progress += delta / distance;
|
||||
this._history.append(time, delta);
|
||||
|
||||
this._progress = Math.clamp(this._progress, ...this._getBounds(this._initialProgress));
|
||||
this.emit('update', this._progress);
|
||||
},
|
||||
};
|
464
extensions/48/vertical-workspaces/lib/util.js
Normal file
464
extensions/48/vertical-workspaces/lib/util.js
Normal file
|
@ -0,0 +1,464 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* util.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Gio from 'gi://Gio';
|
||||
import GLib from 'gi://GLib';
|
||||
import GObject from 'gi://GObject';
|
||||
import Meta from 'gi://Meta';
|
||||
import Shell from 'gi://Shell';
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as ModalDialog from 'resource:///org/gnome/shell/ui/modalDialog.js';
|
||||
import { InjectionManager } from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
let Me;
|
||||
let _;
|
||||
let _installedExtensions;
|
||||
|
||||
export function init(me) {
|
||||
Me = me;
|
||||
_ = Me.gettext;
|
||||
}
|
||||
|
||||
export function cleanGlobals() {
|
||||
Me = null;
|
||||
_ = null;
|
||||
_installedExtensions = null;
|
||||
}
|
||||
|
||||
export class Overrides extends InjectionManager {
|
||||
constructor() {
|
||||
super();
|
||||
this._overrides = {};
|
||||
}
|
||||
|
||||
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,
|
||||
prototype,
|
||||
};
|
||||
}
|
||||
|
||||
removeOverride(name) {
|
||||
const override = this._overrides[name];
|
||||
if (!override)
|
||||
return false;
|
||||
|
||||
this.overrideProto(override.prototype, override.originals, name);
|
||||
delete this._overrides[name];
|
||||
return true;
|
||||
}
|
||||
|
||||
removeAll() {
|
||||
for (let name in this._overrides) {
|
||||
this.removeOverride(name);
|
||||
delete this._overrides[name];
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
const res = fn.apply(this, args);
|
||||
afterFn.apply(this, args);
|
||||
return res;
|
||||
};
|
||||
backup[actualSymbol] = fn;
|
||||
} else if (overrides[symbol] !== null) {
|
||||
backup[symbol] = proto[symbol];
|
||||
this._installMethod(proto, symbol, overrides[symbol]);
|
||||
}
|
||||
}
|
||||
return backup;
|
||||
}
|
||||
}
|
||||
|
||||
export 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, isMe = null;
|
||||
|
||||
for (let win of windows) {
|
||||
const app = tracker.get_window_app(win);
|
||||
if (win.get_title()?.includes(metadata.name) && app.get_name() === 'Extensions') {
|
||||
// this is our existing window
|
||||
metaWin = win;
|
||||
isMe = true;
|
||||
break;
|
||||
} else if (win.wm_class?.includes('org.gnome.Shell.Extensions')) {
|
||||
// this is prefs window of another extension
|
||||
metaWin = win;
|
||||
isMe = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (metaWin && !isMe) {
|
||||
// other prefs window blocks opening another prefs window, so close it
|
||||
metaWin.delete(global.get_current_time());
|
||||
} else if (metaWin && isMe) {
|
||||
// if prefs window already exist, move it to the current WS and activate it
|
||||
metaWin.change_workspace(global.workspace_manager.get_active_workspace());
|
||||
metaWin.activate(global.get_current_time());
|
||||
}
|
||||
|
||||
if (!metaWin || (metaWin && !isMe)) {
|
||||
// delay to avoid errors if previous prefs window has been closed
|
||||
GLib.idle_add(GLib.PRIORITY_LOW, () => {
|
||||
try {
|
||||
Main.extensionManager.openExtensionPrefs(metadata.uuid, '', {});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function activateSearchProvider(prefix = '') {
|
||||
const searchEntry = Main.overview.searchEntry;
|
||||
const searchEntryText = searchEntry.get_text();
|
||||
if (!searchEntryText || (searchEntryText && !searchEntry.get_text().startsWith(prefix))) {
|
||||
prefix = `${prefix} `;
|
||||
const position = prefix.length;
|
||||
searchEntry.set_text(prefix);
|
||||
searchEntry.get_first_child().set_cursor_position(position);
|
||||
searchEntry.get_first_child().set_selection(position, position);
|
||||
searchEntry.grab_key_focus();
|
||||
} else {
|
||||
searchEntry.set_text('');
|
||||
}
|
||||
}
|
||||
|
||||
export function dashNotDefault() {
|
||||
return Main.overview.dash !== Main.overview._overview._controls.layoutManager._dash;
|
||||
}
|
||||
|
||||
export function dashIsDashToDock() {
|
||||
return Main.overview.dash._isHorizontal !== undefined;
|
||||
}
|
||||
|
||||
// Reorder Workspaces - callback for Dash and workspacesDisplay
|
||||
export function reorderWorkspace(direction = 0) {
|
||||
let activeWs = global.workspace_manager.get_active_workspace();
|
||||
let activeWsIdx = activeWs.index();
|
||||
let targetIdx = activeWsIdx + direction;
|
||||
if (targetIdx > -1 && targetIdx < global.workspace_manager.get_n_workspaces())
|
||||
global.workspace_manager.reorder_workspace(activeWs, targetIdx);
|
||||
}
|
||||
|
||||
// In WINDOW_PICKER mode, enable keyboard navigation
|
||||
// by focusing on the active window's preview
|
||||
export function activateKeyboardForWorkspaceView() {
|
||||
const currentWindowActor = global.display.focus_window?.get_compositor_private();
|
||||
if (!currentWindowActor)
|
||||
return;
|
||||
|
||||
const activeWorkspace = global.workspace_manager.get_active_workspace().index();
|
||||
const nMonitors = global.display.get_n_monitors();
|
||||
for (let monitor = 0; monitor < nMonitors; monitor++) {
|
||||
// secondary monitor
|
||||
let windows = Main.overview._overview.controls._workspacesDisplay._workspacesViews[monitor]._workspacesView?._workspaces[activeWorkspace]._windows;
|
||||
if (!windows) // primary monitor
|
||||
windows = Main.overview._overview.controls._workspacesDisplay._workspacesViews[monitor]._workspaces[activeWorkspace]._windows;
|
||||
for (const win of windows) {
|
||||
if (win._windowActor === currentWindowActor) {
|
||||
win.grab_key_focus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function exposeWindows() {
|
||||
Main.overview._overview.controls._workspacesDisplay._workspacesViews.forEach(
|
||||
view => {
|
||||
view.exposeWindows();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function exposeWindowsWithOverviewTransition() {
|
||||
// in OVERVIEW MODE 2 windows are not spread and workspace is not scaled
|
||||
// we need to repeat transition to the overview state 1 (window picker), but with spreading windows animation
|
||||
const stateAdjustment = Main.overview._overview.controls._stateAdjustment;
|
||||
Me.opt.WORKSPACE_MODE = 1;
|
||||
// setting value to 0 would reset WORKSPACE_MODE
|
||||
stateAdjustment.value = 0.01;
|
||||
stateAdjustment.ease(1, {
|
||||
duration: 200,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => activateKeyboardForWorkspaceView(),
|
||||
});
|
||||
}
|
||||
|
||||
export function isShiftPressed(state = null) {
|
||||
if (state === null)
|
||||
[,, state] = global.get_pointer();
|
||||
return (state & Clutter.ModifierType.SHIFT_MASK) !== 0;
|
||||
}
|
||||
|
||||
export function isCtrlPressed(state = null) {
|
||||
if (state === null)
|
||||
[,, state] = global.get_pointer();
|
||||
return (state & Clutter.ModifierType.CONTROL_MASK) !== 0;
|
||||
}
|
||||
|
||||
export function isAltPressed(state = null) {
|
||||
if (state === null)
|
||||
[,, state] = global.get_pointer();
|
||||
return (state & Clutter.ModifierType.MOD1_MASK) !== 0;
|
||||
}
|
||||
|
||||
export function fuzzyMatch(term, text) {
|
||||
let pos = -1;
|
||||
const matches = [];
|
||||
// convert all accented chars to their basic form and to lower case
|
||||
const _text = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
const _term = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
|
||||
// if term matches the substring exactly, gains the highest weight
|
||||
if (_text.includes(_term))
|
||||
return 0;
|
||||
|
||||
for (let i = 0; i < _term.length; i++) {
|
||||
let c = _term[i];
|
||||
let p;
|
||||
if (pos > 0)
|
||||
p = _term[i - 1];
|
||||
while (true) {
|
||||
pos += 1;
|
||||
if (pos >= _text.length)
|
||||
return -1;
|
||||
|
||||
if (_text[pos] === c) {
|
||||
matches.push(pos);
|
||||
break;
|
||||
} else if (_text[pos] === p) {
|
||||
matches.pop();
|
||||
matches.push(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all position to get a weight of the result
|
||||
// results closer to the beginning of the text and term characters closer to each other will gain more weight.
|
||||
return matches.reduce((r, p) => r + p) - matches.length * matches[0] + matches[0];
|
||||
}
|
||||
|
||||
export function strictMatch(term, text) {
|
||||
// remove diacritics and accents from letters
|
||||
let s = text.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
let p = term.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
let ps = p.split(/ +/);
|
||||
|
||||
// allows to use multiple exact patterns separated by a space in arbitrary order
|
||||
for (let w of ps) { // escape regex control chars
|
||||
if (!s.match(w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')))
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function isMoreRelevant(stringA, stringB, pattern) {
|
||||
let regex = /[^a-zA-Z\d]/;
|
||||
let strSplitA = stringA.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().split(regex);
|
||||
let strSplitB = stringB.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().split(regex);
|
||||
let aAny = false;
|
||||
strSplitA.forEach(w => {
|
||||
aAny = aAny || w.startsWith(pattern);
|
||||
});
|
||||
let bAny = false;
|
||||
strSplitB.forEach(w => {
|
||||
bAny = bAny || w.startsWith(pattern);
|
||||
});
|
||||
|
||||
// if both strings contain a word that starts with the pattern
|
||||
// prefer the one whose first word starts with the pattern
|
||||
if (aAny && bAny)
|
||||
return !strSplitA[0].startsWith(pattern) && strSplitB[0].startsWith(pattern);
|
||||
else
|
||||
return !aAny && bAny;
|
||||
}
|
||||
|
||||
export function getEnabledExtensions(pattern = '') {
|
||||
let result = [];
|
||||
// extensionManager is unreliable at startup because it is uncertain whether all extensions have been loaded
|
||||
// also gsettings key can contain already removed extensions (user deleted them without disabling them first)
|
||||
// therefore we have to check what's really installed in the filesystem
|
||||
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;
|
||||
});
|
||||
}
|
||||
// _enabledExtensions contains content of the enabled-extensions key from gsettings, not actual state
|
||||
const enabled = Main.extensionManager._enabledExtensions;
|
||||
result = _installedExtensions.filter(ext => enabled.includes(ext));
|
||||
// _extensions contains already loaded extensions, so we can try to filter out broken or incompatible extensions
|
||||
const active = Main.extensionManager._extensions;
|
||||
result = result.filter(ext => {
|
||||
const extension = active.get(ext);
|
||||
if (extension)
|
||||
return ![3, 4].includes(extension.state); // 3 - ERROR, 4 - OUT_OF_TIME (not supported by shell-version in metadata)
|
||||
// extension can be enabled but not yet loaded, we just cannot see its state at this moment, so let it pass as enabled
|
||||
return true;
|
||||
});
|
||||
// return only extensions matching the search pattern
|
||||
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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getScrollDirection(event) {
|
||||
// scroll wheel provides two types of direction information:
|
||||
// 1. Clutter.ScrollDirection.DOWN / Clutter.ScrollDirection.UP
|
||||
// 2. Clutter.ScrollDirection.SMOOTH + event.get_scroll_delta()
|
||||
// first SMOOTH event returns 0 delta,
|
||||
// so we need to always read event.direction
|
||||
// since mouse without smooth scrolling provides exactly one SMOOTH event on one wheel rotation click
|
||||
// on the other hand, under X11, one wheel rotation click sometimes doesn't send direction event, only several SMOOTH events
|
||||
// so we also need to convert the delta to direction
|
||||
let direction = event.get_scroll_direction();
|
||||
|
||||
if (direction !== Clutter.ScrollDirection.SMOOTH)
|
||||
return direction;
|
||||
|
||||
let [, delta] = event.get_scroll_delta();
|
||||
|
||||
if (!delta)
|
||||
return null;
|
||||
|
||||
direction = delta > 0 ? Clutter.ScrollDirection.DOWN : Clutter.ScrollDirection.UP;
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
export function getWindows(workspace) {
|
||||
// We ignore skip-taskbar windows in switchers, but if they are attached
|
||||
// to their parent, their position in the MRU list may be more appropriate
|
||||
// than the parent; so start with the complete list ...
|
||||
let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace);
|
||||
// ... map windows to their parent where appropriate ...
|
||||
return windows.map(w => {
|
||||
return w.is_attached_dialog() ? w.get_transient_for() : w;
|
||||
// ... and filter out skip-taskbar windows and duplicates
|
||||
}).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) === i);
|
||||
}
|
||||
|
||||
export function monitorHasLowResolution(monitorIndex, resolutionLimit) {
|
||||
resolutionLimit = resolutionLimit ?? 1200000;
|
||||
monitorIndex = monitorIndex ?? global.display.get_primary_monitor();
|
||||
const monitorGeometry = global.display.get_monitor_geometry(monitorIndex);
|
||||
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
||||
const monitorResolution = monitorGeometry.width * monitorGeometry.height;
|
||||
return (monitorResolution / scaleFactor) < resolutionLimit;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Status dialog that appears during updating V-Shell configuration and blocks inputs
|
||||
|
||||
export const RestartMessage = GObject.registerClass({
|
||||
// Registered name should be unique
|
||||
GTypeName: `RestartMessage${Math.floor(Math.random() * 1000)}`,
|
||||
}, class RestartMessage extends ModalDialog.ModalDialog {
|
||||
_init() {
|
||||
super._init({
|
||||
shellReactive: false,
|
||||
styleClass: 'restart-message headline update-message',
|
||||
shouldFadeIn: false,
|
||||
destroyOnClose: false,
|
||||
});
|
||||
|
||||
const label = new St.Label({
|
||||
text: _('Updating V-Shell'),
|
||||
x_align: Clutter.ActorAlign.CENTER,
|
||||
y_align: Clutter.ActorAlign.CENTER,
|
||||
});
|
||||
|
||||
this.contentLayout.add_child(label);
|
||||
this.buttonLayout.hide();
|
||||
this.connect('destroy', () => this.removeMessage());
|
||||
}
|
||||
|
||||
showMessage(timeout = 500) {
|
||||
if (this._timeoutId || Me._resetInProgress || Main.layoutManager._startingUp)
|
||||
return;
|
||||
this._removeTimeout();
|
||||
this.open();
|
||||
this._timeoutId = GLib.timeout_add(
|
||||
GLib.PRIORITY_LOW,
|
||||
timeout,
|
||||
() => {
|
||||
this._timeoutId = 0;
|
||||
this.removeMessage();
|
||||
return GLib.SOURCE_REMOVE;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_removeTimeout() {
|
||||
if (this._timeoutId) {
|
||||
GLib.source_remove(this._timeoutId);
|
||||
this._timeoutId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
removeMessage() {
|
||||
this._removeTimeout();
|
||||
this.close();
|
||||
}
|
||||
});
|
185
extensions/48/vertical-workspaces/lib/windowAttentionHandler.js
Normal file
185
extensions/48/vertical-workspaces/lib/windowAttentionHandler.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* windowAttentionHandler.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
|
||||
|
||||
const shellVersion46 = !Clutter.Container;
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const WindowAttentionHandlerModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('windowAttentionHandlerModule');
|
||||
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(' WindowAttentionHandlerModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
this._updateConnections();
|
||||
console.debug(' WindowAttentionHandlerModule - Activated');
|
||||
}
|
||||
|
||||
_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) {
|
||||
// Deny attention notifications if the App Grid is open, to avoid notification spree when opening a folder
|
||||
if (Main.overview._shown && Main.overview.dash.showAppsButton.checked) {
|
||||
return;
|
||||
} else if (opt.WINDOW_ATTENTION_FOCUS_IMMEDIATELY) {
|
||||
if (!Main.overview._shown)
|
||||
Main.activateWindow(window);
|
||||
return;
|
||||
}
|
||||
|
||||
const app = this._tracker.get_window_app(window);
|
||||
let args;
|
||||
if (shellVersion46)
|
||||
args = { title: app.get_name() };
|
||||
else
|
||||
args = app.get_name();
|
||||
|
||||
const source = new MessageTray.Source(args);
|
||||
new Me.Util.Overrides().addOverride('MessageSource', source, WindowAttentionSourceCommon);
|
||||
source._init(app, window);
|
||||
Main.messageTray.add(source);
|
||||
|
||||
let [title, body] = this._getTitleAndBanner(app, window);
|
||||
args = shellVersion46
|
||||
? [{ source, title, body, forFeedback: true }]
|
||||
: [source, title, body];
|
||||
|
||||
const notification = new MessageTray.Notification(...args);
|
||||
if (!shellVersion46)
|
||||
notification.setForFeedback(true);
|
||||
|
||||
notification.connect('activated', () => {
|
||||
source.open();
|
||||
});
|
||||
|
||||
if (shellVersion46) {
|
||||
notification.acknowledged = opt.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS;
|
||||
source.addNotification(notification);
|
||||
if (opt.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS) {
|
||||
// just push the notification to the message tray without showing notification
|
||||
notification.acknowledged = true;
|
||||
Main.messageTray._notificationQueue.push(notification);
|
||||
Main.panel.statusArea.dateMenu._indicator.show();
|
||||
}
|
||||
window.connectObject('notify::title', () => {
|
||||
[title, body] = this._getTitleAndBanner(app, window);
|
||||
notification.set({ title, body });
|
||||
}, source);
|
||||
} else {
|
||||
if (opt.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS)
|
||||
// just push the notification to the message tray without showing notification
|
||||
source.pushNotification(notification);
|
||||
else
|
||||
source.showNotification(notification);
|
||||
|
||||
window.connectObject('notify::title', () => {
|
||||
[title, body] = this._getTitleAndBanner(app, window);
|
||||
notification.update(title, body);
|
||||
}, source);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const WindowAttentionSourceCommon = {
|
||||
_init(app, window) {
|
||||
this._window = window;
|
||||
this._app = app;
|
||||
|
||||
this._window.connectObject(
|
||||
'notify::demands-attention', this._sync.bind(this),
|
||||
'notify::urgent', this._sync.bind(this),
|
||||
'focus', () => this.destroy(),
|
||||
'unmanaged', () => this.destroy(), this);
|
||||
},
|
||||
|
||||
_sync() {
|
||||
if (this._window.demands_attention || this._window.urgent)
|
||||
return;
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_createPolicy() {
|
||||
if (this._app && this._app.get_app_info()) {
|
||||
let id = this._app.get_id().replace(/\.desktop$/, '');
|
||||
return new MessageTray.NotificationApplicationPolicy(id);
|
||||
} else {
|
||||
return new MessageTray.NotificationGenericPolicy();
|
||||
}
|
||||
},
|
||||
|
||||
createIcon(size) {
|
||||
return this._app.create_icon_texture(size);
|
||||
},
|
||||
|
||||
destroy(params) {
|
||||
this._window.disconnectObject(this);
|
||||
|
||||
MessageTray.Source.prototype.destroy.bind(this)(params);
|
||||
},
|
||||
|
||||
open() {
|
||||
Main.activateWindow(this._window);
|
||||
},
|
||||
};
|
383
extensions/48/vertical-workspaces/lib/windowManager.js
Normal file
383
extensions/48/vertical-workspaces/lib/windowManager.js
Normal file
|
@ -0,0 +1,383 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* windowManager.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
import Meta from 'gi://Meta';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as WindowManager from 'resource:///org/gnome/shell/ui/windowManager.js';
|
||||
import * as WorkspaceAnimation from 'resource:///org/gnome/shell/ui/workspaceAnimation.js';
|
||||
|
||||
const MINIMIZE_WINDOW_ANIMATION_TIME = 400; // windowManager.MINIMIZE_WINDOW_ANIMATION_TIME
|
||||
const MINIMIZE_WINDOW_ANIMATION_MODE = Clutter.AnimationMode.EASE_OUT_EXPO; // WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const WindowManagerModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
|
||||
this._originalMinimizeSigId = 0;
|
||||
this._minimizeSigId = 0;
|
||||
this._originalUnminimizeSigId = 0;
|
||||
this._unminimizeSigId = 0;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('windowManagerModule');
|
||||
const conflict = false;
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
this._overrides.addOverride('WindowManager', WindowManager.WindowManager.prototype, WindowManagerCommon);
|
||||
if (opt.WS_SWITCHER_CURRENT_MONITOR)
|
||||
this._overrides.addOverride('WorkspaceAnimationController', WorkspaceAnimation.WorkspaceAnimationController.prototype, WorkspaceAnimationController);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
_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;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
};
|
||||
|
||||
const WindowManagerCommon = {
|
||||
actionMoveWorkspace(workspace) {
|
||||
if (!Main.sessionMode.hasWorkspaces)
|
||||
return;
|
||||
|
||||
if (opt.WS_SWITCHER_CURRENT_MONITOR)
|
||||
this._switchWorkspaceCurrentMonitor(workspace);
|
||||
else if (!workspace.active)
|
||||
workspace.activate(global.get_current_time());
|
||||
},
|
||||
|
||||
actionMoveWindow(window, workspace) {
|
||||
if (!Main.sessionMode.hasWorkspaces)
|
||||
return;
|
||||
|
||||
if (!workspace.active) {
|
||||
// This won't have any effect for "always sticky" windows
|
||||
// (like desktop windows or docks)
|
||||
|
||||
this._workspaceAnimation.movingWindow = window;
|
||||
window.change_workspace(workspace);
|
||||
|
||||
global.display.clear_mouse_mode();
|
||||
|
||||
if (opt.SWITCH_ONLY_CURRENT_MONITOR_WS) {
|
||||
this._switchWorkspaceCurrentMonitor(workspace, window.get_monitor());
|
||||
window.activate(global.get_current_time());
|
||||
} else {
|
||||
workspace.activate_with_focus(window, global.get_current_time());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_switchWorkspaceCurrentMonitor(workspace, monitor) {
|
||||
// const focusedWindow = global.display.get_focus_window();
|
||||
// const currentMonitor = focusedWindow ? focusedWindow.get_monitor() : global.display.get_current_monitor();
|
||||
// using focused window to determine the current monitor can lead to inconsistent behavior and switching monitors between switches
|
||||
// depending on which window takes focus on each workspace
|
||||
// mouse pointer is more stable and predictable source
|
||||
const currentMonitor = monitor ? monitor : global.display.get_current_monitor();
|
||||
const primaryMonitor = currentMonitor === Main.layoutManager.primaryIndex;
|
||||
const nMonitors = Main.layoutManager.monitors.length;
|
||||
const lastIndexCorrection = Meta.prefs_get_dynamic_workspaces() ? 2 : 1;
|
||||
const lastIndex = global.workspaceManager.get_n_workspaces() - lastIndexCorrection;
|
||||
const targetWsIndex = workspace.index();
|
||||
const activeWs = global.workspaceManager.get_active_workspace();
|
||||
const activeWsIndex = activeWs.index();
|
||||
const diff = activeWsIndex - targetWsIndex;
|
||||
|
||||
let direction = diff > 0 ? Meta.MotionDirection.UP : Meta.MotionDirection.DOWN;
|
||||
if (diff === 0) {
|
||||
// no actual ws to switch, but secondary monitors are always in wraparound mode so we need to get direction
|
||||
direction = activeWsIndex >= lastIndex ? Meta.MotionDirection.DOWN : Meta.MotionDirection.UP;
|
||||
}
|
||||
if (Math.abs(diff) > 1) {
|
||||
// workspace is probably in wraparound mode and just wrapped so so we need to translate direction
|
||||
direction = diff > 0 ? Meta.MotionDirection.DOWN : Meta.MotionDirection.UP;
|
||||
}
|
||||
|
||||
if (!primaryMonitor) {
|
||||
this._rotateWorkspaces(direction, currentMonitor);
|
||||
return;
|
||||
}
|
||||
|
||||
// avoid ws rotations if the last empty dynamic workspace is involved, but allow to rotate from the last to the first, if wraparound is enabled
|
||||
if (workspace !== activeWs && !((targetWsIndex > lastIndex && direction === Meta.MotionDirection.DOWN) || (activeWsIndex > lastIndex && targetWsIndex >= lastIndex))) {
|
||||
for (let i = 0; i < nMonitors; i++) {
|
||||
if (i !== currentMonitor) {
|
||||
const oppositeDirection = direction === Meta.MotionDirection.UP ? Meta.MotionDirection.DOWN : Meta.MotionDirection.UP;
|
||||
this._rotateWorkspaces(oppositeDirection, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
workspace.activate(global.get_current_time());
|
||||
},
|
||||
|
||||
_rotateWorkspaces(direction = 0, monitorIndex = -1, step = 1) {
|
||||
step = direction === Meta.MotionDirection.UP ? Number(step) : -step;
|
||||
const monitor = monitorIndex > -1 ? monitorIndex : global.display.get_current_monitor();
|
||||
// don't move windows to the last empty workspace if dynamic workspaces are enabled
|
||||
const lastIndexCorrection = Meta.prefs_get_dynamic_workspaces() ? 2 : 1;
|
||||
const lastIndex = global.workspaceManager.get_n_workspaces() - lastIndexCorrection;
|
||||
let windows = Me.Util.getWindows(null);
|
||||
for (let win of windows.reverse()) {
|
||||
// avoid moving modal windows as they move with their parents (and vice versa) immediately, before we move the parent window.
|
||||
if (win.get_monitor() === monitor && !win.is_always_on_all_workspaces() && !win.is_attached_dialog() && !win.get_transient_for()) {
|
||||
let wWs = win.get_workspace().index();
|
||||
wWs += step;
|
||||
if (wWs < 0)
|
||||
wWs = lastIndex;
|
||||
if (wWs > lastIndex)
|
||||
wWs = 0;
|
||||
const ws = global.workspaceManager.get_workspace_by_index(wWs);
|
||||
win.change_workspace(ws);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// fix for mainstream bug - fullscreen windows should minimize using opacity transition
|
||||
// but its being applied directly on window actor and that doesn't work
|
||||
// anyway, animation is better, even if the Activities button is not visible...
|
||||
// and also add support for bottom position of the panel
|
||||
_minimizeWindow(shellwm, actor) {
|
||||
const types = [
|
||||
Meta.WindowType.NORMAL,
|
||||
Meta.WindowType.MODAL_DIALOG,
|
||||
Meta.WindowType.DIALOG,
|
||||
];
|
||||
if (!this._shouldAnimateActor(actor, types)) {
|
||||
shellwm.completed_minimize(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
actor.set_scale(1.0, 1.0);
|
||||
|
||||
this._minimizing.add(actor);
|
||||
|
||||
/* if (actor.meta_window.is_monitor_sized()) {
|
||||
actor.get_first_child().ease({
|
||||
opacity: 0,
|
||||
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
|
||||
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
|
||||
onStopped: () => this._minimizeWindowDone(shellwm, actor),
|
||||
});
|
||||
} else { */
|
||||
let xDest, yDest, xScale, yScale;
|
||||
let [success, geom] = actor.meta_window.get_icon_geometry();
|
||||
if (success) {
|
||||
xDest = geom.x;
|
||||
yDest = geom.y;
|
||||
xScale = geom.width / actor.width;
|
||||
yScale = geom.height / actor.height;
|
||||
} else {
|
||||
let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
|
||||
if (!monitor) {
|
||||
this._minimizeWindowDone();
|
||||
return;
|
||||
}
|
||||
xDest = monitor.x;
|
||||
yDest = opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height;
|
||||
if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
|
||||
xDest += monitor.width;
|
||||
xScale = 0;
|
||||
yScale = 0;
|
||||
}
|
||||
|
||||
actor.ease({
|
||||
scale_x: xScale,
|
||||
scale_y: yScale,
|
||||
x: xDest,
|
||||
y: yDest,
|
||||
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
|
||||
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
|
||||
onStopped: () => this._minimizeWindowDone(shellwm, actor),
|
||||
});
|
||||
// }
|
||||
},
|
||||
|
||||
_minimizeWindowDone(shellwm, actor) {
|
||||
if (this._minimizing.delete(actor)) {
|
||||
actor.remove_all_transitions();
|
||||
actor.set_scale(1.0, 1.0);
|
||||
actor.get_first_child().set_opacity(255);
|
||||
actor.set_pivot_point(0, 0);
|
||||
|
||||
shellwm.completed_minimize(actor);
|
||||
}
|
||||
},
|
||||
|
||||
_unminimizeWindow(shellwm, actor) {
|
||||
const types = [
|
||||
Meta.WindowType.NORMAL,
|
||||
Meta.WindowType.MODAL_DIALOG,
|
||||
Meta.WindowType.DIALOG,
|
||||
];
|
||||
if (!this._shouldAnimateActor(actor, types)) {
|
||||
shellwm.completed_unminimize(actor);
|
||||
return;
|
||||
}
|
||||
|
||||
this._unminimizing.add(actor);
|
||||
|
||||
/* if (false/* actor.meta_window.is_monitor_sized()) {
|
||||
actor.opacity = 0;
|
||||
actor.set_scale(1.0, 1.0);
|
||||
actor.ease({
|
||||
opacity: 255,
|
||||
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
|
||||
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
|
||||
onStopped: () => this._unminimizeWindowDone(shellwm, actor),
|
||||
});
|
||||
} else { */
|
||||
let [success, geom] = actor.meta_window.get_icon_geometry();
|
||||
if (success) {
|
||||
actor.set_position(geom.x, geom.y);
|
||||
actor.set_scale(geom.width / actor.width,
|
||||
geom.height / actor.height);
|
||||
} else {
|
||||
let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
|
||||
if (!monitor) {
|
||||
actor.show();
|
||||
this._unminimizeWindowDone();
|
||||
return;
|
||||
}
|
||||
actor.set_position(monitor.x, opt.PANEL_POSITION_TOP ? monitor.y : monitor.y + monitor.height);
|
||||
if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
|
||||
actor.x += monitor.width;
|
||||
actor.set_scale(0, 0);
|
||||
}
|
||||
|
||||
let rect = actor.meta_window.get_buffer_rect();
|
||||
let [xDest, yDest] = [rect.x, rect.y];
|
||||
|
||||
actor.show();
|
||||
actor.ease({
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
x: xDest,
|
||||
y: yDest,
|
||||
duration: MINIMIZE_WINDOW_ANIMATION_TIME,
|
||||
mode: MINIMIZE_WINDOW_ANIMATION_MODE,
|
||||
onStopped: () => this._unminimizeWindowDone(shellwm, actor),
|
||||
});
|
||||
// }
|
||||
},
|
||||
};
|
||||
|
||||
const WorkspaceAnimationController = {
|
||||
_prepareWorkspaceSwitch(workspaceIndices) {
|
||||
if (this._switchData)
|
||||
return;
|
||||
|
||||
const workspaceManager = global.workspace_manager;
|
||||
const nWorkspaces = workspaceManager.get_n_workspaces();
|
||||
|
||||
const switchData = {};
|
||||
|
||||
this._switchData = switchData;
|
||||
switchData.monitors = [];
|
||||
|
||||
switchData.gestureActivated = false;
|
||||
switchData.inProgress = false;
|
||||
|
||||
if (!workspaceIndices)
|
||||
workspaceIndices = [...Array(nWorkspaces).keys()];
|
||||
|
||||
let monitors = opt.WS_SWITCHER_CURRENT_MONITOR
|
||||
? [Main.layoutManager.currentMonitor] : Main.layoutManager.monitors;
|
||||
monitors = Meta.prefs_get_workspaces_only_on_primary()
|
||||
? [Main.layoutManager.primaryMonitor] : monitors;
|
||||
|
||||
for (const monitor of monitors) {
|
||||
if (Meta.prefs_get_workspaces_only_on_primary() &&
|
||||
monitor.index !== Main.layoutManager.primaryIndex)
|
||||
continue;
|
||||
|
||||
const group = new WorkspaceAnimation.MonitorGroup(monitor, workspaceIndices, this.movingWindow);
|
||||
|
||||
Main.uiGroup.insert_child_above(group, global.window_group);
|
||||
|
||||
switchData.monitors.push(group);
|
||||
}
|
||||
|
||||
if (Meta.disable_unredirect_for_display)
|
||||
Meta.disable_unredirect_for_display(global.display);
|
||||
else // new in GS 48
|
||||
global.compositor.disable_unredirect();
|
||||
},
|
||||
};
|
632
extensions/48/vertical-workspaces/lib/windowPreview.js
Normal file
632
extensions/48/vertical-workspaces/lib/windowPreview.js
Normal file
|
@ -0,0 +1,632 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* windowPreview.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import Clutter from 'gi://Clutter';
|
||||
import St from 'gi://St';
|
||||
import Meta from 'gi://Meta';
|
||||
import Shell from 'gi://Shell';
|
||||
import Pango from 'gi://Pango';
|
||||
import Graphene from 'gi://Graphene';
|
||||
import Atk from 'gi://Atk';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
|
||||
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js';
|
||||
import * as WindowPreview from 'resource:///org/gnome/shell/ui/windowPreview.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
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 ICON_OVERLAP = 0.7;
|
||||
const ICON_TITLE_SPACING = 6;
|
||||
|
||||
const ControlsState = OverviewControls.ControlsState;
|
||||
|
||||
export const WindowPreviewModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('windowPreviewModule');
|
||||
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(' WindowPreviewModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
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; // incompatible
|
||||
console.debug(' WindowPreviewModule - Activated');
|
||||
}
|
||||
|
||||
_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();
|
||||
|
||||
this._overrides = null;
|
||||
|
||||
console.debug(' WindowPreviewModule - Disabled');
|
||||
}
|
||||
};
|
||||
|
||||
const WindowPreviewCommon = {
|
||||
_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 windowContainer = new Clutter.Actor({
|
||||
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,
|
||||
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._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 && global.windowThumbnails) {
|
||||
this._removeLaters();
|
||||
global.windowThumbnails?.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 && global.windowThumbnails) {
|
||||
this._removeLaters();
|
||||
global.windowThumbnails?.createThumbnail(metaWindow);
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
}
|
||||
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 && global.windowThumbnails) {
|
||||
this._removeLaters();
|
||||
global.windowThumbnails?.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);
|
||||
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;
|
||||
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;
|
||||
this._icon.scale_y = 0;
|
||||
this._title.opacity = 0;
|
||||
}
|
||||
|
||||
if (opt.ALWAYS_SHOW_WIN_TITLES)
|
||||
this._title.show();
|
||||
|
||||
if (opt.OVERVIEW_MODE === 1) {
|
||||
// spread windows on hover
|
||||
this._wsStateConId = this.connect('enter-event', () => {
|
||||
// prevent spreading windows immediately after entering overview
|
||||
if (global.get_pointer()[0] === opt.showingPointerX || Main.overview._overview._controls._stateAdjustment.value < 1)
|
||||
return;
|
||||
|
||||
opt.WORKSPACE_MODE = 1;
|
||||
const view = this._workspace.get_parent();
|
||||
view.exposeWindows();
|
||||
this.disconnect(this._wsStateConId);
|
||||
});
|
||||
}
|
||||
|
||||
if (opt.OVERVIEW_MODE) {
|
||||
// show window icon and title on ws windows spread
|
||||
this._stateAdjustmentSigId = this._workspace.stateAdjustment.connect('notify::value', this._updateIconScale.bind(this));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
_closeWinAction() {
|
||||
this.hide();
|
||||
this._deleteAll();
|
||||
},
|
||||
|
||||
_removeLaters() {
|
||||
if (this._longPressLater) {
|
||||
const laters = global.compositor.get_laters();
|
||||
laters.remove(this._longPressLater);
|
||||
delete this._longPressLater;
|
||||
}
|
||||
},
|
||||
|
||||
_searchAppWindowsAction() {
|
||||
// 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
|
||||
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();
|
||||
|
||||
// Current state - 0 - HIDDEN, 1 - WINDOW_PICKER, 2 - APP_GRID
|
||||
const primaryMonitor = this.metaWindow.get_monitor() === global.display.get_primary_monitor();
|
||||
|
||||
const visible =
|
||||
(initialState > ControlsState.HIDDEN || finalState > ControlsState.HIDDEN) &&
|
||||
!(finalState === ControlsState.APP_GRID && opt.WS_ANIMATION && primaryMonitor);
|
||||
|
||||
let scale = 0;
|
||||
if (visible)
|
||||
scale = currentState >= 1 ? 1 : currentState % 1;
|
||||
|
||||
if (!primaryMonitor && opt.WORKSPACE_MODE &&
|
||||
((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) ||
|
||||
(initialState === ControlsState.APP_GRID && finalState === ControlsState.WINDOW_PICKER))
|
||||
)
|
||||
scale = 1;
|
||||
else if (!primaryMonitor && opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE)
|
||||
scale = 0;
|
||||
/* } else if (primaryMonitor && ((initialState === ControlsState.WINDOW_PICKER && finalState === ControlsState.APP_GRID) ||
|
||||
initialState === ControlsState.APP_GRID && finalState === ControlsState.HIDDEN)) {*/
|
||||
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)
|
||||
scale = this._workspace._background._stateAdjustment.value;
|
||||
else if ((finalState === 1 && !opt.WORKSPACE_MODE) || (finalState === 0 && !opt.WORKSPACE_MODE))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opt.WS_ANIMATION && (Main.overview.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.set({
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
});
|
||||
this._title.ease({
|
||||
duration: 100,
|
||||
opacity: 255,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
} else {
|
||||
this._title.opacity = 0;
|
||||
this._icon.set({
|
||||
scale_x: scale,
|
||||
scale_y: scale,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
showOverlay(animate) {
|
||||
if (!this._overlayEnabled)
|
||||
return;
|
||||
|
||||
if (this._overlayShown)
|
||||
return;
|
||||
|
||||
this._overlayShown = true;
|
||||
this._restack();
|
||||
|
||||
// If we're supposed to animate and an animation in our direction
|
||||
// is already happening, let that one continue
|
||||
const ongoingTransition = this._title.get_transition('opacity');
|
||||
if (animate &&
|
||||
ongoingTransition &&
|
||||
ongoingTransition.get_interval().peek_final_value() === 255)
|
||||
return;
|
||||
|
||||
const toShow = this._windowCanClose() && opt.SHOW_CLOSE_BUTTON
|
||||
? [this._closeButton]
|
||||
: [];
|
||||
|
||||
if (!opt.ALWAYS_SHOW_WIN_TITLES)
|
||||
toShow.push(this._title);
|
||||
|
||||
|
||||
toShow.forEach(a => {
|
||||
a.opacity = 0;
|
||||
a.show();
|
||||
a.ease({
|
||||
opacity: 255,
|
||||
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
});
|
||||
|
||||
const [width, height] = this.window_container.get_size();
|
||||
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
||||
const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
|
||||
const origSize = Math.max(width, height);
|
||||
const scale = (origSize + activeExtraSize) / origSize;
|
||||
|
||||
this.window_container.ease({
|
||||
scale_x: scale,
|
||||
scale_y: scale,
|
||||
duration: animate ? WINDOW_SCALE_TIME : 0,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
|
||||
this.emit('show-chrome');
|
||||
},
|
||||
|
||||
hideOverlay(animate) {
|
||||
if (!this._overlayShown)
|
||||
return;
|
||||
this._overlayShown = false;
|
||||
|
||||
// When leaving overview, mark the window for activation if needed
|
||||
// The marked window is activated during _onDestroy()
|
||||
const leavingOverview = Main.overview._overview.controls._stateAdjustment.value < 1;
|
||||
if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && leavingOverview)
|
||||
this._activateSelected = true;
|
||||
|
||||
// Prevent restacking the preview if it should remain on top
|
||||
// while leaving overview
|
||||
if (!(opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && leavingOverview))
|
||||
this._restack();
|
||||
|
||||
// If we're supposed to animate and an animation in our direction
|
||||
// is already happening, let that one continue
|
||||
const ongoingTransition = this._title.get_transition('opacity');
|
||||
if (animate &&
|
||||
ongoingTransition &&
|
||||
ongoingTransition.get_interval().peek_final_value() === 0)
|
||||
return;
|
||||
|
||||
const toHide = [this._closeButton];
|
||||
|
||||
if (!opt.ALWAYS_SHOW_WIN_TITLES)
|
||||
toHide.push(this._title);
|
||||
|
||||
toHide.forEach(a => {
|
||||
a.opacity = 255;
|
||||
a.ease({
|
||||
opacity: 0,
|
||||
duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => a.hide(),
|
||||
});
|
||||
});
|
||||
|
||||
if (this.window_container) {
|
||||
this.window_container.ease({
|
||||
scale_x: 1,
|
||||
scale_y: 1,
|
||||
duration: animate ? WINDOW_SCALE_TIME : 0,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
overlapHeights() {
|
||||
const [, titleHeight] = this._title.get_preferred_height(-1);
|
||||
|
||||
const topOverlap = 0;
|
||||
const bottomOverlap = opt.WIN_TITLES_POSITION === 2 ? titleHeight + ICON_TITLE_SPACING : 0;
|
||||
|
||||
return [topOverlap, bottomOverlap];
|
||||
},
|
||||
|
||||
_onDestroy() {
|
||||
if (this._activateSelected)
|
||||
this._activate();
|
||||
|
||||
this.metaWindow._delegate = null;
|
||||
this._delegate = null;
|
||||
this._destroyed = true;
|
||||
|
||||
if (this._longPressLater) {
|
||||
const laters = global.compositor.get_laters();
|
||||
laters.remove(this._longPressLater);
|
||||
delete this._longPressLater;
|
||||
}
|
||||
|
||||
if (this._idleHideOverlayId > 0) {
|
||||
GLib.source_remove(this._idleHideOverlayId);
|
||||
this._idleHideOverlayId = 0;
|
||||
}
|
||||
|
||||
if (this.inDrag) {
|
||||
this.emit('drag-end');
|
||||
this.inDrag = false;
|
||||
}
|
||||
|
||||
if (this._stateAdjustmentSigId)
|
||||
this._workspace.stateAdjustment.disconnect(this._stateAdjustmentSigId);
|
||||
},
|
||||
};
|
478
extensions/48/vertical-workspaces/lib/workspace.js
Normal file
478
extensions/48/vertical-workspaces/lib/workspace.js
Normal file
|
@ -0,0 +1,478 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* workspace.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import St from 'gi://St';
|
||||
// import Graphene from 'gi://Graphene';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Workspace from 'resource:///org/gnome/shell/ui/workspace.js';
|
||||
import * as Params from 'resource:///org/gnome/shell/misc/params.js';
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
let WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95;
|
||||
|
||||
export const WorkspaceModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('workspaceModule');
|
||||
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(' WorkspaceModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
this._overrides.addOverride('WorkspaceBackground', Workspace.WorkspaceBackground.prototype, WorkspaceBackground);
|
||||
|
||||
// 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;
|
||||
console.debug(' WorkspaceModule - Disabled');
|
||||
}
|
||||
|
||||
setWindowPreviewMaxScale(scale) {
|
||||
WINDOW_PREVIEW_MAXIMUM_SCALE = scale;
|
||||
}
|
||||
};
|
||||
|
||||
// Workaround for an upstream bug affecting window scaling and positioning:
|
||||
//
|
||||
// Issue:
|
||||
// - Smaller windows cannot scale below 0.95 (WINDOW_PREVIEW_MAXIMUM_SCALE)
|
||||
// when their target scale for the spread windows view (workspace state 1)
|
||||
// exceeds the scale needed for workspace state 0.
|
||||
// - In workspace state 0 (where windows are not spread and scale matches the workspace),
|
||||
// the window aligns correctly to the top-left corner but does not scale with the workspace,
|
||||
// causing visual issues and the window may exceed the workspace border.
|
||||
//
|
||||
// Effects:
|
||||
// - Particularly noticeable in OVERVIEW_MODE 1 with a single smaller window on the workspace.
|
||||
// - Also impacts the appGrid transition animation.
|
||||
const WorkspaceLayout = {
|
||||
// injection to _init()
|
||||
after__init() {
|
||||
if (opt.OVERVIEW_MODE !== 1)
|
||||
WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95;
|
||||
if (opt.OVERVIEW_MODE === 1) {
|
||||
this._stateAdjustment.connect('notify::value', () => {
|
||||
// When transitioning to workspace state 1 (WINDOW_PICKER),
|
||||
// replace the constant with the original value.
|
||||
// Ensure that the scale for workspace state 0 is smaller
|
||||
// than the minimum possible scale of any window on the workspace,
|
||||
// so they stay at their real size relative to ws preview
|
||||
const scale = this._stateAdjustment.value ? 0.95 : 0.1;
|
||||
if (scale !== WINDOW_PREVIEW_MAXIMUM_SCALE)
|
||||
WINDOW_PREVIEW_MAXIMUM_SCALE = scale;
|
||||
// Force recalculation of the target layout
|
||||
// to ensure that the new WINDOW_PREVIEW_MAXIMUM_SCALE is applied
|
||||
if (this._stateAdjustment.value < 0.5)
|
||||
this._needsLayout = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_adjustSpacingAndPadding(rowSpacing, colSpacing, containerBox) {
|
||||
if (this._sortedWindows.length === 0)
|
||||
return [rowSpacing, colSpacing, containerBox];
|
||||
|
||||
// All of the overlays have the same chrome sizes,
|
||||
// so just pick the first one.
|
||||
const window = this._sortedWindows[0];
|
||||
|
||||
const [topOversize, bottomOversize] = window.chromeHeights();
|
||||
const [leftOversize, rightOversize] = window.chromeWidths();
|
||||
|
||||
let oversize = Math.max(topOversize, bottomOversize, leftOversize, rightOversize);
|
||||
|
||||
if (rowSpacing !== null)
|
||||
rowSpacing += oversize;
|
||||
if (colSpacing !== null)
|
||||
colSpacing += oversize;
|
||||
|
||||
// Chrome highlights and window titles may exceed the workspace preview area
|
||||
// and also the screen area if there is no overview element below/above/on_the_right of the workspace
|
||||
// The original code tests whether window titles are out of the screen and applies correction accordingly
|
||||
// That is a problem when workspaces are vertically stacked, because this method is called even during transitions between workspaces
|
||||
// In V-Shell, this issue can be solved by reducing the workspace preview scale in the Settings
|
||||
|
||||
// Original code - horizontal orientation only
|
||||
/* if (containerBox) {
|
||||
const monitor = Main.layoutManager.monitors[this._monitorIndex];
|
||||
|
||||
const bottomPoint = new Graphene.Point3D({ y: containerBox.y2 });
|
||||
const transformedBottomPoint =
|
||||
this._container.apply_transform_to_point(bottomPoint);
|
||||
const bottomFreeSpace =
|
||||
(monitor.y + monitor.height) - transformedBottomPoint.y;
|
||||
|
||||
const [, bottomOverlap] = window.overlapHeights();
|
||||
|
||||
if ((bottomOverlap + oversize) > bottomFreeSpace)
|
||||
containerBox.y2 -= (bottomOverlap + oversize) - bottomFreeSpace;
|
||||
}*/
|
||||
|
||||
// Alternative code reducing the box size unconditionally
|
||||
/* if (containerBox) {
|
||||
const [, bottomOverlap] = window.overlapHeights();
|
||||
|
||||
// Adjusting x1/x2 here is pointless,
|
||||
// x1 only moves window previews to the right and down, x2 has no effect
|
||||
// Prevent window previews from overlapping a workspace preview
|
||||
oversize *= 1.5;
|
||||
containerBox.y1 += oversize;
|
||||
containerBox.y2 -= bottomOverlap + oversize;
|
||||
}*/
|
||||
|
||||
return [rowSpacing, colSpacing, containerBox];
|
||||
},
|
||||
|
||||
_createBestLayout(area) {
|
||||
const [rowSpacing, columnSpacing] =
|
||||
this._adjustSpacingAndPadding(this._spacing, this._spacing, null);
|
||||
|
||||
// We look for the largest scale that allows us to fit the
|
||||
// largest row/tallest column on the workspace.
|
||||
this._layoutStrategy = new UnalignedLayoutStrategy({
|
||||
monitor: Main.layoutManager.monitors[this._monitorIndex],
|
||||
rowSpacing,
|
||||
columnSpacing,
|
||||
});
|
||||
|
||||
let lastLayout = null;
|
||||
let lastNumColumns = -1;
|
||||
let lastScale = 0;
|
||||
let lastSpace = 0;
|
||||
|
||||
for (let numRows = 1; ; numRows++) {
|
||||
const numColumns = Math.ceil(this._sortedWindows.length / numRows);
|
||||
|
||||
// If adding a new row does not change column count just stop
|
||||
// (for instance: 9 windows, with 3 rows -> 3 columns, 4 rows ->
|
||||
// 3 columns as well => just use 3 rows then)
|
||||
if (numColumns === lastNumColumns)
|
||||
break;
|
||||
|
||||
const layout = this._layoutStrategy.computeLayout(this._sortedWindows, {
|
||||
numRows,
|
||||
});
|
||||
|
||||
const [scale, space] = this._layoutStrategy.computeScaleAndSpace(layout, area);
|
||||
|
||||
if (lastLayout && !this._isBetterScaleAndSpace(lastScale, lastSpace, scale, space))
|
||||
break;
|
||||
|
||||
lastLayout = layout;
|
||||
lastNumColumns = numColumns;
|
||||
lastScale = scale;
|
||||
lastSpace = space;
|
||||
}
|
||||
|
||||
return lastLayout;
|
||||
},
|
||||
};
|
||||
|
||||
class UnalignedLayoutStrategy extends Workspace.LayoutStrategy {
|
||||
_newRow() {
|
||||
// Row properties:
|
||||
//
|
||||
// * x, y are the position of row, relative to area
|
||||
//
|
||||
// * width, height are the scaled versions of fullWidth, fullHeight
|
||||
//
|
||||
// * width also has the spacing in between windows. It's not in
|
||||
// fullWidth, as the spacing is constant, whereas fullWidth is
|
||||
// meant to be scaled
|
||||
//
|
||||
// * neither height/fullHeight have any sort of spacing or padding
|
||||
return {
|
||||
x: 0, y: 0,
|
||||
width: 0, height: 0,
|
||||
fullWidth: 0, fullHeight: 0,
|
||||
windows: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Computes and returns an individual scaling factor for @window,
|
||||
// to be applied in addition to the overall layout scale.
|
||||
_computeWindowScale(window) {
|
||||
// Since we align windows next to each other, the height of the
|
||||
// thumbnails is much more important to preserve than the width of
|
||||
// them, so two windows with equal height, but maybe differering
|
||||
// widths line up.
|
||||
let ratio = window.boundingBox.height / this._monitor.height;
|
||||
|
||||
// The purpose of this manipulation here is to prevent windows
|
||||
// from getting too small. For something like a calculator window,
|
||||
// we need to bump up the size just a bit to make sure it looks
|
||||
// good. We'll use a multiplier of 1.5 for this.
|
||||
|
||||
// Map from [0, 1] to [1.5, 1]
|
||||
return Util.lerp(1.5, 1, ratio);
|
||||
}
|
||||
|
||||
_computeRowSizes(layout) {
|
||||
let { rows, scale } = layout;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
row.width = row.fullWidth * scale + (row.windows.length - 1) * this._columnSpacing;
|
||||
row.height = row.fullHeight * scale;
|
||||
}
|
||||
}
|
||||
|
||||
_keepSameRow(row, window, width, idealRowWidth) {
|
||||
if (row.fullWidth + width <= idealRowWidth)
|
||||
return true;
|
||||
|
||||
let oldRatio = row.fullWidth / idealRowWidth;
|
||||
let newRatio = (row.fullWidth + width) / idealRowWidth;
|
||||
|
||||
if (Math.abs(1 - newRatio) < Math.abs(1 - oldRatio))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_sortRow(row) {
|
||||
// Sort windows horizontally to minimize travel distance.
|
||||
// This affects in what order the windows end up in a row.
|
||||
row.windows.sort((a, b) => a.windowCenter.x - b.windowCenter.x);
|
||||
}
|
||||
|
||||
computeLayout(windows, layoutParams) {
|
||||
layoutParams = Params.parse(layoutParams, {
|
||||
numRows: 0,
|
||||
});
|
||||
|
||||
if (layoutParams.numRows === 0)
|
||||
throw new Error(`${this.constructor.name}: No numRows given in layout params`);
|
||||
|
||||
const numRows = layoutParams.numRows;
|
||||
|
||||
let rows = [];
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let window = windows[i];
|
||||
let s = this._computeWindowScale(window);
|
||||
totalWidth += window.boundingBox.width * s;
|
||||
}
|
||||
|
||||
let idealRowWidth = totalWidth / numRows;
|
||||
|
||||
// Sort windows vertically to minimize travel distance.
|
||||
// This affects what rows the windows get placed in.
|
||||
let sortedWindows = windows.slice();
|
||||
sortedWindows.sort((a, b) => a.windowCenter.y - b.windowCenter.y);
|
||||
|
||||
let windowIdx = 0;
|
||||
for (let i = 0; i < numRows; i++) {
|
||||
let row = this._newRow();
|
||||
rows.push(row);
|
||||
|
||||
for (; windowIdx < sortedWindows.length; windowIdx++) {
|
||||
let window = sortedWindows[windowIdx];
|
||||
let s = this._computeWindowScale(window);
|
||||
let width = window.boundingBox.width * s;
|
||||
let height = window.boundingBox.height * s;
|
||||
row.fullHeight = Math.max(row.fullHeight, height);
|
||||
|
||||
// either new width is < idealWidth or new width is nearer from idealWidth then oldWidth
|
||||
if (this._keepSameRow(row, window, width, idealRowWidth) || (i === numRows - 1)) {
|
||||
row.windows.push(window);
|
||||
row.fullWidth += width;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gridHeight = 0;
|
||||
let maxRow;
|
||||
for (let i = 0; i < numRows; i++) {
|
||||
let row = rows[i];
|
||||
this._sortRow(row);
|
||||
|
||||
if (!maxRow || row.fullWidth > maxRow.fullWidth)
|
||||
maxRow = row;
|
||||
gridHeight += row.fullHeight;
|
||||
}
|
||||
|
||||
return {
|
||||
numRows,
|
||||
rows,
|
||||
maxColumns: maxRow.windows.length,
|
||||
gridWidth: maxRow.fullWidth,
|
||||
gridHeight,
|
||||
};
|
||||
}
|
||||
|
||||
computeScaleAndSpace(layout, area) {
|
||||
let hspacing = (layout.maxColumns - 1) * this._columnSpacing;
|
||||
let vspacing = (layout.numRows - 1) * this._rowSpacing;
|
||||
|
||||
let spacedWidth = area.width - hspacing;
|
||||
let spacedHeight = area.height - vspacing;
|
||||
|
||||
let horizontalScale = spacedWidth / layout.gridWidth;
|
||||
let verticalScale = spacedHeight / layout.gridHeight;
|
||||
|
||||
// Thumbnails should be less than 70% of the original size
|
||||
let scale = Math.min(
|
||||
horizontalScale, verticalScale, WINDOW_PREVIEW_MAXIMUM_SCALE);
|
||||
|
||||
let scaledLayoutWidth = layout.gridWidth * scale + hspacing;
|
||||
let scaledLayoutHeight = layout.gridHeight * scale + vspacing;
|
||||
let space = (scaledLayoutWidth * scaledLayoutHeight) / (area.width * area.height);
|
||||
|
||||
layout.scale = scale;
|
||||
|
||||
return [scale, space];
|
||||
}
|
||||
|
||||
computeWindowSlots(layout, area) {
|
||||
this._computeRowSizes(layout);
|
||||
|
||||
let { rows, scale } = layout;
|
||||
|
||||
let slots = [];
|
||||
|
||||
// Do this in three parts.
|
||||
let heightWithoutSpacing = 0;
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
heightWithoutSpacing += row.height;
|
||||
}
|
||||
|
||||
let verticalSpacing = (rows.length - 1) * this._rowSpacing;
|
||||
let additionalVerticalScale = Math.min(1, (area.height - verticalSpacing) / heightWithoutSpacing);
|
||||
|
||||
// keep track how much smaller the grid becomes due to scaling
|
||||
// so it can be centered again
|
||||
let compensation = 0;
|
||||
let y = 0;
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
let row = rows[i];
|
||||
|
||||
// If this window layout row doesn't fit in the actual
|
||||
// geometry, then apply an additional scale to it.
|
||||
let horizontalSpacing = (row.windows.length - 1) * this._columnSpacing;
|
||||
let widthWithoutSpacing = row.width - horizontalSpacing;
|
||||
let additionalHorizontalScale = Math.min(1, (area.width - horizontalSpacing) / widthWithoutSpacing);
|
||||
|
||||
if (additionalHorizontalScale < additionalVerticalScale) {
|
||||
row.additionalScale = additionalHorizontalScale;
|
||||
// Only consider the scaling in addition to the vertical scaling for centering.
|
||||
compensation += (additionalVerticalScale - additionalHorizontalScale) * row.height;
|
||||
} else {
|
||||
row.additionalScale = additionalVerticalScale;
|
||||
// No compensation when scaling vertically since centering based on a too large
|
||||
// height would undo what vertical scaling is trying to achieve.
|
||||
}
|
||||
|
||||
row.x = area.x + (Math.max(area.width - (widthWithoutSpacing * row.additionalScale + horizontalSpacing), 0) / 2);
|
||||
row.y = area.y + (Math.max(area.height - (heightWithoutSpacing + verticalSpacing), 0) / 2) + y;
|
||||
y += row.height * row.additionalScale + this._rowSpacing;
|
||||
}
|
||||
|
||||
compensation /= 2;
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i];
|
||||
const rowY = row.y + compensation;
|
||||
const rowHeight = row.height * row.additionalScale;
|
||||
|
||||
let x = row.x;
|
||||
for (let j = 0; j < row.windows.length; j++) {
|
||||
let window = row.windows[j];
|
||||
|
||||
let s = scale * this._computeWindowScale(window) * row.additionalScale;
|
||||
let cellWidth = window.boundingBox.width * s;
|
||||
let cellHeight = window.boundingBox.height * s;
|
||||
|
||||
s = Math.min(s, WINDOW_PREVIEW_MAXIMUM_SCALE);
|
||||
let cloneWidth = window.boundingBox.width * s;
|
||||
const cloneHeight = window.boundingBox.height * s;
|
||||
|
||||
let cloneX = x + (cellWidth - cloneWidth) / 2;
|
||||
let cloneY;
|
||||
|
||||
// If there's only one row, align windows vertically centered inside the row
|
||||
if (rows.length === 1)
|
||||
cloneY = rowY + (rowHeight - cloneHeight) / 2;
|
||||
// If there are multiple rows, align windows to the bottom edge of the row
|
||||
else
|
||||
cloneY = rowY + rowHeight - cellHeight;
|
||||
|
||||
// Align with the pixel grid to prevent blurry windows at scale = 1
|
||||
cloneX = Math.floor(cloneX);
|
||||
cloneY = Math.floor(cloneY);
|
||||
|
||||
slots.push([cloneX, cloneY, cloneWidth, cloneHeight, window]);
|
||||
x += cellWidth + this._columnSpacing;
|
||||
}
|
||||
}
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
|
||||
const WorkspaceBackground = {
|
||||
_updateBorderRadius(value = false) {
|
||||
// don't round already rounded corners during exposing windows
|
||||
if (value === false && opt.OVERVIEW_MODE === 1)
|
||||
return;
|
||||
|
||||
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
||||
const cornerRadius = scaleFactor * opt.WS_PREVIEW_BG_RADIUS;
|
||||
|
||||
const backgroundContent = this._bgManager.backgroundActor.content;
|
||||
value = value !== false
|
||||
? value
|
||||
: this._stateAdjustment.value;
|
||||
|
||||
backgroundContent.rounded_clip_radius =
|
||||
Util.lerp(0, cornerRadius, value);
|
||||
},
|
||||
};
|
288
extensions/48/vertical-workspaces/lib/workspaceAnimation.js
Normal file
288
extensions/48/vertical-workspaces/lib/workspaceAnimation.js
Normal file
|
@ -0,0 +1,288 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* workspacesAnimation.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
import GObject from 'gi://GObject';
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
|
||||
import * as WorkspaceSwitcherPopup from 'resource:///org/gnome/shell/ui/workspaceSwitcherPopup.js';
|
||||
import * as WorkspaceAnimation from 'resource:///org/gnome/shell/ui/workspaceAnimation.js';
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const WorkspaceAnimationModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
this._origBaseDistance = null;
|
||||
this._wsAnimationSwipeBeginId = 0;
|
||||
this._wsAnimationSwipeUpdateId = 0;
|
||||
this._wsAnimationSwipeEndId = 0;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('workspaceAnimationModule');
|
||||
const conflict = !WorkspaceAnimation.MonitorGroup;
|
||||
if (conflict)
|
||||
console.warn(`[${Me.metadata.name}] Warning: "WorkspaceAnimation" module disabled due to compatibility - GNOME Shell 45.1 or later is required`);
|
||||
|
||||
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(' WorkspaceAnimationModule - Keeping untouched');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
this._overrides.addOverride('MonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroup);
|
||||
this._connectWsAnimationSwipeTracker();
|
||||
this._overrideMonitorGroupProperty();
|
||||
|
||||
console.debug(' WorkspaceAnimationModule - Activated');
|
||||
}
|
||||
|
||||
_disableModule() {
|
||||
if (this._overrides)
|
||||
this._overrides.removeAll();
|
||||
this._overrides = null;
|
||||
const reset = true;
|
||||
this._connectWsAnimationSwipeTracker(reset);
|
||||
this._overrideMonitorGroupProperty(reset);
|
||||
|
||||
console.debug(' WorkspaceAnimationModule - Disabled');
|
||||
}
|
||||
|
||||
_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));
|
||||
}
|
||||
}
|
||||
|
||||
_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));
|
||||
}
|
||||
}
|
||||
|
||||
_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);
|
||||
}
|
||||
|
||||
_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 = 0; // 100 * St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const MonitorGroup = {
|
||||
_init(monitor, workspaceIndices, movingWindow) {
|
||||
St.Widget.prototype._init.bind(this)({
|
||||
clip_to_allocation: true,
|
||||
style_class: 'workspace-animation',
|
||||
});
|
||||
|
||||
this._monitor = monitor;
|
||||
|
||||
const constraint = new Layout.MonitorConstraint({ index: monitor.index });
|
||||
this.add_constraint(constraint);
|
||||
|
||||
this._container = new Clutter.Actor();
|
||||
this.add_child(this._container);
|
||||
|
||||
const stickyGroup = new WorkspaceAnimation.WorkspaceGroup(null, monitor, movingWindow);
|
||||
stickyGroup._windowRecords.forEach(r => {
|
||||
const metaWin = r.windowActor.metaWindow;
|
||||
// conky is sticky but should never get above other windows during ws animation
|
||||
// so we hide it from the overlay group, we will see the original if not covered by other windows
|
||||
if (metaWin.wm_class === 'conky')
|
||||
r.clone.opacity = 0;
|
||||
});
|
||||
this.add_child(stickyGroup);
|
||||
|
||||
this._workspaceGroups = [];
|
||||
|
||||
const workspaceManager = global.workspace_manager;
|
||||
const vertical = workspaceManager.layout_rows === -1;
|
||||
const activeWorkspace = workspaceManager.get_active_workspace();
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
for (const i of workspaceIndices) {
|
||||
const ws = workspaceManager.get_workspace_by_index(i);
|
||||
const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && w.is_fullscreen());
|
||||
|
||||
if (i > 0 && vertical && !fullscreen && monitor.index === Main.layoutManager.primaryIndex) {
|
||||
// We have to shift windows up or down by the height of the panel to prevent having a
|
||||
// visible gap between the windows while switching workspaces. Since fullscreen windows
|
||||
// hide the panel, they don't need to be shifted up or down.
|
||||
y -= Main.panel.height;
|
||||
}
|
||||
|
||||
const group = new WorkspaceAnimation.WorkspaceGroup(ws, monitor, movingWindow);
|
||||
|
||||
this._workspaceGroups.push(group);
|
||||
this._container.add_child(group);
|
||||
group.set_position(x, y);
|
||||
|
||||
if (vertical)
|
||||
y += this.baseDistance;
|
||||
else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
|
||||
x -= this.baseDistance;
|
||||
else
|
||||
x += this.baseDistance;
|
||||
}
|
||||
|
||||
this.progress = this.getWorkspaceProgress(activeWorkspace);
|
||||
|
||||
if (monitor.index === Main.layoutManager.primaryIndex) {
|
||||
this._workspacesAdjustment = Main.createWorkspacesAdjustment(this);
|
||||
this.bind_property_full('progress',
|
||||
this._workspacesAdjustment, 'value',
|
||||
GObject.BindingFlags.SYNC_CREATE,
|
||||
(bind, source) => {
|
||||
const indices = [
|
||||
workspaceIndices[Math.floor(source)],
|
||||
workspaceIndices[Math.ceil(source)],
|
||||
];
|
||||
return [true, Util.lerp(...indices, source % 1.0)];
|
||||
},
|
||||
null);
|
||||
|
||||
this.connect('destroy', () => {
|
||||
// for some reason _workspaceAdjustment bound to the progress property in V-Shell
|
||||
// causes the adjustment doesn't reach a whole number
|
||||
// when switching ws up and that breaks the showing overview animation
|
||||
// as a workaround round workspacesDisplay._scrollAdjustment value on destroy
|
||||
// but it should be handled elsewhere as this workaround doesn't work when this module is disabled
|
||||
const workspacesAdj = Main.overview._overview.controls._workspacesDisplay._scrollAdjustment;
|
||||
workspacesAdj.value = Math.round(workspacesAdj.value);
|
||||
delete this._workspacesAdjustment;
|
||||
});
|
||||
}
|
||||
|
||||
if (!opt.STATIC_WS_SWITCHER_BG)
|
||||
return;
|
||||
|
||||
// we have two options to implement static bg feature
|
||||
// one is adding background to monitorGroup
|
||||
// but this one has disadvantage - sticky windows will be always on top of animated windows
|
||||
// which is bad for conky, for example, that window should be always below
|
||||
/* this._bgManager = new Background.BackgroundManager({
|
||||
container: this,
|
||||
monitorIndex: this._monitor.index,
|
||||
controlPosition: false,
|
||||
});*/
|
||||
|
||||
// the second option is to make background of the monitorGroup transparent so the real desktop content will stay visible,
|
||||
// hide windows that should be animated and keep only sticky windows
|
||||
// we can keep certain sticky windows bellow and also extensions like DING (icons on desktop) will stay visible
|
||||
this.set_style('background-color: transparent;');
|
||||
// stickyGroup holds the Always on Visible Workspace windows to keep them static and above other windows during animation
|
||||
this._hiddenWindows = [];
|
||||
// remove (hide) background wallpaper from the animation, we will see the original one
|
||||
this._workspaceGroups.forEach(w => {
|
||||
w._background.opacity = 0;
|
||||
});
|
||||
// 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()) &&
|
||||
!(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;
|
||||
this._hiddenWindows.push(actor);
|
||||
}
|
||||
});
|
||||
|
||||
// restore all hidden windows at the end of animation
|
||||
// todo - actors removed during transition need to be removed from the list to avoid access to destroyed actor
|
||||
this.connect('destroy', () => {
|
||||
this._hiddenWindows.forEach(actor => {
|
||||
actor.scale_x = 1;
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
107
extensions/48/vertical-workspaces/lib/workspaceSwitcherPopup.js
Normal file
107
extensions/48/vertical-workspaces/lib/workspaceSwitcherPopup.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* workspacesSwitcherPopup.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as WorkspaceSwitcherPopup from 'resource:///org/gnome/shell/ui/workspaceSwitcherPopup.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
export const WorkspaceSwitcherPopupModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
update(reset) {
|
||||
this.moduleEnabled = opt.get('workspaceSwitcherPopupModule');
|
||||
const conflict = Me.Util.getEnabledExtensions('workspace-switcher-manager').length ||
|
||||
Me.Util.getEnabledExtensions('WsSwitcherPopupManager').length;
|
||||
if (conflict && !reset)
|
||||
console.warn(`[${Me.metadata.name}] Warning: "WorkspaceSwitcherPopup" module disabled due to potential conflict with another extension`);
|
||||
|
||||
reset = reset || !this.moduleEnabled || conflict;
|
||||
|
||||
// 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');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
this._overrides.addOverride('WorkspaceSwitcherPopup', WorkspaceSwitcherPopup.WorkspaceSwitcherPopup.prototype, WorkspaceSwitcherPopupCommon);
|
||||
console.debug(' WorkspaceSwitcherPopupModule - Activated');
|
||||
}
|
||||
|
||||
_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.add_style_class_name('ws-switcher-vertical');
|
||||
}
|
||||
this._list.set_style('margin: 0;');
|
||||
if (this.get_constraints()[0])
|
||||
this.remove_constraint(this.get_constraints()[0]);
|
||||
},
|
||||
|
||||
// injection to display()
|
||||
after_display() {
|
||||
if (opt.WS_SW_POPUP_MODE)
|
||||
this._setPopupPosition();
|
||||
else
|
||||
this.opacity = 0;
|
||||
},
|
||||
|
||||
_setPopupPosition() {
|
||||
let workArea;
|
||||
if (opt.WS_SW_POPUP_MODE === 1)
|
||||
workArea = global.display.get_monitor_geometry(Main.layoutManager.primaryIndex);
|
||||
else
|
||||
workArea = global.display.get_monitor_geometry(global.display.get_current_monitor());
|
||||
|
||||
|
||||
let [, natHeight] = this.get_preferred_height(global.screen_width);
|
||||
let [, natWidth] = this.get_preferred_width(natHeight);
|
||||
let h = opt.WS_SW_POPUP_H_POSITION;
|
||||
let v = opt.WS_SW_POPUP_V_POSITION;
|
||||
this.x = workArea.x + Math.floor((workArea.width - natWidth) * h);
|
||||
this.y = workArea.y + Math.floor((workArea.height - natHeight) * v);
|
||||
this.set_position(this.x, this.y);
|
||||
},
|
||||
};
|
1253
extensions/48/vertical-workspaces/lib/workspaceThumbnail.js
Normal file
1253
extensions/48/vertical-workspaces/lib/workspaceThumbnail.js
Normal file
File diff suppressed because it is too large
Load diff
997
extensions/48/vertical-workspaces/lib/workspacesView.js
Normal file
997
extensions/48/vertical-workspaces/lib/workspacesView.js
Normal file
|
@ -0,0 +1,997 @@
|
|||
/**
|
||||
* V-Shell (Vertical Workspaces)
|
||||
* workspacesView.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 - 2024
|
||||
* @license GPL-3.0
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Clutter from 'gi://Clutter';
|
||||
import St from 'gi://St';
|
||||
import Meta from 'gi://Meta';
|
||||
import GObject from 'gi://GObject';
|
||||
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';
|
||||
import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js';
|
||||
import * as WorkspacesView from 'resource:///org/gnome/shell/ui/workspacesView.js';
|
||||
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js';
|
||||
|
||||
let Me;
|
||||
let opt;
|
||||
|
||||
const ControlsState = OverviewControls.ControlsState;
|
||||
const FitMode = WorkspacesView.FitMode;
|
||||
|
||||
export const WorkspacesViewModule = class {
|
||||
constructor(me) {
|
||||
Me = me;
|
||||
opt = Me.opt;
|
||||
|
||||
this._firstActivation = true;
|
||||
this.moduleEnabled = false;
|
||||
this._overrides = null;
|
||||
}
|
||||
|
||||
cleanGlobals() {
|
||||
Me = null;
|
||||
opt = null;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
_activateModule() {
|
||||
if (!this._overrides)
|
||||
this._overrides = new Me.Util.Overrides();
|
||||
|
||||
const desktopCubeEnabled = Me.Util.getEnabledExtensions('desktop-cube@schneegans.github.com').length;
|
||||
const desktopCubeConflict = desktopCubeEnabled && !opt.ORIENTATION && !opt.OVERVIEW_MODE;
|
||||
|
||||
if (!desktopCubeConflict)
|
||||
this._overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon);
|
||||
else
|
||||
this._overrides.removeOverride('WorkspacesView');
|
||||
|
||||
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) {
|
||||
let [width, height] = box.get_size();
|
||||
const [workspace] = this._workspaces;
|
||||
|
||||
const rtl = this.text_direction === Clutter.TextDirection.RTL;
|
||||
const adj = this._scrollAdjustment;
|
||||
const currentWorkspace = vertical || !rtl
|
||||
? adj.value : adj.upper - adj.value - 1;
|
||||
|
||||
// Single fit mode implies centered too
|
||||
let [x1, y1] = box.get_origin();
|
||||
const [, workspaceWidth] = workspace ? workspace.get_preferred_width(Math.floor(height)) : [0, width];
|
||||
const [, workspaceHeight] = workspace ? workspace.get_preferred_height(workspaceWidth) : [0, height];
|
||||
|
||||
if (vertical) {
|
||||
x1 += (width - workspaceWidth) / 2;
|
||||
y1 -= currentWorkspace * (workspaceHeight + spacing);
|
||||
} else {
|
||||
x1 += (width - workspaceWidth) / 2;
|
||||
x1 -= currentWorkspace * (workspaceWidth + spacing);
|
||||
}
|
||||
|
||||
const fitSingleBox = new Clutter.ActorBox({ x1, y1 });
|
||||
fitSingleBox.set_size(workspaceWidth, workspaceHeight);
|
||||
|
||||
return fitSingleBox;
|
||||
},
|
||||
|
||||
// set spacing between ws previews
|
||||
_getSpacing(box, fitMode, vertical) {
|
||||
const [width, height] = box.get_size();
|
||||
const [workspace] = this._workspaces;
|
||||
|
||||
if (!workspace)
|
||||
return 0;
|
||||
|
||||
let availableSpace;
|
||||
let workspaceSize;
|
||||
if (vertical) {
|
||||
[, workspaceSize] = workspace.get_preferred_height(width);
|
||||
availableSpace = height;
|
||||
} else {
|
||||
[, workspaceSize] = workspace.get_preferred_width(height);
|
||||
availableSpace = width;
|
||||
}
|
||||
|
||||
const spacing = (availableSpace - workspaceSize * 0.4) * (1 - fitMode);
|
||||
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
|
||||
return Math.clamp(spacing,
|
||||
opt.WORKSPACE_MIN_SPACING * scaleFactor,
|
||||
opt.WORKSPACE_MAX_SPACING * scaleFactor);
|
||||
},
|
||||
|
||||
// this function has duplicate in OverviewControls so we use one function for both to avoid issues with syncing them
|
||||
_getFitModeForState(state) {
|
||||
return _getFitModeForState(state);
|
||||
},
|
||||
|
||||
// normal view 0, spread windows 1
|
||||
_getWorkspaceModeForOverviewState(state) {
|
||||
switch (state) {
|
||||
case ControlsState.HIDDEN:
|
||||
return 0;
|
||||
case ControlsState.WINDOW_PICKER:
|
||||
return opt.WORKSPACE_MODE;
|
||||
case ControlsState.APP_GRID:
|
||||
return (this._monitorIndex !== global.display.get_primary_monitor() || !opt.WS_ANIMATION) && !opt.OVERVIEW_MODE ? 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
_updateVisibility() {
|
||||
// visibility handles _updateWorkspacesState()
|
||||
},
|
||||
|
||||
// disable scaling and hide inactive workspaces
|
||||
_updateWorkspacesState() {
|
||||
const adj = this._scrollAdjustment;
|
||||
const fitMode = this._fitModeAdjustment.value;
|
||||
|
||||
let { initialState, finalState, progress, currentState } =
|
||||
this._overviewAdjustment.getStateTransitionParams();
|
||||
|
||||
const workspaceMode = (1 - fitMode) * Util.lerp(
|
||||
this._getWorkspaceModeForOverviewState(initialState),
|
||||
this._getWorkspaceModeForOverviewState(finalState),
|
||||
progress);
|
||||
|
||||
const primaryMonitor = Main.layoutManager.primaryMonitor.index;
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
// and only current and adjacent ws when switching ws
|
||||
w.visible = opt.WS_ANIMATION_ALL ||
|
||||
((this._animating && wsScrollProgress && distanceToCurrentWorkspace <= (opt.NUMBER_OF_VISIBLE_NEIGHBORS + 1)) ||
|
||||
scaleProgress === 1 ||
|
||||
(opt.WORKSPACE_MAX_SPACING >= opt.WS_MAX_SPACING_OFF_SCREEN &&
|
||||
distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS &&
|
||||
currentState === ControlsState.WINDOW_PICKER
|
||||
) ||
|
||||
(this._monitorIndex !== primaryMonitor && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS) ||
|
||||
(!opt.WS_ANIMATION && distanceToCurrentWorkspace < opt.NUMBER_OF_VISIBLE_NEIGHBORS) ||
|
||||
(distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS &&
|
||||
currentState <= ControlsState.WINDOW_PICKER &&
|
||||
(initialState < ControlsState.APP_GRID && finalState < ControlsState.APP_GRID)
|
||||
));
|
||||
|
||||
// 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, move them to their position from outside of the monitor
|
||||
if (currentState === ControlsState.WINDOW_PICKER && !w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && initialState === ControlsState.APP_GRID) {
|
||||
w.remove_all_transitions();
|
||||
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: 300,
|
||||
translation_x: 0,
|
||||
translation_y: 0,
|
||||
opacity: 255,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
|
||||
// force ws preview bg corner radiuses where GS doesn't do it
|
||||
if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1 && distanceToCurrentWorkspace < 2)
|
||||
w._background._updateBorderRadius(Math.min(1, w._overviewAdjustment.value));
|
||||
|
||||
|
||||
// hide workspace background
|
||||
if (!opt.SHOW_WS_PREVIEW_BG && w._background.opacity)
|
||||
w._background.opacity = 0;
|
||||
});
|
||||
},
|
||||
|
||||
exposeWindows(workspaceIndex = null) {
|
||||
let adjustments = [];
|
||||
if (workspaceIndex === null) {
|
||||
this._workspaces.forEach(ws => {
|
||||
adjustments.push(ws._background._stateAdjustment);
|
||||
});
|
||||
} else {
|
||||
adjustments.push(this._workspaces[workspaceIndex]._background._stateAdjustment);
|
||||
}
|
||||
|
||||
opt.WORKSPACE_MODE = 1;
|
||||
adjustments.forEach(adj => {
|
||||
if (adj.value === 0) {
|
||||
adj.ease(1, {
|
||||
duration: 200,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const SecondaryMonitorDisplayCommon = {
|
||||
exposeWindows(...args) {
|
||||
this._workspacesView.exposeWindows(...args);
|
||||
},
|
||||
};
|
||||
|
||||
const SecondaryMonitorDisplayVertical = {
|
||||
_getThumbnailParamsForState(state) {
|
||||
const spacing = opt.SPACING;
|
||||
let opacity, scale, translationX;
|
||||
switch (state) {
|
||||
case ControlsState.HIDDEN:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationX = 0;
|
||||
if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2))
|
||||
translationX = (this._thumbnails.width + spacing) * (opt.SEC_WS_TMB_LEFT ? -1 : 1);
|
||||
|
||||
break;
|
||||
case ControlsState.WINDOW_PICKER:
|
||||
case ControlsState.APP_GRID:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationX = 0;
|
||||
break;
|
||||
default:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationX = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return { opacity, scale, translationX };
|
||||
},
|
||||
|
||||
_getWorkspacesBoxForState(state, box, workArea, wsTmbWidth, spacing) {
|
||||
let workspaceBox = box.copy();
|
||||
|
||||
if (
|
||||
(state === ControlsState.WINDOW_PICKER || state === ControlsState.APP_GRID) &&
|
||||
!(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)
|
||||
) {
|
||||
workspaceBox = workArea.copy();
|
||||
const [startX, startY] = workspaceBox.get_origin();
|
||||
let [width, height] = workspaceBox.get_size();
|
||||
|
||||
let wsBoxWidth = width - (wsTmbWidth ? wsTmbWidth + spacing : 0) - 2 * spacing;
|
||||
let wsBoxHeight = height - 2 * spacing;
|
||||
|
||||
const ratio = width / height;
|
||||
let wRatio = wsBoxWidth / wsBoxHeight;
|
||||
let scale = ratio / wRatio;
|
||||
|
||||
if (scale > 1) {
|
||||
wsBoxHeight /= scale;
|
||||
wsBoxWidth = wsBoxHeight * ratio;
|
||||
} else {
|
||||
wsBoxWidth *= scale;
|
||||
wsBoxHeight = wsBoxWidth / ratio;
|
||||
}
|
||||
|
||||
// height decides the actual size, ratio is given by the workArea
|
||||
wsBoxHeight = Math.round(wsBoxHeight * opt.SEC_WS_PREVIEW_SCALE);
|
||||
wsBoxWidth = Math.round(wsBoxWidth * opt.SEC_WS_PREVIEW_SCALE);
|
||||
|
||||
let offset = Math.round(width - wsTmbWidth - wsBoxWidth - spacing) / 2;
|
||||
|
||||
const wsbX = startX + (opt.SEC_WS_TMB_LEFT
|
||||
? wsTmbWidth + spacing + offset
|
||||
: offset);
|
||||
|
||||
const wsbY = Math.round(startY + (height - wsBoxHeight) / 2);
|
||||
|
||||
workspaceBox.set_origin(wsbX, wsbY);
|
||||
workspaceBox.set_size(wsBoxWidth, wsBoxHeight);
|
||||
}
|
||||
|
||||
return workspaceBox;
|
||||
},
|
||||
|
||||
_getWorkAreaBox(box) {
|
||||
if (!opt.SEC_WS_PREVIEW_SHIFT || !Main.panel.visible)
|
||||
return box;
|
||||
|
||||
const workArea = box.copy();
|
||||
const panelHeight = Main.panel.height;
|
||||
workArea.y1 += opt.PANEL_POSITION_TOP ? panelHeight : 0;
|
||||
workArea.y2 -= opt.PANEL_POSITION_BOTTOM ? panelHeight : 0;
|
||||
|
||||
return workArea;
|
||||
},
|
||||
|
||||
vfunc_allocate(box) {
|
||||
this.set_allocation(box);
|
||||
|
||||
const themeNode = this.get_theme_node();
|
||||
const contentBox = themeNode.get_content_box(box);
|
||||
|
||||
const workArea = this._getWorkAreaBox(contentBox);
|
||||
|
||||
let [width, height] = workArea.get_size();
|
||||
let [startX, startY] = workArea.get_origin();
|
||||
|
||||
const spacing = opt.SPACING;
|
||||
|
||||
let wsTmbWidth = 0;
|
||||
let wsTmbHeight = 0;
|
||||
this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN;
|
||||
if (this._thumbnails.visible) {
|
||||
wsTmbWidth = Math.round(width * opt.SEC_MAX_THUMBNAIL_SCALE);
|
||||
|
||||
let totalTmbSpacing;
|
||||
[totalTmbSpacing, wsTmbHeight] = this._thumbnails.get_preferred_height(wsTmbWidth);
|
||||
wsTmbHeight += totalTmbSpacing;
|
||||
|
||||
const thumbnailsHeightMax = height - spacing;
|
||||
|
||||
if (wsTmbHeight > thumbnailsHeightMax) {
|
||||
wsTmbHeight = thumbnailsHeightMax;
|
||||
wsTmbWidth = Math.round(this._thumbnails.get_preferred_width(wsTmbHeight)[1]);
|
||||
}
|
||||
|
||||
let wsTmbX = opt.SEC_WS_TMB_LEFT
|
||||
? startX + spacing
|
||||
: startX + width - wsTmbWidth - spacing;
|
||||
|
||||
let offset = (height - wsTmbHeight) / 2;
|
||||
const wsTmbY = startY + Math.round(offset - opt.SEC_WS_TMB_POSITION_ADJUSTMENT * (offset - spacing));
|
||||
|
||||
const childBox = new Clutter.ActorBox();
|
||||
childBox.set_origin(wsTmbX, wsTmbY);
|
||||
childBox.set_size(wsTmbWidth, wsTmbHeight);
|
||||
this._thumbnails.allocate(childBox);
|
||||
}
|
||||
|
||||
const {
|
||||
currentState, initialState, finalState, transitioning, progress,
|
||||
} = this._overviewAdjustment.getStateTransitionParams();
|
||||
|
||||
let workspacesBox;
|
||||
const workspaceParams = [contentBox, workArea, wsTmbWidth, spacing];
|
||||
if (!transitioning) {
|
||||
workspacesBox =
|
||||
this._getWorkspacesBoxForState(currentState, ...workspaceParams);
|
||||
} else {
|
||||
const initialBox =
|
||||
this._getWorkspacesBoxForState(initialState, ...workspaceParams);
|
||||
const finalBox =
|
||||
this._getWorkspacesBoxForState(finalState, ...workspaceParams);
|
||||
workspacesBox = initialBox.interpolate(finalBox, progress);
|
||||
}
|
||||
this._workspacesView.allocate(workspacesBox);
|
||||
},
|
||||
|
||||
_updateThumbnailVisibility() {
|
||||
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._updateThumbnailParams();
|
||||
this._thumbnails.ease_property('expand-fraction', visible ? 1 : 0, {
|
||||
duration: Overview.ANIMATION_TIME,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
onComplete: () => {
|
||||
this._thumbnails.visible = visible;
|
||||
this._thumbnails._indicator.visible = visible;
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
_updateThumbnailParams() {
|
||||
if (opt.SEC_WS_TMB_HIDDEN)
|
||||
return;
|
||||
|
||||
// workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails
|
||||
// the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid
|
||||
if (!this._thumbnails._thumbnails.length)
|
||||
this._thumbnails._createThumbnails();
|
||||
|
||||
const { initialState, finalState, progress } =
|
||||
this._overviewAdjustment.getStateTransitionParams();
|
||||
|
||||
const initialParams = this._getThumbnailParamsForState(initialState);
|
||||
const finalParams = this._getThumbnailParamsForState(finalState);
|
||||
|
||||
/* const opacity =
|
||||
Util.lerp(initialParams.opacity, finalParams.opacity, progress);
|
||||
const scale =
|
||||
Util.lerp(initialParams.scale, finalParams.scale, progress);*/
|
||||
|
||||
// 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);
|
||||
const translationX = !Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2)
|
||||
? Util.lerp(initialParams.translationX, finalParams.translationX, progress)
|
||||
: 0;
|
||||
|
||||
this._thumbnails.set({
|
||||
opacity: 255,
|
||||
// scale_x: scale,
|
||||
// scale_y: scale,
|
||||
translation_x: translationX,
|
||||
});
|
||||
},
|
||||
|
||||
_updateWorkspacesView() {
|
||||
if (this._workspacesView)
|
||||
this._workspacesView.destroy();
|
||||
|
||||
if (this._settings.get_boolean('workspaces-only-on-primary')) {
|
||||
opt.SEC_WS_TMB_HIDDEN = true;
|
||||
this._workspacesView = new WorkspacesView.ExtraWorkspaceView(
|
||||
this._monitorIndex,
|
||||
this._overviewAdjustment);
|
||||
} else {
|
||||
opt.SEC_WS_TMB_HIDDEN = !opt.SHOW_SEC_WS_TMB;
|
||||
this._workspacesView = new WorkspacesView.WorkspacesView(
|
||||
this._monitorIndex,
|
||||
this._controls,
|
||||
this._scrollAdjustment,
|
||||
// Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
|
||||
// this._fitModeAdjustment,
|
||||
new St.Adjustment({
|
||||
actor: this,
|
||||
value: 0, // FitMode.SINGLE,
|
||||
lower: 0, // FitMode.SINGLE,
|
||||
upper: 0, // FitMode.SINGLE,
|
||||
}),
|
||||
// secondaryOverviewAdjustment);
|
||||
this._overviewAdjustment);
|
||||
}
|
||||
this.add_child(this._workspacesView);
|
||||
this._thumbnails.opacity = 0;
|
||||
},
|
||||
};
|
||||
|
||||
const SecondaryMonitorDisplayHorizontal = {
|
||||
_getThumbnailParamsForState(state) {
|
||||
const spacing = opt.SPACING;
|
||||
let opacity, scale, translationY;
|
||||
switch (state) {
|
||||
case ControlsState.HIDDEN:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationY = 0;
|
||||
if (!Main.layoutManager._startingUp && (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2))
|
||||
translationY = (this._thumbnails.height + spacing) * (opt.SEC_WS_TMB_TOP ? -1 : 1);
|
||||
|
||||
break;
|
||||
case ControlsState.WINDOW_PICKER:
|
||||
case ControlsState.APP_GRID:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationY = 0;
|
||||
break;
|
||||
default:
|
||||
opacity = 255;
|
||||
scale = 1;
|
||||
translationY = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return { opacity, scale, translationY };
|
||||
},
|
||||
|
||||
_getWorkspacesBoxForState(state, box, workArea, wsTmbHeight, spacing) {
|
||||
let workspaceBox = box.copy();
|
||||
|
||||
if (
|
||||
(state === ControlsState.WINDOW_PICKER || state === ControlsState.APP_GRID) &&
|
||||
!(opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)
|
||||
) {
|
||||
workspaceBox = workArea.copy();
|
||||
const [startX, startY] = workspaceBox.get_origin();
|
||||
let [width, height] = workspaceBox.get_size();
|
||||
|
||||
let wsBoxWidth = width - 2 * spacing;
|
||||
let wsBoxHeight = height - (wsTmbHeight ? wsTmbHeight + spacing : 0) - 2 * spacing;
|
||||
|
||||
const ratio = width / height;
|
||||
let wRatio = wsBoxWidth / wsBoxHeight;
|
||||
let scale = ratio / wRatio;
|
||||
|
||||
if (scale > 1) {
|
||||
wsBoxHeight /= scale;
|
||||
wsBoxWidth = wsBoxHeight * ratio;
|
||||
} else {
|
||||
wsBoxWidth *= scale;
|
||||
wsBoxHeight = wsBoxWidth / ratio;
|
||||
}
|
||||
|
||||
// height decides the actual size, ratio is given by the workArea
|
||||
wsBoxHeight = Math.round(wsBoxHeight * opt.SEC_WS_PREVIEW_SCALE);
|
||||
wsBoxWidth = Math.round(wsBoxWidth * opt.SEC_WS_PREVIEW_SCALE);
|
||||
|
||||
let offset = Math.round(height - wsTmbHeight - wsBoxHeight - spacing) / 2;
|
||||
|
||||
const wsbX = Math.round(startX + (width - wsBoxWidth) / 2);
|
||||
|
||||
const wsbY = startY + (opt.SEC_WS_TMB_TOP
|
||||
? wsTmbHeight + spacing + offset
|
||||
: offset);
|
||||
|
||||
workspaceBox.set_origin(wsbX, wsbY);
|
||||
workspaceBox.set_size(wsBoxWidth, wsBoxHeight);
|
||||
}
|
||||
|
||||
return workspaceBox;
|
||||
},
|
||||
|
||||
_getWorkAreaBox: SecondaryMonitorDisplayVertical._getWorkAreaBox,
|
||||
|
||||
vfunc_allocate(box) {
|
||||
this.set_allocation(box);
|
||||
|
||||
const themeNode = this.get_theme_node();
|
||||
const contentBox = themeNode.get_content_box(box);
|
||||
|
||||
const workArea = this._getWorkAreaBox(contentBox);
|
||||
|
||||
let [width, height] = workArea.get_size();
|
||||
let [startX, startY] = workArea.get_origin();
|
||||
|
||||
const spacing = opt.SPACING;
|
||||
|
||||
let wsTmbWidth = 0;
|
||||
let wsTmbHeight = 0;
|
||||
this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN;
|
||||
if (this._thumbnails.visible) {
|
||||
wsTmbHeight = Math.round(height * opt.SEC_MAX_THUMBNAIL_SCALE);
|
||||
|
||||
let totalTmbSpacing;
|
||||
[totalTmbSpacing, wsTmbWidth] = this._thumbnails.get_preferred_width(wsTmbHeight);
|
||||
wsTmbWidth += totalTmbSpacing;
|
||||
|
||||
const thumbnailsWidthMax = width - 2 * spacing;
|
||||
|
||||
if (wsTmbWidth > thumbnailsWidthMax) {
|
||||
wsTmbWidth = thumbnailsWidthMax;
|
||||
wsTmbHeight = Math.round(this._thumbnails.get_preferred_height(wsTmbWidth)[1]);
|
||||
}
|
||||
|
||||
let wsTmbY = opt.SEC_WS_TMB_TOP
|
||||
? startY + spacing
|
||||
: startY + height - wsTmbHeight - spacing;
|
||||
|
||||
let offset = (width - wsTmbWidth) / 2;
|
||||
const wsTmbX = startX + Math.round(offset - opt.SEC_WS_TMB_POSITION_ADJUSTMENT * (offset - spacing));
|
||||
|
||||
const childBox = new Clutter.ActorBox();
|
||||
childBox.set_origin(wsTmbX, wsTmbY);
|
||||
childBox.set_size(wsTmbWidth, wsTmbHeight);
|
||||
this._thumbnails.allocate(childBox);
|
||||
}
|
||||
|
||||
const {
|
||||
currentState, initialState, finalState, transitioning, progress,
|
||||
} = this._overviewAdjustment.getStateTransitionParams();
|
||||
|
||||
let workspacesBox;
|
||||
const workspaceParams = [contentBox, workArea, wsTmbHeight, spacing];
|
||||
if (!transitioning) {
|
||||
workspacesBox =
|
||||
this._getWorkspacesBoxForState(currentState, ...workspaceParams);
|
||||
} else {
|
||||
const initialBox =
|
||||
this._getWorkspacesBoxForState(initialState, ...workspaceParams);
|
||||
const finalBox =
|
||||
this._getWorkspacesBoxForState(finalState, ...workspaceParams);
|
||||
workspacesBox = initialBox.interpolate(finalBox, progress);
|
||||
}
|
||||
this._workspacesView.allocate(workspacesBox);
|
||||
},
|
||||
|
||||
_updateThumbnailVisibility: SecondaryMonitorDisplayVertical._updateThumbnailVisibility,
|
||||
|
||||
_updateThumbnailParams() {
|
||||
if (opt.SEC_WS_TMB_HIDDEN)
|
||||
return;
|
||||
|
||||
// workaround for upstream bug - secondary thumbnails boxes don't catch 'showing' signal on the shell startup and don't populate the box with thumbnails
|
||||
// the tmbBox contents is also destroyed when overview state adjustment gets above 1 when swiping gesture from window picker to app grid
|
||||
if (!this._thumbnails._thumbnails.length)
|
||||
this._thumbnails._createThumbnails();
|
||||
|
||||
const { initialState, finalState, progress } =
|
||||
this._overviewAdjustment.getStateTransitionParams();
|
||||
|
||||
const initialParams = this._getThumbnailParamsForState(initialState);
|
||||
const finalParams = this._getThumbnailParamsForState(finalState);
|
||||
|
||||
/* const opacity =
|
||||
Util.lerp(initialParams.opacity, finalParams.opacity, progress);
|
||||
const scale =
|
||||
Util.lerp(initialParams.scale, finalParams.scale, progress);*/
|
||||
|
||||
// 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);
|
||||
const translationY = !Main.layoutManager._startingUp && ((!opt.SHOW_WS_PREVIEW_BG && !opt.OVERVIEW_MODE2) || animateOverviewMode2)
|
||||
? Util.lerp(initialParams.translationY, finalParams.translationY, progress)
|
||||
: 0;
|
||||
|
||||
this._thumbnails.set({
|
||||
opacity: 255,
|
||||
// scale_x: scale,
|
||||
// scale_y: scale,
|
||||
translation_y: translationY,
|
||||
});
|
||||
},
|
||||
|
||||
_updateWorkspacesView() {
|
||||
if (this._workspacesView)
|
||||
this._workspacesView.destroy();
|
||||
|
||||
if (this._settings.get_boolean('workspaces-only-on-primary')) {
|
||||
opt.SEC_WS_TMB_HIDDEN = true;
|
||||
this._workspacesView = new WorkspacesView.ExtraWorkspaceView(
|
||||
this._monitorIndex,
|
||||
this._overviewAdjustment);
|
||||
} else {
|
||||
opt.SEC_WS_TMB_HIDDEN = !opt.SHOW_SEC_WS_TMB;
|
||||
this._workspacesView = new WorkspacesView.WorkspacesView(
|
||||
this._monitorIndex,
|
||||
this._controls,
|
||||
this._scrollAdjustment,
|
||||
// Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
|
||||
// this._fitModeAdjustment,
|
||||
new St.Adjustment({
|
||||
actor: this,
|
||||
value: 0, // FitMode.SINGLE,
|
||||
lower: 0, // FitMode.SINGLE,
|
||||
upper: 0, // FitMode.SINGLE,
|
||||
}),
|
||||
// secondaryOverviewAdjustment);
|
||||
this._overviewAdjustment);
|
||||
}
|
||||
this.add_child(this._workspacesView);
|
||||
this._thumbnails.opacity = 0;
|
||||
},
|
||||
};
|
||||
|
||||
const ExtraWorkspaceViewCommon = {
|
||||
_updateWorkspaceMode() {
|
||||
const overviewState = this._overviewAdjustment.value;
|
||||
|
||||
const progress = Math.clamp(overviewState,
|
||||
ControlsState.HIDDEN,
|
||||
opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE ? ControlsState.HIDDEN : ControlsState.WINDOW_PICKER);
|
||||
|
||||
this._workspace.stateAdjustment.value = progress;
|
||||
|
||||
// force ws preview bg corner radiuses where GS doesn't do it
|
||||
if (opt.SHOW_WS_PREVIEW_BG && opt.OVERVIEW_MODE === 1)
|
||||
this._workspace._background._updateBorderRadius(Math.min(1, this._workspace._overviewAdjustment.value));
|
||||
|
||||
|
||||
// hide workspace background
|
||||
if (!opt.SHOW_WS_PREVIEW_BG && this._workspace._background.opacity)
|
||||
this._workspace._background.opacity = 0;
|
||||
},
|
||||
|
||||
exposeWindows() {
|
||||
const adjustment = this._workspace._background._stateAdjustment;
|
||||
opt.WORKSPACE_MODE = 1;
|
||||
if (adjustment.value === 0) {
|
||||
adjustment.ease(1, {
|
||||
duration: 200,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const WorkspacesDisplayCommon = {
|
||||
_updateWorkspacesViews() {
|
||||
for (let i = 0; i < this._workspacesViews.length; i++)
|
||||
this._workspacesViews[i].destroy();
|
||||
|
||||
this._primaryIndex = Main.layoutManager.primaryIndex;
|
||||
this._workspacesViews = [];
|
||||
let monitors = Main.layoutManager.monitors;
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let view;
|
||||
if (i === this._primaryIndex) {
|
||||
view = new WorkspacesView.WorkspacesView(i,
|
||||
this._controls,
|
||||
this._scrollAdjustment,
|
||||
this._fitModeAdjustment,
|
||||
this._overviewAdjustment);
|
||||
|
||||
view.visible = this._primaryVisible;
|
||||
this.bind_property('opacity', view, 'opacity', GObject.BindingFlags.SYNC_CREATE);
|
||||
this.add_child(view);
|
||||
} else {
|
||||
view = new WorkspacesView.SecondaryMonitorDisplay(i,
|
||||
this._controls,
|
||||
this._scrollAdjustment,
|
||||
// Secondary monitors don't need FitMode.ALL since there is workspace switcher always visible
|
||||
// this._fitModeAdjustment,
|
||||
new St.Adjustment({
|
||||
actor: this,
|
||||
value: 0, // FitMode.SINGLE,
|
||||
lower: 0, // FitMode.SINGLE,
|
||||
upper: 0, // FitMode.SINGLE,
|
||||
}),
|
||||
this._overviewAdjustment);
|
||||
Main.layoutManager.overviewGroup.add_child(view);
|
||||
|
||||
if (opt.CLICK_EMPTY_CLOSE) {
|
||||
// Allow users to close the overview by clicking on an empty space on the secondary monitor
|
||||
// The primary monitor overview is handled in the overviewControls
|
||||
const clickAction = new Clutter.ClickAction();
|
||||
clickAction.connect('clicked', () => {
|
||||
Main.overview.hide();
|
||||
});
|
||||
view.reactive = true;
|
||||
view.add_action(clickAction);
|
||||
}
|
||||
}
|
||||
|
||||
this._workspacesViews.push(view);
|
||||
}
|
||||
},
|
||||
|
||||
_onScrollEvent(actor, event) {
|
||||
if (this._swipeTracker.canHandleScrollEvent(event))
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
if (!this.mapped)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
if (this._workspacesOnlyOnPrimary &&
|
||||
this._getMonitorIndexForEvent(event) !== this._primaryIndex)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
if (opt.PANEL_MODE === 1) {
|
||||
const panelBox = Main.layoutManager.panelBox;
|
||||
const [, y] = global.get_pointer();
|
||||
if (y > panelBox.allocation.y1 && y < panelBox.allocation.y2)
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (direction === Clutter.ScrollDirection.UP)
|
||||
direction = -1;
|
||||
|
||||
else if (direction === Clutter.ScrollDirection.DOWN)
|
||||
direction = 1;
|
||||
else
|
||||
direction = 0;
|
||||
|
||||
if (direction) {
|
||||
Me.Util.reorderWorkspace(direction);
|
||||
// make all workspaces on primary monitor visible for case the new position is hidden
|
||||
const primaryMonitorIndex = global.display.get_primary_monitor();
|
||||
Main.overview._overview._controls._workspacesDisplay._workspacesViews[primaryMonitorIndex]._workspaces.forEach(w => {
|
||||
w.visible = true;
|
||||
});
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
}
|
||||
|
||||
return Main.wm.handleWorkspaceScroll(event);
|
||||
},
|
||||
|
||||
_onKeyPressEvent(actor, event) {
|
||||
const symbol = event.get_key_symbol();
|
||||
/* const { ControlsState } = OverviewControls;
|
||||
if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER && symbol !== Clutter.KEY_space)
|
||||
return Clutter.EVENT_PROPAGATE;*/
|
||||
|
||||
/* if (!this.reactive)
|
||||
return Clutter.EVENT_PROPAGATE; */
|
||||
const { workspaceManager } = global;
|
||||
const vertical = workspaceManager.layout_rows === -1;
|
||||
const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
|
||||
const state = this._overviewAdjustment.value;
|
||||
|
||||
let which;
|
||||
switch (symbol) {
|
||||
case Clutter.KEY_Return:
|
||||
case Clutter.KEY_KP_Enter:
|
||||
if (Me.Util.isCtrlPressed()) {
|
||||
Main.ctrlAltTabManager._items.forEach(i => {
|
||||
if (i.sortGroup === 1 && i.name === 'Dash')
|
||||
Main.ctrlAltTabManager.focusGroup(i);
|
||||
});
|
||||
}
|
||||
return Clutter.EVENT_STOP;
|
||||
case Clutter.KEY_Page_Up:
|
||||
if (vertical)
|
||||
which = Meta.MotionDirection.UP;
|
||||
else if (rtl)
|
||||
which = Meta.MotionDirection.RIGHT;
|
||||
else
|
||||
which = Meta.MotionDirection.LEFT;
|
||||
break;
|
||||
case Clutter.KEY_Page_Down:
|
||||
if (vertical)
|
||||
which = Meta.MotionDirection.DOWN;
|
||||
else if (rtl)
|
||||
which = Meta.MotionDirection.LEFT;
|
||||
else
|
||||
which = Meta.MotionDirection.RIGHT;
|
||||
break;
|
||||
case Clutter.KEY_Home:
|
||||
which = 0;
|
||||
break;
|
||||
case Clutter.KEY_End:
|
||||
which = workspaceManager.n_workspaces - 1;
|
||||
break;
|
||||
case Clutter.KEY_space:
|
||||
if (Me.Util.isCtrlPressed() && Me.Util.isShiftPressed()) {
|
||||
Me.Util.openPreferences();
|
||||
} else if (Me.Util.isAltPressed()) {
|
||||
Main.ctrlAltTabManager._items.forEach(i => {
|
||||
if (i.sortGroup === 1 && i.name === 'Dash')
|
||||
Main.ctrlAltTabManager.focusGroup(i);
|
||||
});
|
||||
} else if (Me.Util.getEnabledExtensions('extensions-search-provider').length && Me.Util.isCtrlPressed()) {
|
||||
Me.Util.activateSearchProvider(Me.ESP_PREFIX);
|
||||
} else if (Me.Util.getEnabledExtensions('windows-search-provider').length) {
|
||||
Me.Util.activateSearchProvider(Me.WSP_PREFIX);
|
||||
}
|
||||
|
||||
return Clutter.EVENT_STOP;
|
||||
case Clutter.KEY_Down:
|
||||
case Clutter.KEY_Left:
|
||||
case Clutter.KEY_Right:
|
||||
case Clutter.KEY_Up:
|
||||
case Clutter.KEY_Tab:
|
||||
if (Main.overview.searchController.searchActive) {
|
||||
Main.overview.searchEntry.grab_key_focus();
|
||||
} else if (!opt.WORKSPACE_MODE && state <= 1) {
|
||||
if (opt.OVERVIEW_MODE2)
|
||||
Main.overview._overview.controls._updateSearchStyle(true);
|
||||
// spread windows in OVERVIEW_MODE
|
||||
if (state < 1)
|
||||
opt.WORKSPACE_MODE = 1;
|
||||
else if (opt.OVERVIEW_MODE2)
|
||||
Me.Util.exposeWindowsWithOverviewTransition();
|
||||
else
|
||||
Me.Util.exposeWindows();
|
||||
} else {
|
||||
if (state === 2)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
Me.Util.activateKeyboardForWorkspaceView();
|
||||
}
|
||||
|
||||
return Clutter.EVENT_STOP;
|
||||
default:
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
}
|
||||
|
||||
if (state === 2)
|
||||
return Clutter.EVENT_PROPAGATE;
|
||||
|
||||
let ws;
|
||||
if (which < 0)
|
||||
// Negative workspace numbers are directions
|
||||
ws = workspaceManager.get_active_workspace().get_neighbor(which);
|
||||
else
|
||||
// Otherwise it is a workspace index
|
||||
ws = workspaceManager.get_workspace_by_index(which);
|
||||
|
||||
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)
|
||||
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;
|
||||
});
|
||||
return Clutter.EVENT_STOP;
|
||||
}
|
||||
|
||||
if (ws)
|
||||
Main.wm.actionMoveWorkspace(ws);
|
||||
|
||||
return Clutter.EVENT_STOP;
|
||||
},
|
||||
};
|
||||
|
||||
// same copy of this function should be available in OverviewControls and WorkspacesView
|
||||
function _getFitModeForState(state) {
|
||||
switch (state) {
|
||||
case ControlsState.HIDDEN:
|
||||
case ControlsState.WINDOW_PICKER:
|
||||
return FitMode.SINGLE;
|
||||
case ControlsState.APP_GRID:
|
||||
if (opt.WS_ANIMATION && opt.SHOW_WS_TMB)
|
||||
return FitMode.ALL;
|
||||
else
|
||||
return FitMode.SINGLE;
|
||||
default:
|
||||
return FitMode.SINGLE;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue