1
0
Fork 0

Adding upstream version 20230618.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 23:10:52 +01:00
parent 17f856de92
commit f8854b5650
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
67 changed files with 11203 additions and 4163 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
/**
* V-Shell (Vertical Workspaces)
* appFavorites.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Shell } = imports.gi;
const AppFavorites = imports.ui.appFavorites;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let opt;
let _overrides;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('appFavoritesModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
// if notifications are enabled no override is needed
if (reset || opt.SHOW_FAV_NOTIFICATION) {
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
// AppFavorites.AppFavorites is const, first access returns undefined
const dummy = AppFavorites.AppFavorites;
_overrides.addOverride('AppFavorites', AppFavorites.AppFavorites.prototype, AppFavoritesCommon);
}
const AppFavoritesCommon = {
addFavoriteAtPos(appId, pos) {
this._addFavorite(appId, pos);
},
removeFavorite(appId) {
this._removeFavorite(appId);
},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,314 @@
/**
* V-Shell (Vertical Workspaces)
* iconGrid.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { GLib, St, Meta } = imports.gi;
const IconGrid = imports.ui.iconGrid;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const shellVersion = _Util.shellVersion;
// added sizes for better scaling
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,
48: 48,
TINY: 32,
};
const PAGE_WIDTH_CORRECTION = 100;
let opt;
let _overrides;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('appDisplayModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset) {
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
if (shellVersion < 43 && IconGridCommon._findBestModeForSize) {
IconGridCommon['findBestModeForSize'] = IconGridCommon._findBestModeForSize;
IconGridCommon['_findBestModeForSize'] = undefined;
}
_overrides.addOverride('IconGrid', IconGrid.IconGrid.prototype, IconGridCommon);
_overrides.addOverride('IconGridLayout', IconGrid.IconGridLayout.prototype, IconGridLayoutCommon);
}
// workaround - silence page -2 error on gnome 43 while cleaning app grid
const IconGridCommon = {
getItemsAtPage(page) {
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);
},
_findBestModeForSize(width, height) {
// this function is for main grid only, folder grid calculation is in appDisplay.AppFolderDialog class
if (this._currentMode > -1 || this.layoutManager._isFolder)
return;
const { pagePadding } = this.layout_manager;
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
const iconPadding = 53 * scaleFactor;
// provided width is usually about 100px wider in horizontal orientation with prev/next page indicators
const pageIndicatorCompensation = opt.ORIENTATION ? 0 : PAGE_WIDTH_CORRECTION;
width -= pagePadding.left + pagePadding.right + pageIndicatorCompensation;
width *= opt.APP_GRID_PAGE_WIDTH_SCALE;
height -= pagePadding.top + pagePadding.bottom;
// store grid max dimensions for icon size algorithm
this.layoutManager._gridWidth = width;
this.layoutManager._gridHeight = height;
const spacing = opt.APP_GRID_SPACING;
const iconSize = (opt.APP_GRID_ICON_SIZE > 0 ? opt.APP_GRID_ICON_SIZE : opt.APP_GRID_ICON_SIZE_DEFAULT) * scaleFactor;
// if this._gridModes.length === 1, custom grid should be used
// if (iconSize > 0 && this._gridModes.length > 1) {
let columns = opt.APP_GRID_COLUMNS;
let rows = opt.APP_GRID_ROWS;
// 0 means adaptive size
let unusedSpaceH = -1;
let unusedSpaceV = -1;
if (!columns) {
columns = Math.floor(width / (iconSize + iconPadding)) + 1;
while (unusedSpaceH < 0) {
columns -= 1;
unusedSpaceH = width - columns * (iconSize + iconPadding) - (columns - 1) * spacing;
}
}
if (!rows) {
rows = Math.floor(height / (iconSize + iconPadding)) + 1;
while (unusedSpaceV < 0) {
rows -= 1;
unusedSpaceV = height - rows * (iconSize + iconPadding) - (rows - 1) * spacing;
}
}
this._gridModes = [{ columns, rows }];
// }
this._setGridMode(0);
},
};
const IconGridLayoutCommon = {
_findBestIconSize() {
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
const nColumns = this.columnsPerPage;
const nRows = this.rowsPerPage;
const columnSpacingPerPage = opt.APP_GRID_SPACING * (nColumns - 1);
const rowSpacingPerPage = opt.APP_GRID_SPACING * (nRows - 1);
const iconPadding = 53 * scaleFactor;
const paddingH = this._isFolder ? this.pagePadding.left + this.pagePadding.right : 0;
const paddingV = this._isFolder ? this.pagePadding.top + this.pagePadding.bottom : 0;
const width = this._gridWidth ? this._gridWidth : this._pageWidth;
const height = this._gridHeight ? this._gridHeight : this._pageHeight;
if (!width || !height)
return opt.APP_GRID_ICON_SIZE_DEFAULT;
const [firstItem] = this._container;
if (this.fixedIconSize !== -1)
return this.fixedIconSize;
/* if (opt.APP_GRID_ADAPTIVE && !this._isFolder)
return opt.APP_GRID_ICON_SIZE_DEFAULT;*/
let iconSizes = Object.values(IconSize).sort((a, b) => b - a);
// limit max icon size for folders, the whole range is for the main grid with active folders
if (this._isFolder)
iconSizes = iconSizes.slice(iconSizes.indexOf(IconSize.LARGE), -1);
let sizeInvalid = false;
for (const size of iconSizes) {
let usedWidth, usedHeight;
if (firstItem) {
firstItem.icon.setIconSize(size);
const [firstItemWidth, firstItemHeight] =
firstItem.get_preferred_size();
const itemSize = Math.max(firstItemWidth, firstItemHeight);
if (itemSize < size)
sizeInvalid = true;
usedWidth = itemSize * nColumns;
usedHeight = itemSize * nRows;
}
if (!firstItem || sizeInvalid) {
usedWidth = (size + iconPadding) * nColumns;
usedHeight = (size + iconPadding) * nRows;
}
const emptyHSpace =
width - usedWidth - columnSpacingPerPage - paddingH;
// this.pagePadding.left - this.pagePadding.right;
const emptyVSpace =
height - usedHeight - rowSpacingPerPage - paddingV;
// this.pagePadding.top - this.pagePadding.bottom;
if (emptyHSpace >= 0 && emptyVSpace >= 0) {
return size;
}
}
return IconSize.TINY;
},
removeItem(item) {
if (!this._items.has(item)) {
log(`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)) {
log(`iconGrid: Item ${item} already added to IconGridLayout`);
return;
// throw new Error(`Item ${item} already added to IconGridLayout`);
}
if (page > this._pages.length) {
log(`iconGrid: Cannot add ${item} to page ${page}`);
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;
this._container.add_child(item);
this._addItemToPage(item, page, index);
},
moveItem(item, newPage, newPosition) {
if (!this._items.has(item)) {
log(`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);
},
_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;
},
};

View file

@ -0,0 +1,380 @@
/**
* V-Shell (Vertical Workspaces)
* layout.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Meta, GLib, Shell, Clutter, GObject } = imports.gi;
const Main = imports.ui.main;
const Layout = imports.ui.layout;
const Ripples = imports.ui.ripples;
const DND = imports.ui.dnd;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let _overrides;
let _timeouts;
let opt;
let _firstRun = true;
let _originalUpdateHotCorners;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('layoutModule', true);
const conflict = _Util.getEnabledExtensions('custom-hot-corners').length ||
_Util.getEnabledExtensions('dash-to-panel').length;
reset = reset || !moduleEnabled;
// don't even touch this module if disabled or in conflict
if (_firstRun && (reset || conflict))
return;
_firstRun = false;
if (!_originalUpdateHotCorners)
_originalUpdateHotCorners = Layout.LayoutManager.prototype._updateHotCorners;
if (_overrides)
_overrides.removeAll();
if (_timeouts) {
Object.values(_timeouts).forEach(t => {
if (t)
GLib.source_remove(t);
});
}
if (reset) {
_overrides = null;
opt = null;
_timeouts = null;
Main.layoutManager._updateHotCorners = _originalUpdateHotCorners;
Main.layoutManager._updateHotCorners();
return;
}
_timeouts = {};
_overrides = new _Util.Overrides();
_overrides.addOverride('LayoutManager', Layout.LayoutManager.prototype, LayoutManagerCommon);
Main.layoutManager._updateHotCorners = LayoutManagerCommon._updateHotCorners.bind(Main.layoutManager);
Main.layoutManager._updatePanelBarrier();
Main.layoutManager._updateHotCorners();
}
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)
return;
if (this.panelBox.height) {
let primary = this.primaryMonitor;
if ([0, 1, 3].includes(opt.HOT_CORNER_POSITION)) {
this._rightPanelBarrier = new Meta.Barrier({
display: global.display,
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,
});
}
if ([2, 4].includes(opt.HOT_CORNER_POSITION)) {
this._leftPanelBarrier = new Meta.Barrier({
display: global.display,
x1: primary.x, y1: this.panelBox.allocation.y1,
x2: primary.x, y2: this.panelBox.allocation.y2,
directions: Meta.BarrierDirection.POSITIVE_X,
});
}
}
},
_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;
// 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 HotCorner(this, monitor, cornerX, cornerY);
corner.setBarrierSize(size);
this.hotCorners.push(corner);
} else {
this.hotCorners.push(null);
}
}
this.emit('hot-corners-changed');
},
};
var HotCorner = GObject.registerClass(
class HotCorner extends Layout.HotCorner {
_init(layoutManager, monitor, x, y) {
super._init(layoutManager, monitor, x, y);
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) {
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_EDGE && opt.DASH_VERTICAL && monitor.index === primaryMonitor;
const extendH = opt && opt.HOT_CORNER_EDGE && !opt.DASH_VERTICAL && monitor.index === primaryMonitor;
if (opt.HOT_CORNER_POSITION <= 1) {
this._verticalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y + (extendV ? monitor.height : size),
directions: Meta.BarrierDirection.POSITIVE_X,
});
this._horizontalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x + (extendH ? monitor.width : size), y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.POSITIVE_Y,
});
} else if (opt.HOT_CORNER_POSITION === 2) {
this._verticalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y + (extendV ? monitor.height : size),
directions: Meta.BarrierDirection.NEGATIVE_X,
});
this._horizontalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x - size, x2: this._x, y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.POSITIVE_Y,
});
} else if (opt.HOT_CORNER_POSITION === 3) {
this._verticalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y - size,
directions: Meta.BarrierDirection.POSITIVE_X,
});
this._horizontalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x + (extendH ? monitor.width : size), y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.NEGATIVE_Y,
});
} else if (opt.HOT_CORNER_POSITION === 4) {
this._verticalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x, y1: this._y, y2: this._y - size,
directions: Meta.BarrierDirection.NEGATIVE_X,
});
this._horizontalBarrier = new Meta.Barrier({
display: global.display,
x1: this._x, x2: this._x - size, y1: this._y, y2: this._y,
directions: Meta.BarrierDirection.NEGATIVE_Y,
});
}
this._pressureBarrier.addBarrier(this._verticalBarrier);
this._pressureBarrier.addBarrier(this._horizontalBarrier);
}
}
_toggleOverview() {
if (!opt.HOT_CORNER_ACTION || (!opt.HOT_CORNER_FULLSCREEN && this._monitor.inFullscreen && !Main.overview.visible))
return;
if (Main.overview.shouldToggleByCornerOrButton()) {
if ((opt.HOT_CORNER_ACTION === 1 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 2 && _Util.isCtrlPressed()))
this._toggleWindowPicker(true);
else if ((opt.HOT_CORNER_ACTION === 2 && !_Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 1 && _Util.isCtrlPressed()) || (opt.HOT_CORNER_ACTION === 3 && _Util.isCtrlPressed()))
this._toggleApplications(true);
else if (opt.HOT_CORNER_ACTION === 3 && !_Util.isCtrlPressed())
this._toggleWindowSearchProvider();
if (opt.HOT_CORNER_RIPPLES && Main.overview.animationInProgress)
this._ripples.playAnimation(this._x, this._y);
}
}
_toggleWindowPicker(leaveOverview = 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();
_timeouts.releaseKeyboardTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}
);
} else {
Main.overview.show();
}
}
}
_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._overview._controls._searchController._searchActive) {
this._toggleWindowPicker();
const prefix = 'wq// ';
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();
}
}
});

View file

@ -0,0 +1,67 @@
/**
* V-Shell (Vertical Workspaces)
* messageTray.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Clutter } = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Main = imports.ui.main;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('messageTrayModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (reset) {
opt = null;
setNotificationPosition(1);
return;
}
setNotificationPosition(opt.NOTIFICATION_POSITION);
}
function setNotificationPosition(position) {
switch (position) {
case 0:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START;
break;
case 1:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START;
break;
case 2:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.START;
break;
case 3:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.START;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END;
break;
case 4:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.CENTER;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END;
break;
case 5:
Main.messageTray._bannerBin.x_align = Clutter.ActorAlign.END;
Main.messageTray._bannerBin.y_align = Clutter.ActorAlign.END;
break;
}
}

View file

@ -0,0 +1,645 @@
/**
* V-Shell (Vertical Workspaces)
* optionsFactory.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*/
'use strict';
const { Gtk, Gio, GObject } = imports.gi;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Settings = Me.imports.lib.settings;
const shellVersion = Settings.shellVersion;
// gettext
const _ = Settings._;
const ProfileNames = [
_('GNOME 3'),
_('GNOME 40+ - Bottom Hot Edge'),
_('Hot Corner Centric - Top Left Hot Corner'),
_('Dock Overview - Bottom Hot Edge'),
];
// libadwaita is available starting with GNOME Shell 42.
let Adw = null;
try {
Adw = imports.gi.Adw;
} catch (e) {}
function _newImageFromIconName(name) {
return Gtk.Image.new_from_icon_name(name);
}
var ItemFactory = class ItemFactory {
constructor(gOptions) {
this._gOptions = gOptions;
this._settings = this._gOptions._gsettings;
}
getRowWidget(text, caption, widget, variable, options = []) {
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 && this._gOptions.options[variable]) {
const opt = this._gOptions.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);
}
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 = this._gOptions.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;
}
this._gOptions.connect(`changed::${key}`, () => {
widget.set_active_iter(widget._comboMap[this._gOptions.get(variable, true)]);
});
widget.connect('changed', () => {
const [success, iter] = widget.get_active_iter();
if (!success)
return;
this._gOptions.set(variable, model.get_value(iter, 1));
});
}
_connectDropDown(widget, key, variable, options) {
const model = widget.get_model();
const currentValue = this._gOptions.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();
this._gOptions.set(variable, item.id);
});
this._gOptions.connect(`changed::${key}`, () => {
const newId = this._gOptions.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({
label: shellVersion < 42 ? 'Click Me!' : '',
uri,
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
hexpand: true,
});
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: 40,
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);
entry.connect('icon-press', e => e.set_text(''));
entry.connect('changed', e => opt.set(`profileName${profileIndex}`, e.get_text()));
const resetProfile = this.newButton();
resetProfile.set({
tooltip_text: _('Reset profile to defaults'),
icon_name: 'edit-delete-symbolic',
hexpand: false,
css_classes: ['destructive-action'],
});
function setName() {
let name = opt.get(`profileName${profileIndex}`, true);
if (!name)
name = ProfileNames[profileIndex - 1];
entry.set_text(name);
}
setName();
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: 'edit-delete-symbolic',
});
btn.connect('clicked', () => {
const settings = this._settings;
settings.list_keys().forEach(
key => settings.reset(key)
);
});
btn._activatable = false;
return btn;
}
};
var AdwPrefs = class {
constructor(gOptions) {
this._gOptions = 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 = 840;
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;
}
};
var LegacyPrefs = class {
constructor(gOptions) {
this._gOptions = gOptions;
}
getPrefsWidget(pages) {
const prefsWidget = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
});
const stack = new Gtk.Stack({
hexpand: true,
});
const stackSwitcher = new Gtk.StackSwitcher({
halign: Gtk.Align.CENTER,
hexpand: true,
});
const context = stackSwitcher.get_style_context();
context.add_class('caption');
stackSwitcher.set_stack(stack);
stack.set_transition_duration(300);
stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT_RIGHT);
const pageProperties = {
hscrollbar_policy: Gtk.PolicyType.NEVER,
vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
vexpand: true,
hexpand: true,
visible: true,
};
const pagesBtns = [];
for (let page of pages) {
const name = page.name;
const title = page.title;
const iconName = page.iconName;
const optionList = page.optionList;
stack.add_named(this._getLegacyPage(optionList, pageProperties), name);
pagesBtns.push(
[new Gtk.Label({ label: title }), _newImageFromIconName(iconName, Gtk.IconSize.BUTTON)]
);
}
let stBtn = stackSwitcher.get_first_child ? stackSwitcher.get_first_child() : null;
for (let i = 0; i < pagesBtns.length; i++) {
const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 6, visible: true });
const icon = pagesBtns[i][1];
icon.margin_start = 30;
icon.margin_end = 30;
box.append(icon);
box.append(pagesBtns[i][0]);
if (stackSwitcher.get_children) {
stBtn = stackSwitcher.get_children()[i];
stBtn.add(box);
} else {
stBtn.set_child(box);
stBtn.visible = true;
stBtn = stBtn.get_next_sibling();
}
}
if (stack.show_all)
stack.show_all();
if (stackSwitcher.show_all)
stackSwitcher.show_all();
prefsWidget.append(stack);
if (prefsWidget.show_all)
prefsWidget.show_all();
prefsWidget._stackSwitcher = stackSwitcher;
return prefsWidget;
}
_getLegacyPage(optionList, pageProperties) {
const page = new Gtk.ScrolledWindow(pageProperties);
const mainBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 5,
homogeneous: false,
margin_start: 30,
margin_end: 30,
margin_top: 12,
margin_bottom: 12,
});
let context = page.get_style_context();
context.add_class('background');
let frame;
let frameBox;
for (let item of optionList) {
// label can be plain text for Section Title
// or GtkBox for Option
const option = item[0];
const widget = item[1];
if (!widget) {
const lbl = new Gtk.Label({
label: option,
xalign: 0,
margin_bottom: 4,
});
context = lbl.get_style_context();
context.add_class('heading');
mainBox.append(lbl);
frame = new Gtk.Frame({
margin_bottom: 16,
});
frameBox = new Gtk.ListBox({
selection_mode: null,
});
mainBox.append(frame);
frame.set_child(frameBox);
continue;
}
const grid = new Gtk.Grid({
column_homogeneous: false,
column_spacing: 20,
margin_start: 8,
margin_end: 8,
margin_top: 8,
margin_bottom: 8,
hexpand: true,
});
grid.attach(option, 0, 0, 5, 1);
if (widget)
grid.attach(widget, 5, 0, 2, 1);
frameBox.append(grid);
}
page.set_child(mainBox);
return page;
}
};
const DropDownItem = GObject.registerClass({
GTypeName: 'DropdownItem',
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,
0, 100, 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;
}
}
);

View file

@ -0,0 +1,93 @@
/**
* V-Shell (Vertical Workspaces)
* osdWindow.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Clutter } = imports.gi;
const Main = imports.ui.main;
const OsdWindow = imports.ui.osdWindow;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const OsdPositions = {
1: {
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.START,
},
2: {
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.START,
},
3: {
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.START,
},
4: {
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.CENTER,
},
5: {
x_align: Clutter.ActorAlign.START,
y_align: Clutter.ActorAlign.END,
},
6: {
x_align: Clutter.ActorAlign.CENTER,
y_align: Clutter.ActorAlign.END,
},
7: {
x_align: Clutter.ActorAlign.END,
y_align: Clutter.ActorAlign.END,
},
};
let _overrides;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('osdWindowModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset || !moduleEnabled) {
updateExistingOsdWindows(6);
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
_overrides.addOverride('osdWindow', OsdWindow.OsdWindow.prototype, OsdWindowCommon);
}
function 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]);
},
};

View file

@ -0,0 +1,108 @@
/**
* V-Shell (Vertical Workspaces)
* overlayKey.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { GObject, Gio, GLib, Meta, St } = imports.gi;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const _ = Me.imports.lib.settings._;
const shellVersion = _Util.shellVersion;
const WIN_SEARCH_PREFIX = Me.imports.lib.windowSearchProvider.prefix;
const RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix;
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
let opt;
let _firstRun = true;
let _originalOverlayKeyHandlerId;
let _overlayKeyHandlerId;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('overlayKeyModule', true);
reset = reset || (!_firstRun && !moduleEnabled);
// don't even touch this module if disabled
if (_firstRun && !moduleEnabled)
return;
_firstRun = false;
if (reset) {
_updateOverlayKey(reset);
opt = null;
return;
}
_updateOverlayKey();
}
function _updateOverlayKey(reset = false) {
if (reset) {
_restoreOverlayKeyHandler();
} else if (!_originalOverlayKeyHandlerId) {
_originalOverlayKeyHandlerId = GObject.signal_handler_find(global.display, { signalId: 'overlay-key' });
if (_originalOverlayKeyHandlerId !== null)
global.display.block_signal_handler(_originalOverlayKeyHandlerId);
_connectOverlayKey.bind(Main.overview._overview.controls)();
}
}
function _restoreOverlayKeyHandler() {
// Disconnect modified overlay key handler
if (_overlayKeyHandlerId !== null) {
global.display.disconnect(_overlayKeyHandlerId);
_overlayKeyHandlerId = null;
}
// Unblock original overlay key handler
if (_originalOverlayKeyHandlerId !== null) {
global.display.unblock_signal_handler(_originalOverlayKeyHandlerId);
_originalOverlayKeyHandlerId = null;
}
}
function _connectOverlayKey() {
this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
this._lastOverlayKeyTime = 0;
_overlayKeyHandlerId = global.display.connect('overlay-key', () => {
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) {
if (mode === 1)
this._shiftState(Meta.MotionDirection.UP);
else if (mode === 2)
_Util.activateSearchProvider(WIN_SEARCH_PREFIX);
else if (mode === 3)
_Util.activateSearchProvider(RECENT_FILES_PREFIX);
} else {
Main.overview.toggle();
}
});
}

View file

@ -0,0 +1,59 @@
/**
* V-Shell (Vertical Workspaces)
* overview.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const Overview = imports.ui.overview;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let _overrides;
let opt;
function update(reset = false) {
if (_overrides)
_overrides.removeAll();
if (reset) {
_overrides = null;
opt = null;
return;
}
opt = Me.imports.lib.settings.opt;
_overrides = new _Util.Overrides();
_overrides.addOverride('Overview', Overview.Overview.prototype, OverviewCommon);
}
const OverviewCommon = {
_showDone() {
this._animationInProgress = false;
this._coverPane.hide();
this.emit('shown');
// Handle any calls to hide* while we were showing
if (!this._shown)
this._animateNotVisible();
this._syncGrab();
// if user activates overview during startup animation, transition needs to be shifted to the state 2 here
const controls = this._overview._controls;
if (controls._searchController._searchActive && controls._stateAdjustment.value === 1) {
if (opt.SEARCH_VIEW_ANIMATION)
controls._onSearchChanged();
else if (!opt.OVERVIEW_MODE2)
controls._stateAdjustment.value = 2;
}
},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
/**
* V-Shell (Vertical Workspaces)
* panel.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { GLib } = imports.gi;
const Main = imports.ui.main;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME;
let opt;
let _firstRun = true;
let _showingOverviewConId;
let _hidingOverviewConId;
let _styleChangedConId;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('panelModule', true);
// Avoid conflict with other extensions
const conflict = _Util.getEnabledExtensions('dash-to-panel').length ||
_Util.getEnabledExtensions('hidetopbar').length;
reset = reset || (!_firstRun && !moduleEnabled);
// don't even touch this module if disabled or in potential conflict
if (_firstRun && (reset || conflict))
return;
_firstRun = false;
const panelBox = Main.layoutManager.panelBox;
if (reset || !moduleEnabled) {
// _disconnectPanel();
reset = true;
_setPanelPosition(reset);
_updateOverviewConnection(reset);
_reparentPanel(false);
_updateStyleChangedConnection(reset);
panelBox.translation_y = 0;
Main.panel.opacity = 255;
_setPanelStructs(true);
return;
}
_setPanelPosition();
_updateStyleChangedConnection();
if (opt.PANEL_MODE === 0) {
_updateOverviewConnection(true);
_reparentPanel(false);
panelBox.translation_y = 0;
Main.panel.opacity = 255;
_setPanelStructs(true);
} else if (opt.PANEL_MODE === 1) {
if (opt.SHOW_WS_PREVIEW_BG) {
_reparentPanel(true);
if (opt.OVERVIEW_MODE2) {
// in OM2 if the panel has been moved to the overviewGroup move panel above all
Main.layoutManager.overviewGroup.set_child_above_sibling(panelBox, null);
_updateOverviewConnection();
} else {
// otherwise move the panel below overviewGroup so it can get below workspacesDisplay
Main.layoutManager.overviewGroup.set_child_below_sibling(panelBox, Main.overview._overview);
_updateOverviewConnection(true);
}
_showPanel(true);
} else {
// if ws preview bg is disabled, panel can stay in uiGroup
_reparentPanel(false);
_showPanel(false);
_updateOverviewConnection();
}
// _connectPanel();
} else if (opt.PANEL_MODE === 2) {
_updateOverviewConnection(true);
_reparentPanel(false);
_showPanel(false);
// _connectPanel();
}
_setPanelStructs(opt.PANEL_MODE === 0);
Main.layoutManager._updateHotCorners();
}
function _setPanelPosition(reset = false) {
const geometry = global.display.get_monitor_geometry(global.display.get_primary_monitor());
const panelBox = Main.layoutManager.panelBox;
const panelHeight = Main.panel.height; // panelBox height can be 0 after shell start
if (opt.PANEL_POSITION_TOP || reset)
panelBox.set_position(geometry.x, geometry.y);
else
panelBox.set_position(geometry.x, geometry.y + geometry.height - panelHeight);
}
function _updateStyleChangedConnection(reset = false) {
if (reset) {
if (_styleChangedConId) {
Main.panel.disconnect(_styleChangedConId);
_styleChangedConId = 0;
}
} else if (!_styleChangedConId) {
Main.panel.connect('style-changed', () => {
if (opt.PANEL_MODE === 1)
Main.panel.add_style_pseudo_class('overview');
else if (opt.OVERVIEW_MODE2)
Main.panel.remove_style_pseudo_class('overview');
});
}
}
function _updateOverviewConnection(reset = false) {
if (reset) {
if (_hidingOverviewConId) {
Main.overview.disconnect(_hidingOverviewConId);
_hidingOverviewConId = 0;
}
if (_showingOverviewConId) {
Main.overview.disconnect(_showingOverviewConId);
_showingOverviewConId = 0;
}
} else {
if (!_hidingOverviewConId) {
_hidingOverviewConId = Main.overview.connect('hiding', () => {
if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2)
_showPanel(false);
});
}
if (!_showingOverviewConId) {
_showingOverviewConId = Main.overview.connect('showing', () => {
if (!opt.SHOW_WS_PREVIEW_BG || opt.OVERVIEW_MODE2 || Main.layoutManager.panelBox.translation_y)
_showPanel(true);
});
}
}
}
function _reparentPanel(reparent = false) {
const panel = Main.layoutManager.panelBox;
if (reparent && panel.get_parent() === Main.layoutManager.uiGroup) {
Main.layoutManager.uiGroup.remove_child(panel);
Main.layoutManager.overviewGroup.add_child(panel);
} else if (!reparent && panel.get_parent() === Main.layoutManager.overviewGroup) {
Main.layoutManager.overviewGroup.remove_child(panel);
// return the panel at default position, panel shouldn't cover objects that should be above
Main.layoutManager.uiGroup.insert_child_at_index(panel, 4);
}
}
function _setPanelStructs(state) {
Main.layoutManager._trackedActors.forEach(a => {
if (a.actor === Main.layoutManager.panelBox)
a.affectsStruts = state;
});
// workaround to force maximized windows to resize after removing affectsStruts
// simulation of minimal swipe gesture to the opposite direction
// todo - needs better solution!!!!!!!!!!!
// const direction = _getAppGridAnimationDirection() === 2 ? 1 : -1;
// Main.overview._swipeTracker._beginTouchSwipe(null, global.get_current_time(), 1, 1);
// Main.overview._swipeTracker._updateGesture(null, global.get_current_time(), direction, 1);
// GLib.timeout_add(0, 50, () => Main.overview._swipeTracker._endGesture(global.get_current_time(), 1, true));*/
}
function _showPanel(show = true) {
if (show) {
Main.panel.opacity = 255;
Main.layoutManager.panelBox.ease({
duration: ANIMATION_TIME,
translation_y: 0,
onComplete: () => {
_setPanelStructs(opt.PANEL_MODE === 0);
},
});
} else {
const panelHeight = Main.panel.height;
Main.layoutManager.panelBox.ease({
duration: ANIMATION_TIME,
translation_y: opt.PANEL_POSITION_TOP ? -panelHeight + 1 : panelHeight - 1,
onComplete: () => {
Main.panel.opacity = 0;
_setPanelStructs(opt.PANEL_MODE === 0);
},
});
}
}

View file

@ -0,0 +1,260 @@
/**
* Vertical Workspaces
* recentFilesSearchProvider.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*/
'use strict';
const { GLib, Gio, Meta, St, Shell, Gtk } = imports.gi;
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Settings = Me.imports.lib.settings;
const _Util = Me.imports.lib.util;
// gettext
const _ = Settings._;
const shellVersion = Settings.shellVersion;
const ModifierType = imports.gi.Clutter.ModifierType;
let recentFilesSearchProvider;
let _enableTimeoutId = 0;
// prefix helps to eliminate results from other search providers
// so it needs to be something less common
// needs to be accessible from vw module
var prefix = 'fq//';
var opt;
function getOverviewSearchResult() {
return Main.overview._overview.controls._searchController._searchResults;
}
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
if (!reset && opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && !recentFilesSearchProvider) {
enable();
} else if (reset || !opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED) {
disable();
opt = null;
}
}
function enable() {
// delay because Fedora had problem to register a new provider soon after Shell restarts
_enableTimeoutId = GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
2000,
() => {
if (!recentFilesSearchProvider) {
recentFilesSearchProvider = new RecentFilesSearchProvider(opt);
getOverviewSearchResult()._registerProvider(recentFilesSearchProvider);
}
_enableTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}
);
}
function disable() {
if (recentFilesSearchProvider) {
getOverviewSearchResult()._unregisterProvider(recentFilesSearchProvider);
recentFilesSearchProvider = null;
}
if (_enableTimeoutId) {
GLib.source_remove(_enableTimeoutId);
_enableTimeoutId = 0;
}
}
function makeResult(window, i) {
const app = Shell.WindowTracker.get_default().get_window_app(window);
const appName = app ? app.get_name() : 'Unknown';
const windowTitle = window.get_title();
const wsIndex = window.get_workspace().index();
return {
'id': i,
// convert all accented chars to their basic form and lower case for search
'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(),
appName,
windowTitle,
window,
};
}
const closeSelectedRegex = /^\/x!$/;
const closeAllResultsRegex = /^\/xa!$/;
const moveToWsRegex = /^\/m[0-9]+$/;
const moveAllToWsRegex = /^\/ma[0-9]+$/;
const RecentFilesSearchProvider = class RecentFilesSearchProvider {
constructor() {
this.id = 'org.gnome.Nautilus.desktop';
this.appInfo = Gio.AppInfo.create_from_commandline('/usr/bin/nautilus -ws recent:///', 'Recent Files', null);
// this.appInfo = Shell.AppSystem.get_default().lookup_app('org.gnome.Nautilus.desktop').appInfo;
this.appInfo.get_description = () => _('Search recent files');
this.appInfo.get_name = () => _('Recent Files');
this.appInfo.get_id = () => this.id;
this.appInfo.get_icon = () => Gio.icon_new_for_string('document-open-recent-symbolic');
this.appInfo.should_show = () => true;
this.canLaunchSearch = true;
this.isRemoteProvider = false;
}
_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 = this.files;
const _terms = [].concat(termsCopy);
// let match;
const term = _terms.join(' ');
/* match = s => {
return fuzzyMatch(term, s);
}; */
const results = [];
let m;
for (let id in candidates) {
const file = this.files[id];
const name = `${file.get_age()}d: ${file.get_display_name()} ${file.get_uri_display().replace(`/${file.get_display_name()}`, '')}`;
if (opt.SEARCH_FUZZY)
m = _Util.fuzzyMatch(term, name);
else
m = _Util.strictMatch(term, name);
if (m !== -1)
results.push({ weight: m, id });
}
results.sort((a, b) => this.files[a.id].get_visited() < this.files[b.id].get_visited());
this.resultIds = results.map(item => item.id);
return this.resultIds;
}
getResultMetas(resultIds, callback = null) {
const metas = resultIds.map(id => this.getResultMeta(id));
if (shellVersion >= 43)
return new Promise(resolve => resolve(metas));
else if (callback)
callback(metas);
return null;
}
getResultMeta(resultId) {
const result = this.files[resultId];
return {
'id': resultId,
'name': `${result.get_age()}: ${result.get_display_name()}`,
'description': `${result.get_uri_display().replace(`/${result.get_display_name()}`, '')}`,
'createIcon': size => {
let icon = this.getIcon(result, size);
return icon;
},
};
}
getIcon(result, size) {
let file = Gio.File.new_for_uri(result.get_uri());
let info = file.query_info(Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH,
Gio.FileQueryInfoFlags.NONE, null);
let path = info.get_attribute_byte_string(
Gio.FILE_ATTRIBUTE_THUMBNAIL_PATH);
let icon, gicon;
if (path) {
gicon = Gio.FileIcon.new(Gio.File.new_for_path(path));
} else {
const appInfo = Gio.AppInfo.get_default_for_type(result.get_mime_type(), false);
if (appInfo)
gicon = appInfo.get_icon();
}
if (gicon)
icon = new St.Icon({ gicon, icon_size: size });
else
icon = new St.Icon({ icon_name: 'icon-missing', icon_size: size });
return icon;
}
launchSearch(/* terms, timeStamp */) {
this._openNautilus('recent:///');
}
_openNautilus(uri) {
try {
GLib.spawn_command_line_async(`nautilus -ws ${uri}`);
} catch (e) {
log(e);
}
}
activateResult(resultId /* , terms, timeStamp */) {
const file = this.files[resultId];
if (_Util.isShiftPressed()) {
Main.overview.toggle();
this._openNautilus(file.get_uri());
} else {
const appInfo = Gio.AppInfo.get_default_for_type(file.get_mime_type(), false);
if (!(appInfo && appInfo.launch_uris([file.get_uri()], null)))
this._openNautilus(file.get_uri());
}
}
getInitialResultSet(terms, callback /* , cancellable = null*/) {
// In GS 43 callback arg has been removed
/* if (shellVersion >= 43)
cancellable = callback; */
const filesDict = {};
const files = Gtk.RecentManager.get_default().get_items().filter(f => f.exists());
for (let file of files)
filesDict[file.get_uri()] = file;
this.files = filesDict;
if (shellVersion >= 43)
return new Promise(resolve => resolve(this._getResultSet(terms)));
else
callback(this._getResultSet(terms));
return null;
}
filterResults(results, maxResults) {
return results.slice(0, 20);
// return results.slice(0, maxResults);
}
getSubsearchResultSet(previousResults, terms, callback /* , cancellable*/) {
// if we return previous results, quick typers get non-actual results
callback(this._getResultSet(terms));
}
/* createResultObject(resultMeta) {
return this.files[resultMeta.id];
}*/
};

View file

@ -0,0 +1,206 @@
/**
* V-Shell (Vertical Workspaces)
* search.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Shell, Gio, St, Clutter } = imports.gi;
const Main = imports.ui.main;
const AppDisplay = imports.ui.appDisplay;
const Search = imports.ui.search;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const _ = Me.imports.lib.settings._;
const shellVersion = _Util.shellVersion;
let opt;
let _overrides;
let _firstRun = true;
let SEARCH_MAX_WIDTH;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('searchModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
_updateSearchViewWidth(reset);
if (reset) {
Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.FILL;
opt = null;
_overrides = null;
return;
}
_overrides = new _Util.Overrides();
_overrides.addOverride('AppSearchProvider', AppDisplay.AppSearchProvider.prototype, AppSearchProvider);
_overrides.addOverride('SearchResult', Search.SearchResult.prototype, SearchResult);
_overrides.addOverride('SearchResultsView', Search.SearchResultsView.prototype, SearchResultsView);
// Don't expand the search view vertically and align it to the top
// this is important in the static workspace mode when the search view bg is not transparent
// also the "Searching..." and "No Results" notifications will be closer to the search entry, with the distance given by margin-top in the stylesheet
Main.overview._overview._controls.layoutManager._searchController.y_align = Clutter.ActorAlign.START;
}
function _updateSearchViewWidth(reset = false) {
const searchContent = Main.overview._overview._controls.layoutManager._searchController._searchResults._content;
if (!SEARCH_MAX_WIDTH) { // just store original value;
const themeNode = searchContent.get_theme_node();
const width = themeNode.get_max_width();
SEARCH_MAX_WIDTH = width;
}
if (reset) {
searchContent.set_style('');
} else {
let width = Math.round(SEARCH_MAX_WIDTH * opt.SEARCH_VIEW_SCALE);
searchContent.set_style(`max-width: ${width}px;`);
}
}
// AppDisplay.AppSearchProvider
const AppSearchProvider = {
getInitialResultSet(terms, callback, _cancellable) {
// Defer until the parental controls manager is initialized, so the
// results can be filtered correctly.
if (!this._parentalControlsManager.initialized) {
let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => {
if (this._parentalControlsManager.initialized) {
this._parentalControlsManager.disconnect(initializedId);
this.getInitialResultSet(terms, callback, _cancellable);
}
});
return;
}
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 dispName = appInfo.get_display_name() || '';
let gName = appInfo.get_generic_name() || '';
let description = appInfo.get_description() || '';
let categories = appInfo.get_string('Categories') || '';
let keywords = appInfo.get_string('Keywords') || '';
name = dispName;
string = `${dispName} ${gName} ${description} ${categories} ${keywords}`;
}
}
let m = -1;
if (shouldShow && opt.SEARCH_FUZZY) {
m = _Util.fuzzyMatch(pattern, name);
m = (m + _Util.strictMatch(pattern, string)) / 2;
} else if (shouldShow) {
m = _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) => _Util.isMoreRelevant(a.get_display_name(), b.get_display_name(), pattern));
let results = appInfoList.map(app => app.get_id());
results = results.concat(this._systemActions.getMatchingActions(terms));
if (shellVersion < 43)
callback(results);
else
return new Promise(resolve => resolve(results));
},
// App search result size
createResultObject(resultMeta) {
if (resultMeta.id.endsWith('.desktop')) {
const icon = new AppDisplay.AppIcon(this._appSys.lookup_app(resultMeta['id']), {
expandTitleOnHover: false,
});
icon.icon.setIconSize(opt.SEARCH_ICON_SIZE);
return icon;
} else {
const icon = new AppDisplay.SystemActionIcon(this, resultMeta);
icon.icon._setSizeManually = true;
icon.icon.setIconSize(opt.SEARCH_ICON_SIZE);
return icon;
}
},
};
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 (!_Util.isShiftPressed())
Main.overview.toggle();
},
};
const SearchResultsView = {
_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.'));
}
},
};

View file

@ -0,0 +1,469 @@
/**
* V-Shell (Vertical Workspaces)
* settings.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*/
'use strict';
const { GLib } = imports.gi;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
var shellVersion = parseFloat(Config.PACKAGE_VERSION);
const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']);
var _ = Gettext.gettext;
const _schema = Me.metadata['settings-schema'];
// common instance of Options accessible from all modules
var opt;
var Options = class Options {
constructor() {
this._gsettings = ExtensionUtils.getSettings(_schema);
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._updateCachedSettings();
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: ['bool', 'secondary-ws-preview-shift'],
wsThumbnailsFull: ['bool', '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'],
dashShowWindowsIcon: ['int', 'dash-show-windows-icon'],
dashShowRecentFilesIcon: ['int', 'dash-show-recent-files-icon'],
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'],
dashBgRadius: ['int', 'dash-bg-radius'],
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'],
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'],
fixUbuntuDock: ['boolean', 'fix-ubuntu-dock'],
winPreviewIconSize: ['int', 'win-preview-icon-size'],
alwaysShowWinTitles: ['boolean', 'always-show-win-titles'],
startupState: ['int', 'startup-state'],
overviewMode: ['int', 'overview-mode'],
workspaceSwitcherAnimation: ['int', 'workspace-switcher-animation'],
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'],
appGridNamesMode: ['int', 'app-grid-names'],
appGridActivePreview: ['boolean', 'app-grid-active-preview'],
appGridFolderCenter: ['boolean', 'app-grid-folder-center'],
appGridPageWidthScale: ['int', 'app-grid-page-width-scale'],
appGridSpacing: ['int', 'app-grid-spacing'],
searchWindowsEnable: ['boolean', 'search-windows-enable'],
searchRecentFilesEnable: ['boolean', 'search-recent-files-enable'],
searchFuzzy: ['boolean', 'search-fuzzy'],
searchMaxResultsRows: ['int', 'search-max-results-rows'],
dashShowWindowsBeforeActivation: ['int', 'dash-show-windows-before-activation'],
dashIconScroll: ['int', 'dash-icon-scroll'],
searchWindowsIconScroll: ['int', 'search-windows-icon-scroll'],
panelVisibility: ['int', 'panel-visibility'],
panelPosition: ['int', 'panel-position'],
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'],
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'],
windowIconClickSearch: ['boolean', 'window-icon-click-search'],
overlayKeySecondary: ['int', 'overlay-key-secondary'],
workspaceThumbnailsModule: ['boolean', 'workspace-thumbnails-module'],
workspaceSwitcherPopupModule: ['boolean', 'workspace-switcher-popup-module'],
workspaceAnimationModule: ['boolean', 'workspace-animation-module'],
workspaceModule: ['boolean', 'workspace-module'],
windowManagerModule: ['boolean', 'window-manager-module'],
windowPreviewModule: ['boolean', 'window-preview-module'],
winAttentionHandlerModule: ['boolean', 'win-attention-handler-module'],
swipeTrackerModule: ['boolean', 'swipe-tracker-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.shellVersion = shellVersion;
// this.storeProfile(0);
}
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;
}
}
_updateCachedSettings() {
Object.keys(this.options).forEach(v => this.get(v, true));
}
get(option, updateCache = false) {
if (!this.options[option]) {
log(`[${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 => {
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();
this._gsettings.set_boolean('aaa-loading-profile', !this._gsettings.get_boolean('aaa-loading-profile'));
for (let o of Object.keys(options)) {
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.DASH_POSITION = this.get('dashPosition', true);
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_CLICK_ACTION = this.get('dashShowWindowsBeforeActivation', true);
this.DASH_ICON_SCROLL = this.get('dashIconScroll', true);
this.DASH_SHIFT_CLICK_MV = true;
this.SEARCH_WINDOWS_ICON_SCROLL = this.get('searchWindowsIconScroll', true);
this.DASH_POSITION_ADJUSTMENT = this.get('dashPositionAdjust', true);
this.DASH_POSITION_ADJUSTMENT = this.DASH_POSITION_ADJUSTMENT * -1 / 100; // range 1 to -1
this.CENTER_DASH_WS = this.get('centerDashToWs', true);
this.MAX_ICON_SIZE = 64; // updates from main module
this.SHOW_WINDOWS_ICON = this.get('dashShowWindowsIcon', true);
this.SHOW_RECENT_FILES_ICON = this.get('dashShowRecentFilesIcon', true);
this.WS_TMB_POSITION = this.get('workspaceThumbnailsPosition', true);
this.ORIENTATION = this.WS_TMB_POSITION > 4 ? 0 : 1;
this.WORKSPACE_MAX_SPACING = this.get('wsMaxSpacing', true);
// ORIENTATION || DASH_LEFT || DASH_RIGHT ? 350 : 80;
this.SHOW_WS_TMB = ![4, 9].includes(this.WS_TMB_POSITION); // 4, 9 - disable
this.WS_TMB_FULL = this.get('wsThumbnailsFull', true);
// 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', true) * -1 / 100; // range 1 to -1
this.SEC_WS_TMB_POSITION = this.get('secWsThumbnailsPosition', true);
this.SHOW_SEC_WS_TMB = this.SEC_WS_TMB_POSITION !== 3 && this.SHOW_WS_TMB;
this.SEC_WS_TMB_TOP = (this.SEC_WS_TMB_POSITION === 0 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_TOP);
this.SEC_WS_TMB_RIGHT = (this.SEC_WS_TMB_POSITION === 1 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_RIGHT);
this.SEC_WS_TMB_BOTTOM = (this.SEC_WS_TMB_POSITION === 1 && !this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_BOTTOM);
this.SEC_WS_TMB_LEFT = (this.SEC_WS_TMB_POSITION === 0 && this.ORIENTATION) || (this.SEC_WS_TMB_POSITION === 2 && this.WS_TMB_LEFT);
this.SEC_WS_TMB_POSITION_ADJUSTMENT = this.get('secWsTmbPositionAdjust', true) * -1 / 100; // range 1 to -1
this.SEC_WS_PREVIEW_SHIFT = this.get('secWsPreviewShift', true);
this.SHOW_WST_LABELS = this.get('showWsTmbLabels', true);
this.SHOW_WST_LABELS_ON_HOVER = this.get('showWsTmbLabelsOnHover', true);
this.CLOSE_WS_BUTTON_MODE = this.get('closeWsButtonMode', true);
this.MAX_THUMBNAIL_SCALE = this.get('wsThumbnailScale', true) / 100;
this.MAX_THUMBNAIL_SCALE_APPGRID = this.get('wsThumbnailScaleAppGrid', true) / 100;
if (this.MAX_THUMBNAIL_SCALE_APPGRID === 0)
this.MAX_THUMBNAIL_SCALE_APPGRID = this.MAX_THUMBNAIL_SCALE;
this.MAX_THUMBNAIL_SCALE_STABLE = this.MAX_THUMBNAIL_SCALE === this.MAX_THUMBNAIL_SCALE_APPGRID;
this.SEC_MAX_THUMBNAIL_SCALE = this.get('secWsThumbnailScale', true) / 100;
this.WS_PREVIEW_SCALE = this.get('wsPreviewScale', true) / 100;
this.SEC_WS_PREVIEW_SCALE = this.get('secWsPreviewScale', true) / 100;
// calculate number of possibly visible neighbor previews according to ws scale
this.NUMBER_OF_VISIBLE_NEIGHBORS = Math.round(1 + (1 - this.WS_PREVIEW_SCALE) / 4);
this.SHOW_WS_TMB_BG = this.get('showWsSwitcherBg', true) && this.SHOW_WS_TMB;
this.WS_PREVIEW_BG_RADIUS = this.get('wsPreviewBgRadius', true);
this.SHOW_WS_PREVIEW_BG = this.get('showWsPreviewBg', true);
this.CENTER_APP_GRID = this.get('centerAppGrid', true);
this.SHOW_SEARCH_ENTRY = this.get('showSearchEntry', true);
this.CENTER_SEARCH_VIEW = this.get('centerSearch', true);
this.APP_GRID_ANIMATION = this.get('appGridAnimation', true);
if (this.APP_GRID_ANIMATION === 4)
this.APP_GRID_ANIMATION = this._getAnimationDirection();
this.SEARCH_VIEW_ANIMATION = this.get('searchViewAnimation', true);
if (this.SEARCH_VIEW_ANIMATION === 4)
this.SEARCH_VIEW_ANIMATION = 3;
this.WS_ANIMATION = this.get('workspaceAnimation', true);
this.WIN_PREVIEW_ICON_SIZE = [64, 48, 32, 22, 8][this.get('winPreviewIconSize', true)];
this.ALWAYS_SHOW_WIN_TITLES = this.get('alwaysShowWinTitles', true);
this.STARTUP_STATE = this.get('startupState', true);
this.SHOW_BG_IN_OVERVIEW = this.get('showBgInOverview', true);
this.OVERVIEW_BG_BRIGHTNESS = this.get('overviewBgBrightness', true) / 100;
this.OVERVIEW_BG_BLUR_SIGMA = this.get('overviewBgBlurSigma', true);
this.APP_GRID_BG_BLUR_SIGMA = this.get('appGridBgBlurSigma', true);
this.SMOOTH_BLUR_TRANSITIONS = this.get('smoothBlurTransitions', true);
this.OVERVIEW_MODE = this.get('overviewMode', true);
this.OVERVIEW_MODE2 = this.OVERVIEW_MODE === 2;
this.WORKSPACE_MODE = this.OVERVIEW_MODE ? 0 : 1;
this.STATIC_WS_SWITCHER_BG = this.get('workspaceSwitcherAnimation', true);
this.ANIMATION_TIME_FACTOR = this.get('animationSpeedFactor', true) / 100;
this.SEARCH_ICON_SIZE = this.get('searchIconSize', true);
this.SEARCH_VIEW_SCALE = this.get('searchViewScale', true) / 100;
this.SEARCH_MAX_ROWS = this.get('searchMaxResultsRows', true);
this.SEARCH_FUZZY = this.get('searchFuzzy', true);
this.APP_GRID_ALLOW_INCOMPLETE_PAGES = this.get('appGridIncompletePages', true);
this.APP_GRID_ICON_SIZE = this.get('appGridIconSize', true);
this.APP_GRID_COLUMNS = this.get('appGridColumns', true);
this.APP_GRID_ROWS = this.get('appGridRows', true);
this.APP_GRID_ADAPTIVE = !this.APP_GRID_COLUMNS && !this.APP_GRID_ROWS;
this.APP_GRID_ORDER = this.get('appGridOrder', true);
this.APP_GRID_INCLUDE_DASH = this.get('appGridContent', true);
/* 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', true);
this.APP_GRID_FOLDER_ICON_SIZE = this.get('appGridFolderIconSize', true);
this.APP_GRID_FOLDER_ICON_GRID = this.get('appGridFolderIconGrid', true);
this.APP_GRID_FOLDER_COLUMNS = this.get('appGridFolderColumns', true);
this.APP_GRID_FOLDER_ROWS = this.get('appGridFolderRows', true);
this.APP_GRID_SPACING = this.get('appGridSpacing', true);
this.APP_GRID_FOLDER_DEFAULT = this.APP_GRID_FOLDER_ROWS === 3 && this.APP_GRID_FOLDER_COLUMNS === 3;
this.APP_GRID_ACTIVE_PREVIEW = this.get('appGridActivePreview', true);
this.APP_GRID_FOLDER_CENTER = this.get('appGridFolderCenter', true);
this.APP_GRID_PAGE_WIDTH_SCALE = this.get('appGridPageWidthScale', true) / 100;
this.APP_GRID_ICON_SIZE_DEFAULT = this.APP_GRID_ACTIVE_PREVIEW && !this.APP_GRID_ORDER ? 176 : 96;
this.APP_GRID_FOLDER_ICON_SIZE_DEFAULT = 96;
this.WINDOW_SEARCH_PROVIDER_ENABLED = this.get('searchWindowsEnable', true);
this.RECENT_FILES_SEARCH_PROVIDER_ENABLED = this.get('searchRecentFilesEnable', true);
this.PANEL_POSITION_TOP = this.get('panelPosition', true) === 0;
this.PANEL_MODE = this.get('panelVisibility', true);
this.PANEL_DISABLED = this.PANEL_MODE === 2;
this.PANEL_OVERVIEW_ONLY = this.PANEL_MODE === 1;
this.START_Y_OFFSET = 0; // set from main module
this.FIX_UBUNTU_DOCK = this.get('fixUbuntuDock', true);
this.WINDOW_ATTENTION_MODE = this.get('windowAttentionMode', true);
this.WINDOW_ATTENTION_DISABLE_NOTIFICATIONS = this.WINDOW_ATTENTION_MODE === 1;
this.WINDOW_ATTENTION_FOCUS_IMMEDIATELY = this.WINDOW_ATTENTION_MODE === 2;
this.WS_SW_POPUP_H_POSITION = this.get('wsSwPopupHPosition', true) / 100;
this.WS_SW_POPUP_V_POSITION = this.get('wsSwPopupVPosition', true) / 100;
this.WS_SW_POPUP_MODE = this.get('wsSwPopupMode', true);
this.SHOW_FAV_NOTIFICATION = this.get('favoritesNotify', true);
this.NOTIFICATION_POSITION = this.get('notificationPosition', true);
this.OSD_POSITION = this.get('osdPosition', true);
this.HOT_CORNER_ACTION = this.get('hotCornerAction', true);
this.HOT_CORNER_POSITION = this.get('hotCornerPosition', true);
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', true);
this.HOT_CORNER_RIPPLES = this.get('hotCornerRipples', true);
this.ALWAYS_ACTIVATE_SELECTED_WINDOW = this.get('alwaysActivateSelectedWindow', true);
this.WINDOW_ICON_CLICK_SEARCH = this.get('windowIconClickSearch', true);
this.OVERLAY_KEY_SECONDARY = this.get('overlayKeySecondary', true);
}
_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
}
};

View file

@ -0,0 +1,87 @@
/**
* V-Shell (Vertical Workspaces)
* swipeTracker.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Clutter, GObject } = imports.gi;
const Main = imports.ui.main;
const SwipeTracker = imports.ui.swipeTracker;
const Me = imports.misc.extensionUtils.getCurrentExtension();
let opt;
let _firstRun = true;
let _vwGestureUpdateId;
let _originalGestureUpdateId;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('swipeTrackerModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (reset || !opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL
// original swipeTrackers' orientation and updateGesture function
Main.overview._swipeTracker.orientation = Clutter.Orientation.VERTICAL;
Main.wm._workspaceAnimation._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL;
Main.overview._swipeTracker._updateGesture = SwipeTracker.SwipeTracker.prototype._updateGesture;
if (_vwGestureUpdateId) {
Main.overview._swipeTracker._touchpadGesture.disconnect(_vwGestureUpdateId);
_vwGestureUpdateId = 0;
}
if (_originalGestureUpdateId) {
Main.overview._swipeTracker._touchpadGesture.unblock_signal_handler(_originalGestureUpdateId);
_originalGestureUpdateId = 0;
}
opt = null;
return;
}
if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL
// reverse swipe gestures for enter/leave overview and ws switching
Main.overview._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL;
Main.wm._workspaceAnimation._swipeTracker.orientation = Clutter.Orientation.VERTICAL;
// overview's updateGesture() function should reflect ws tmb position to match appGrid/ws animation direction
// function in connection cannot be overridden in prototype of its class because connected is actually another copy of the original function
if (!_originalGestureUpdateId) {
_originalGestureUpdateId = GObject.signal_handler_find(Main.overview._swipeTracker._touchpadGesture, { signalId: 'update' });
Main.overview._swipeTracker._touchpadGesture.block_signal_handler(_originalGestureUpdateId);
Main.overview._swipeTracker._updateGesture = SwipeTrackerVertical._updateGesture;
_vwGestureUpdateId = Main.overview._swipeTracker._touchpadGesture.connect('update', SwipeTrackerVertical._updateGesture.bind(Main.overview._swipeTracker));
}
}
}
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);
},
};

View file

@ -0,0 +1,364 @@
/**
* V-Shell (Vertical Workspaces)
* util.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const Gi = imports._gi;
const { Shell, Meta, Clutter } = imports.gi;
const Config = imports.misc.config;
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
var shellVersion = parseFloat(Config.PACKAGE_VERSION);
var Overrides = class {
constructor() {
this._overrides = {};
}
addOverride(name, prototype, overrideList) {
this._overrides[name] = {
originals: this.overrideProto(prototype, overrideList),
prototype,
};
}
removeOverride(name) {
const override = this._overrides[name];
if (!override)
return false;
this.overrideProto(override.prototype, override.originals);
this._overrides[name] = undefined;
return true;
}
removeAll() {
for (let name in this._overrides) {
this.removeOverride(name);
this._overrides[name] = undefined;
}
}
hookVfunc(proto, symbol, func) {
proto[Gi.hook_up_vfunc_symbol](symbol, func);
}
overrideProto(proto, overrides) {
const backup = {};
for (let symbol in overrides) {
if (symbol.startsWith('after_')) {
const actualSymbol = symbol.slice('after_'.length);
const 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 {
backup[symbol] = proto[symbol];
if (symbol.startsWith('vfunc')) {
if (shellVersion < 42)
this.hookVfunc(proto, symbol.slice(6), overrides[symbol]);
else
this.hookVfunc(proto[Gi.gobject_prototype_symbol], symbol.slice(6), overrides[symbol]);
} else {
proto[symbol] = overrides[symbol];
}
}
}
return backup;
}
};
function getOverviewTranslations(opt, dash, tmbBox, searchEntryBin) {
// const tmbBox = Main.overview._overview._controls._thumbnailsBox;
let searchTranslationY = 0;
if (searchEntryBin.visible) {
const offset = (dash.visible && (!opt.DASH_VERTICAL ? dash.height + 12 : 0)) +
(opt.WS_TMB_TOP ? tmbBox.height + 12 : 0);
searchTranslationY = -searchEntryBin.height - offset - 30;
}
let tmbTranslationX = 0;
let tmbTranslationY = 0;
let offset;
if (tmbBox.visible) {
switch (opt.WS_TMB_POSITION) {
case 3: // left
offset = 10 + (dash?.visible && opt.DASH_LEFT ? dash.width : 0);
tmbTranslationX = -tmbBox.width - offset;
tmbTranslationY = 0;
break;
case 1: // right
offset = 10 + (dash?.visible && opt.DASH_RIGHT ? dash.width : 0);
tmbTranslationX = tmbBox.width + offset;
tmbTranslationY = 0;
break;
case 0: // top
offset = 10 + (dash?.visible && opt.DASH_TOP ? dash.height : 0) + Main.panel.height;
tmbTranslationX = 0;
tmbTranslationY = -tmbBox.height - offset;
break;
case 2: // bottom
offset = 10 + (dash?.visible && opt.DASH_BOTTOM ? dash.height : 0) + Main.panel.height; // just for case the panel is at bottom
tmbTranslationX = 0;
tmbTranslationY = tmbBox.height + offset;
break;
}
}
let dashTranslationX = 0;
let dashTranslationY = 0;
let position = opt.DASH_POSITION;
// if DtD replaced the original Dash, read its position
if (dashIsDashToDock())
position = dash._position;
if (dash?.visible) {
switch (position) {
case 0: // top
dashTranslationX = 0;
dashTranslationY = -dash.height - dash.margin_bottom - Main.panel.height;
break;
case 1: // right
dashTranslationX = dash.width;
dashTranslationY = 0;
break;
case 2: // bottom
dashTranslationX = 0;
dashTranslationY = dash.height + dash.margin_bottom + Main.panel.height;
break;
case 3: // left
dashTranslationX = -dash.width;
dashTranslationY = 0;
break;
}
}
return [tmbTranslationX, tmbTranslationY, dashTranslationX, dashTranslationY, searchTranslationY];
}
function openPreferences() {
const windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, null);
let tracker = Shell.WindowTracker.get_default();
let metaWin, isVW = null;
for (let win of windows) {
const app = tracker.get_window_app(win);
if (win.get_title().includes(Me.metadata.name) && app.get_name() === 'Extensions') {
// this is our existing window
metaWin = win;
isVW = true;
break;
} else if (win.wm_class.includes('org.gnome.Shell.Extensions')) {
// this is prefs window of another extension
metaWin = win;
isVW = false;
}
}
if (metaWin && !isVW) {
// other prefs window blocks opening another prefs window, so close it
metaWin.delete(global.get_current_time());
} else if (metaWin && isVW) {
// 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 && !isVW)) {
try {
Main.extensionManager.openExtensionPrefs(Me.metadata.uuid, '', {});
} catch (e) {
log(e);
}
}
}
function activateSearchProvider(prefix = '') {
const searchEntry = Main.overview.searchEntry;
if (!searchEntry.get_text() || !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);
} else {
searchEntry.set_text('');
}
}
function dashNotDefault() {
return Main.overview.dash !== Main.overview._overview._controls.layoutManager._dash;
}
function dashIsDashToDock() {
return Main.overview.dash._isHorizontal !== undefined;
}
// Reorder Workspaces - callback for Dash and workspacesDisplay
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);
}
function exposeWindows(adjustment, activateKeyboard) {
// expose windows for static overview modes
if (!adjustment.value && !Main.overview._animationInProgress) {
if (adjustment.value === 0) {
adjustment.value = 0;
adjustment.ease(1, {
duration: 200,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => {
if (activateKeyboard) {
Main.ctrlAltTabManager._items.forEach(i => {
if (i.sortGroup === 1 && i.name === 'Windows')
Main.ctrlAltTabManager.focusGroup(i);
});
}
},
});
}
}
}
function isShiftPressed(state = null) {
if (state === null)
[,, state] = global.get_pointer();
return (state & Clutter.ModifierType.SHIFT_MASK) !== 0;
}
function isCtrlPressed(state = null) {
if (state === null)
[,, state] = global.get_pointer();
return (state & Clutter.ModifierType.CONTROL_MASK) !== 0;
}
function isAltPressed(state = null) {
if (state === null)
[,, state] = global.get_pointer();
return (state & Clutter.ModifierType.MOD1_MASK) !== 0;
}
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];
}
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;
}
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;
}
function getEnabledExtensions(uuid = '') {
let extensions = [];
Main.extensionManager._extensions.forEach(e => {
if (e.state === 1 && e.uuid.includes(uuid))
extensions.push(e);
});
return extensions;
}
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;
}

View file

@ -0,0 +1,90 @@
/**
* V-Shell (Vertical Workspaces)
* windowAttentionHandler.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const Main = imports.ui.main;
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const MessageTray = imports.ui.messageTray;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let opt;
let _firstRun = false;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('winAttentionHandlerModule', true);
reset = reset || !moduleEnabled;
if (_firstRun && reset)
return;
_firstRun = false;
if (reset) {
reset = true;
_updateConnections(reset);
opt = null;
return;
}
_updateConnections();
}
function _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);
const source = new WindowAttentionHandler.WindowAttentionSource(app, window);
Main.messageTray.add(source);
let [title, banner] = this._getTitleAndBanner(app, window);
const notification = new MessageTray.Notification(source, title, banner);
notification.connect('activated', () => {
source.open();
});
notification.setForFeedback(true);
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, banner] = this._getTitleAndBanner(app, window);
notification.update(title, banner);
}, source);
},
};

View file

@ -0,0 +1,217 @@
/**
* V-Shell (Vertical Workspaces)
* windowManager.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { GObject, Clutter, Meta } = imports.gi;
const Main = imports.ui.main;
const WindowManager = imports.ui.windowManager;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let _overrides;
const MINIMIZE_WINDOW_ANIMATION_TIME = WindowManager.MINIMIZE_WINDOW_ANIMATION_TIME;
const MINIMIZE_WINDOW_ANIMATION_MODE = WindowManager.MINIMIZE_WINDOW_ANIMATION_MODE;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('windowManagerModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
_replaceMinimizeFunction(reset);
if (reset) {
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
_overrides.addOverride('WindowManager', WindowManager.WindowManager.prototype, WindowManagerCommon);
}
// ------------- Fix and adapt minimize/unminimize animations --------------------------------------
let _originalMinimizeSigId;
let _minimizeSigId;
let _originalUnminimizeSigId;
let _unminimizeSigId;
function _replaceMinimizeFunction(reset = false) {
if (reset) {
Main.wm._shellwm.disconnect(_minimizeSigId);
_minimizeSigId = 0;
Main.wm._shellwm.unblock_signal_handler(_originalMinimizeSigId);
_originalMinimizeSigId = 0;
Main.wm._shellwm.disconnect(_unminimizeSigId);
_unminimizeSigId = 0;
Main.wm._shellwm.unblock_signal_handler(_originalUnminimizeSigId);
_originalUnminimizeSigId = 0;
} else if (!_minimizeSigId) {
_originalMinimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'minimize' });
if (_originalMinimizeSigId) {
Main.wm._shellwm.block_signal_handler(_originalMinimizeSigId);
_minimizeSigId = Main.wm._shellwm.connect('minimize', WindowManagerCommon._minimizeWindow.bind(Main.wm));
}
_originalUnminimizeSigId = GObject.signal_handler_find(Main.wm._shellwm, { signalId: 'unminimize' });
if (_originalUnminimizeSigId) {
Main.wm._shellwm.block_signal_handler(_originalUnminimizeSigId);
_unminimizeSigId = Main.wm._shellwm.connect('unminimize', WindowManagerCommon._unminimizeWindow.bind(Main.wm));
}
}
}
// 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
const WindowManagerCommon = {
_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),
});
// }
},
};

View file

@ -0,0 +1,379 @@
/**
* V-Shell (Vertical Workspaces)
* windowPreview.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { Clutter, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;
const Main = imports.ui.main;
const WindowPreview = imports.ui.windowPreview;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
const shellVersion = _Util.shellVersion;
let _overrides;
const WINDOW_SCALE_TIME = imports.ui.windowPreview.WINDOW_SCALE_TIME;
const WINDOW_ACTIVE_SIZE_INC = imports.ui.windowPreview.WINDOW_ACTIVE_SIZE_INC;
const WINDOW_OVERLAY_FADE_TIME = imports.ui.windowPreview.WINDOW_OVERLAY_FADE_TIME;
const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix;
const ControlsState = imports.ui.overviewControls.ControlsState;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('windowPreviewModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset) {
_overrides = null;
opt = null;
WindowPreview.WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
return;
}
_overrides = new _Util.Overrides();
_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;
}
const WindowPreviewCommon = {
// injection to _init()
after__init() {
const ICON_OVERLAP = 0.7;
if (opt.WIN_PREVIEW_ICON_SIZE < 64) {
this.remove_child(this._icon);
this._icon.destroy();
const tracker = Shell.WindowTracker.get_default();
const app = tracker.get_window_app(this.metaWindow);
this._icon = app.create_icon_texture(opt.WIN_PREVIEW_ICON_SIZE);
this._icon.add_style_class_name('icon-dropshadow');
this._icon.set({
reactive: true,
pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
});
this._icon.add_constraint(new Clutter.BindConstraint({
source: this.windowContainer,
coordinate: Clutter.BindCoordinate.POSITION,
}));
this._icon.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.X_AXIS,
factor: 0.5,
}));
this._icon.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.Y_AXIS,
pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
factor: 1,
}));
this.add_child(this._icon);
if (opt.WIN_PREVIEW_ICON_SIZE < 22) {
// disable app icon
this._icon.hide();
}
this._iconSize = opt.WIN_PREVIEW_ICON_SIZE;
}
const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
const iconOverlap = opt.WIN_PREVIEW_ICON_SIZE * ICON_OVERLAP;
// we cannot get proper title height before it gets to the stage, so 35 is estimated height + spacing
this._title.get_constraints()[1].offset = scaleFactor * (-iconOverlap - 35);
this.set_child_above_sibling(this._title, null);
// 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', () => {
// don't spread windows if user don't use pointer device at this moment
if (global.get_pointer()[0] === opt.showingPointerX || Main.overview._overview._controls._stateAdjustment.value < 1)
return;
const adjustment = this._workspace._background._stateAdjustment;
opt.WORKSPACE_MODE = 1;
_Util.exposeWindows(adjustment, false);
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));
}
// replace click action with custom one
const action = this.get_actions()[0];
const handlerId = GObject.signal_handler_find(action, { signalId: 'clicked' });
if (handlerId)
action.disconnect(handlerId);
action.connect('clicked', act => {
const button = act.get_button();
if (button === Clutter.BUTTON_PRIMARY) {
this._activate();
return Clutter.EVENT_STOP;
} else if (button === Clutter.BUTTON_SECONDARY) {
// this action cancels long-press event and the 'long-press-cancel' event is used by the Shell to actually initiate DnD
// so the dnd initiation needs to be removed
if (this._longPressLater) {
if (shellVersion >= 44) {
const laters = global.compositor.get_laters();
laters.remove(this._longPressLater);
} else {
Meta.later_remove(this._longPressLater);
delete this._longPressLater;
}
}
const tracker = Shell.WindowTracker.get_default();
const appName = tracker.get_window_app(this.metaWindow).get_name();
_Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
});
if (opt.WINDOW_ICON_CLICK_SEARCH) {
const iconClickAction = new Clutter.ClickAction();
iconClickAction.connect('clicked', act => {
if (act.get_button() === Clutter.BUTTON_PRIMARY) {
const tracker = Shell.WindowTracker.get_default();
const appName = tracker.get_window_app(this.metaWindow).get_name();
_Util.activateSearchProvider(`${SEARCH_WINDOWS_PREFIX} ${appName}`);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
});
this._icon.add_action(iconClickAction);
}
},
_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 && 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 = opt.WORKSPACE_MODE;
else if (finalState === 1 || (finalState === 0 && !opt.WORKSPACE_MODE))
return;
}
if (scale === 1) {
this._icon.ease({
duration: 50,
scale_x: scale,
scale_y: scale,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
this._title.ease({
duration: 100,
opacity: 255,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
} else if (this._icon.scale_x !== 0) {
this._icon.set({
scale_x: 0,
scale_y: 0,
});
this._title.opacity = 0;
}
// if titles are in 'always show' mode, we need to add transition between visible/invisible state
// but the transition is quite expensive,
// showing the titles at the end of the transition is good enough and workspace preview transition is much smoother
},
showOverlay(animate) {
if (!this._overlayEnabled)
return;
if (this._overlayShown)
return;
this._overlayShown = true;
if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW)
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()
? [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;
if (opt.ALWAYS_ACTIVATE_SELECTED_WINDOW && Main.overview._overview.controls._stateAdjustment.value < 1) {
this.get_parent()?.set_child_above_sibling(this, null);
this._activateSelected = true;
}
if (!opt.ALWAYS_ACTIVATE_SELECTED_WINDOW)
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,
});
}
},
_onDestroy() {
// workaround for upstream bug - hideOverlay is called after windowPreview is destroyed, from the leave event callback
// hiding the preview now avoids firing the post-mortem leave event
this.hide();
if (this._activateSelected)
this._activate();
this.metaWindow._delegate = null;
this._delegate = null;
if (this._longPressLater) {
if (shellVersion >= 44) {
const laters = global.compositor.get_laters();
laters.remove(this._longPressLater);
delete this._longPressLater;
} else {
Meta.later_remove(this._longPressLater);
delete this._longPressLater;
}
}
if (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);
},
};

View file

@ -0,0 +1,305 @@
/**
* V-Shell (Vertical Workspaces)
* windowSearchProvider.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 -2023
* @license GPL-3.0
*/
'use strict';
const { GLib, Gio, Meta, St, Shell } = imports.gi;
const Main = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Settings = Me.imports.lib.settings;
const _Util = Me.imports.lib.util;
// gettext
const _ = Settings._;
const shellVersion = Settings.shellVersion;
const ModifierType = imports.gi.Clutter.ModifierType;
let windowSearchProvider;
let _enableTimeoutId = 0;
// prefix helps to eliminate results from other search providers
// so it needs to be something less common
// needs to be accessible from vw module
var prefix = 'wq//';
let opt;
const Action = {
NONE: 0,
CLOSE: 1,
CLOSE_ALL: 2,
MOVE_TO_WS: 3,
MOVE_ALL_TO_WS: 4,
};
function getOverviewSearchResult() {
return Main.overview._overview.controls._searchController._searchResults;
}
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
if (!reset && opt.WINDOW_SEARCH_PROVIDER_ENABLED && !windowSearchProvider) {
enable();
} else if (reset || !opt.WINDOW_SEARCH_PROVIDER_ENABLED) {
disable();
opt = null;
}
}
function enable() {
// delay because Fedora had problem to register a new provider soon after Shell restarts
_enableTimeoutId = GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
2000,
() => {
if (!windowSearchProvider) {
windowSearchProvider = new WindowSearchProvider(opt);
getOverviewSearchResult()._registerProvider(
windowSearchProvider
);
}
_enableTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}
);
}
function disable() {
if (windowSearchProvider) {
getOverviewSearchResult()._unregisterProvider(
windowSearchProvider
);
windowSearchProvider = null;
}
if (_enableTimeoutId) {
GLib.source_remove(_enableTimeoutId);
_enableTimeoutId = 0;
}
}
function makeResult(window, i) {
const app = Shell.WindowTracker.get_default().get_window_app(window);
const appName = app ? app.get_name() : 'Unknown';
const windowTitle = window.get_title();
const wsIndex = window.get_workspace().index();
return {
'id': i,
// convert all accented chars to their basic form and lower case for search
'name': `${wsIndex + 1}: ${windowTitle} ${appName}`.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase(),
appName,
windowTitle,
window,
};
}
const closeSelectedRegex = /^\/x!$/;
const closeAllResultsRegex = /^\/xa!$/;
const moveToWsRegex = /^\/m[0-9]+$/;
const moveAllToWsRegex = /^\/ma[0-9]+$/;
const WindowSearchProvider = class WindowSearchProvider {
constructor() {
this.id = `open-windows@${Me.metadata.uuid}`;
this.appInfo = Gio.AppInfo.create_from_commandline('true', _('Open Windows'), null);
this.appInfo.get_description = () => _('List of open windows');
this.appInfo.get_name = () => _('Open Windows');
this.appInfo.get_id = () => this.id;
this.appInfo.get_icon = () => Gio.icon_new_for_string('focus-windows-symbolic');
this.appInfo.should_show = () => true;
this.canLaunchSearch = true;
this.isRemoteProvider = false;
this.action = 0;
}
_getResultSet(terms) {
// do not modify original terms
let termsCopy = [...terms];
// search for terms without prefix
termsCopy[0] = termsCopy[0].replace(prefix, '');
/* if (gOptions.get('searchWindowsCommands')) {
this.action = 0;
this.targetWs = 0;
const lastTerm = terms[terms.length - 1];
if (lastTerm.match(closeSelectedRegex)) {
this.action = Action.CLOSE;
} else if (lastTerm.match(closeAllResultsRegex)) {
this.action = Action.CLOSE_ALL;
} else if (lastTerm.match(moveToWsRegex)) {
this.action = Action.MOVE_TO_WS;
} else if (lastTerm.match(moveAllToWsRegex)) {
this.action = Action.MOVE_ALL_TO_WS;
}
if (this.action) {
terms.pop();
if (this.action === Action.MOVE_TO_WS || this.action === Action.MOVE_ALL_TO_WS) {
this.targetWs = parseInt(lastTerm.replace(/^[^0-9]+/, '')) - 1;
}
} else if (lastTerm.startsWith('/')) {
terms.pop();
}
}*/
const candidates = this.windows;
const _terms = [].concat(termsCopy);
// let match;
const term = _terms.join(' ');
/* match = s => {
return fuzzyMatch(term, s);
}; */
const results = [];
let m;
for (let key in candidates) {
if (opt.SEARCH_FUZZY)
m = _Util.fuzzyMatch(term, candidates[key].name);
else
m = _Util.strictMatch(term, candidates[key].name);
if (m !== -1)
results.push({ weight: m, id: key });
}
results.sort((a, b) => a.weight > b.weight);
const currentWs = global.workspace_manager.get_active_workspace_index();
// prefer current workspace
results.sort((a, b) => (this.windows[a.id].window.get_workspace().index() !== currentWs) && (this.windows[b.id].window.get_workspace().index() === currentWs));
results.sort((a, b) => (_terms !== ' ') && (a.weight > 0 && b.weight === 0));
this.resultIds = results.map(item => item.id);
return this.resultIds;
}
getResultMetas(resultIds, callback = null) {
const metas = resultIds.map(id => this.getResultMeta(id));
if (shellVersion >= 43)
return new Promise(resolve => resolve(metas));
else
callback(metas);
return null;
}
getResultMeta(resultId) {
const result = this.windows[resultId];
const wsIndex = result.window.get_workspace().index();
const app = Shell.WindowTracker.get_default().get_window_app(result.window);
return {
'id': resultId,
'name': `${wsIndex + 1}: ${result.windowTitle}`,
'description': result.appName,
'createIcon': size => {
return app
? app.create_icon_texture(size)
: new St.Icon({ icon_name: 'icon-missing', icon_size: size });
},
};
}
launchSearch(/* terms, timeStamp*/) {
}
activateResult(resultId/* , terms, timeStamp*/) {
const isCtrlPressed = _Util.isCtrlPressed();
const isShiftPressed = _Util.isShiftPressed();
this.action = 0;
this.targetWs = 0;
this.targetWs = global.workspaceManager.get_active_workspace().index() + 1;
if (isShiftPressed && !isCtrlPressed)
this.action = Action.MOVE_TO_WS;
else if (isShiftPressed && isCtrlPressed)
this.action = Action.MOVE_ALL_TO_WS;
if (!this.action) {
const result = this.windows[resultId];
Main.activateWindow(result.window);
return;
}
switch (this.action) {
case Action.CLOSE:
this._closeWindows([resultId]);
break;
case Action.CLOSE_ALL:
this._closeWindows(this.resultIds);
break;
case Action.MOVE_TO_WS:
this._moveWindowsToWs(resultId, [resultId]);
break;
case Action.MOVE_ALL_TO_WS:
this._moveWindowsToWs(resultId, this.resultIds);
break;
}
}
_closeWindows(ids) {
let time = global.get_current_time();
for (let i = 0; i < ids.length; i++)
this.windows[ids[i]].window.delete(time + i);
Main.notify('Window Search Provider', `Closed ${ids.length} windows.`);
}
_moveWindowsToWs(selectedId, resultIds) {
const workspace = global.workspaceManager.get_active_workspace();
for (let i = 0; i < resultIds.length; i++)
this.windows[resultIds[i]].window.change_workspace(workspace);
const selectedWin = this.windows[selectedId].window;
selectedWin.activate_with_workspace(global.get_current_time(), workspace);
}
getInitialResultSet(terms, callback/* , cancellable = null*/) {
// In GS 43 callback arg has been removed
/* if (shellVersion >= 43)
cancellable = callback;*/
let windows;
this.windows = windows = {};
global.display.get_tab_list(Meta.TabList.NORMAL, null).filter(w => w.get_workspace() !== null).map(
(v, i) => {
windows[`${i}-${v.get_id()}`] = makeResult(v, `${i}-${v.get_id()}`);
return windows[`${i}-${v.get_id()}`];
}
);
if (shellVersion >= 43)
return new Promise(resolve => resolve(this._getResultSet(terms)));
else
callback(this._getResultSet(terms));
return null;
}
filterResults(results /* , maxResults*/) {
// return results.slice(0, maxResults);
return results;
}
getSubsearchResultSet(previousResults, terms, callback/* , cancellable*/) {
// if we return previous results, quick typers get non-actual results
callback(this._getResultSet(terms));
}
/* createResultObject(resultMeta) {
const app = Shell.WindowTracker.get_default().get_window_app(resultMeta.id);
return new AppIcon(app);
}*/
};

View file

@ -0,0 +1,152 @@
/**
* V-Shell (Vertical Workspaces)
* workspace.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { St, Graphene } = imports.gi;
const Main = imports.ui.main;
const Util = imports.misc.util;
const Workspace = imports.ui.workspace;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let _overrides;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('workspaceModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset) {
Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95;
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
_overrides.addOverride('WorkspaceBackground', Workspace.WorkspaceBackground.prototype, WorkspaceBackground);
// fix overlay base for Vertical Workspaces
_overrides.addOverride('WorkspaceLayout', Workspace.WorkspaceLayout.prototype, WorkspaceLayout);
}
// workaround for upstream bug (that is not that invisible in default shell)
// smaller window cannot be scaled below 0.95 (WINDOW_PREVIEW_MAXIMUM_SCALE)
// when its target scale for exposed windows view (workspace state 1) is bigger than the scale needed for ws state 0.
// in workspace state 0 where windows are not spread and window scale should follow workspace scale,
// this window follows proper top left corner position, but doesn't scale with the workspace
// so it looks bad and the window can exceed border of the workspace
// extremely annoying in OVERVIEW_MODE 1 with single smaller window on the workspace, also affects appGrid transition animation
// disadvantage of following workaround - the WINDOW_PREVIEW_MAXIMUM_SCALE value is common for every workspace,
// on multi-monitor system can be visible unwanted scaling of windows on workspace in WORKSPACE_MODE 0 (windows not spread)
// when leaving overview while any other workspace is in the WORKSPACE_MODE 1.
const WorkspaceLayout = {
// injection to _init()
after__init() {
if (opt.OVERVIEW_MODE === 1) {
this._stateAdjustment.connect('notify::value', () => {
// scale 0.1 for window state 0 just needs to be smaller then possible scale of any window in spread view
const scale = this._stateAdjustment.value ? 0.95 : 0.1;
if (scale !== this.WINDOW_PREVIEW_MAXIMUM_SCALE) {
this.WINDOW_PREVIEW_MAXIMUM_SCALE = scale;
// when transition to ws state 1 (WINDOW_PICKER) begins, replace the constant with the original one
Workspace.WINDOW_PREVIEW_MAXIMUM_SCALE = scale;
// and force recalculation of the target layout, so the transition will be smooth
this._needsLayout = true;
}
});
}
},
// this fixes wrong size and position calculation of window clones while moving overview to the next (+1) workspace if vertical ws orientation is enabled in GS
_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();
const oversize = Math.max(topOversize, bottomOversize, leftOversize, rightOversize);
if (rowSpacing !== null)
rowSpacing += oversize;
if (colSpacing !== null)
colSpacing += oversize;
if (containerBox) {
const vertical = global.workspaceManager.layout_rows === -1;
const monitor = Main.layoutManager.monitors[this._monitorIndex];
const bottomPoint = new Graphene.Point3D();
if (vertical)
bottomPoint.x = containerBox.x2;
else
bottomPoint.y = containerBox.y2;
const transformedBottomPoint =
this._container.apply_transform_to_point(bottomPoint);
const bottomFreeSpace = vertical
? (monitor.x + monitor.height) - transformedBottomPoint.x
: (monitor.y + monitor.height) - transformedBottomPoint.y;
const [, bottomOverlap] = window.overlapHeights();
if ((bottomOverlap + oversize) > bottomFreeSpace && !vertical)
containerBox.y2 -= (bottomOverlap + oversize) - bottomFreeSpace;
}
return [rowSpacing, colSpacing, containerBox];
},
};
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);
},
};

View file

@ -0,0 +1,184 @@
/**
* V-Shell (Vertical Workspaces)
* workspacesAnimation.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const Main = imports.ui.main;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const WorkspaceAnimation = imports.ui.workspaceAnimation;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
// first reference to constant defined using const in other module returns undefined, the MonitorGroup const will remain empty and unused
let MonitorGroupDummy = WorkspaceAnimation.MonitorGroup;
MonitorGroupDummy = null;
let _origBaseDistance;
let _wsAnimationSwipeBeginId;
let _wsAnimationSwipeUpdateId;
let _wsAnimationSwipeEndId;
let _overrides;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('workspaceAnimationModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset || !moduleEnabled) {
_connectWsAnimationSwipeTracker(true);
_overrideMonitorGroupProperty(true);
_overrides = null;
opt = null;
return;
}
if (opt.STATIC_WS_SWITCHER_BG) {
_overrides = new _Util.Overrides();
_overrideMonitorGroupProperty();
_overrides.addOverride('WorkspaceAnimationMonitorGroup', WorkspaceAnimation.MonitorGroup.prototype, MonitorGroup);
}
_connectWsAnimationSwipeTracker();
}
// remove spacing between workspaces during transition to remove flashing wallpaper between workspaces with maximized windows
function _overrideMonitorGroupProperty(reset = false) {
if (!_origBaseDistance)
_origBaseDistance = Object.getOwnPropertyDescriptor(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance').get;
let getter;
if (reset) {
if (_origBaseDistance)
getter = { get: _origBaseDistance };
} else {
getter = {
get() {
// const spacing = 100 * imports.gi.St.ThemeContext.get_for_stage(global.stage).scale_factor;
const spacing = 0;
if (global.workspace_manager.layout_rows === -1)
return this._monitor.height + spacing + (opt.PANEL_MODE ? Main.panel.height : 0); // compensation for hidden panel
else
return this._monitor.width + spacing;
},
};
}
if (getter)
Object.defineProperty(WorkspaceAnimation.MonitorGroup.prototype, 'baseDistance', getter);
}
const MonitorGroup = {
// injection to _init()
after__init() {
// 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
const stickyGroup = this.get_children()[1];
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._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())) { //* && !w.is_on_all_workspaces()*/) {
// 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;
});
});
},
};
function _connectWsAnimationSwipeTracker(reset = false) {
if (reset) {
if (_wsAnimationSwipeBeginId) {
Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeBeginId);
_wsAnimationSwipeBeginId = 0;
}
if (_wsAnimationSwipeEndId) {
Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeEndId);
_wsAnimationSwipeEndId = 0;
}
} else if (!_wsAnimationSwipeBeginId) {
// display ws switcher popup when gesture begins and connect progress
_wsAnimationSwipeBeginId = Main.wm._workspaceAnimation._swipeTracker.connect('begin', () => _connectWsAnimationProgress(true));
// we want to be sure that popup with the final ws index show up when gesture ends
_wsAnimationSwipeEndId = Main.wm._workspaceAnimation._swipeTracker.connect('end', (tracker, duration, endProgress) => _connectWsAnimationProgress(false, endProgress));
}
}
function _connectWsAnimationProgress(connect, endProgress = null) {
if (Main.overview.visible)
return;
if (connect && !_wsAnimationSwipeUpdateId) {
_wsAnimationSwipeUpdateId = Main.wm._workspaceAnimation._swipeTracker.connect('update', (tracker, progress) => _showWsSwitcherPopup(progress));
} else if (!connect && _wsAnimationSwipeUpdateId) {
Main.wm._workspaceAnimation._swipeTracker.disconnect(_wsAnimationSwipeUpdateId);
_wsAnimationSwipeUpdateId = 0;
_showWsSwitcherPopup(Math.round(endProgress));
}
}
function _showWsSwitcherPopup(progress) {
if (Main.overview.visible)
return;
const wsIndex = Math.round(progress);
if (Main.wm._workspaceSwitcherPopup === null) {
Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
Main.wm._workspaceSwitcherPopup.connect('destroy', () => {
Main.wm._workspaceSwitcherPopup = null;
});
}
Main.wm._workspaceSwitcherPopup.display(wsIndex);
}

View file

@ -0,0 +1,90 @@
/**
* V-Shell (Vertical Workspaces)
* workspacesSwitcherPopup.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const Main = imports.ui.main;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const _Util = Me.imports.lib.util;
let _overrides;
let opt;
let _firstRun = true;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
const moduleEnabled = opt.get('workspaceSwitcherPopupModule', true);
reset = reset || !moduleEnabled;
// don't even touch this module if disabled
if (_firstRun && reset)
return;
_firstRun = false;
if (_overrides)
_overrides.removeAll();
if (reset) {
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
const enabled = global.settings.get_strv('enabled-extensions');
const allowWsPopupInjection = !(enabled.includes('workspace-switcher-manager@G-dH.github.com') || enabled.includes('WsSwitcherPopupManager@G-dH.github.com-dev'));
if (allowWsPopupInjection) { // 1-VERTICAL, 0-HORIZONTAL
_overrides.addOverride('WorkspaceSwitcherPopup', WorkspaceSwitcherPopup.WorkspaceSwitcherPopup.prototype, WorkspaceSwitcherPopupOverride);
}
}
const WorkspaceSwitcherPopupOverride = {
// injection to _init()
after__init() {
if (opt.ORIENTATION) { // 1-VERTICAL, 0-HORIZONTAL
this._list.vertical = true;
}
this._list.set_style('margin: 0;');
this.remove_constraint(this.get_constraints()[0]);
},
// 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 = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);*/
workArea = global.display.get_monitor_geometry(Main.layoutManager.primaryIndex);
} else {
// workArea = Main.layoutManager.getWorkAreaForMonitor(global.display.get_current_monitor());
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);
},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,934 @@
/**
* V-Shell (Vertical Workspaces)
* workspacesView.js
*
* @author GdH <G-dH@github.com>
* @copyright 2022 - 2023
* @license GPL-3.0
*
*/
'use strict';
const { GObject, Clutter, Meta, St } = imports.gi;
const Main = imports.ui.main;
const Util = imports.misc.util;
const WorkspacesView = imports.ui.workspacesView;
// first reference to constant defined using const in other module returns undefined, the SecondaryMonitorDisplay const will remain empty and unused
const SecondaryMonitorDisplay = WorkspacesView.SecondaryMonitorDisplay;
const ControlsState = imports.ui.overviewControls.ControlsState;
const FitMode = imports.ui.workspacesView.FitMode;
const SIDE_CONTROLS_ANIMATION_TIME = imports.ui.overview.ANIMATION_TIME;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const SEARCH_WINDOWS_PREFIX = Me.imports.lib.windowSearchProvider.prefix;
const SEARCH_RECENT_FILES_PREFIX = Me.imports.lib.recentFilesSearchProvider.prefix;
const _Util = Me.imports.lib.util;
let _overrides;
let opt;
function update(reset = false) {
opt = Me.imports.lib.settings.opt;
opt.DESKTOP_CUBE_ENABLED = Main.extensionManager._enabledExtensions.includes('desktop-cube@schneegans.github.com');
const cubeSupported = opt.DESKTOP_CUBE_ENABLED && !opt.ORIENTATION && !opt.OVERVIEW_MODE;
// if desktop cube extension is enabled while V-Shell is loaded, removeAll() would override its code
if (_overrides && !cubeSupported) {
_overrides.removeAll();
global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, 1, -1);
}
if (reset) {
_overrides = null;
opt = null;
return;
}
_overrides = new _Util.Overrides();
if (!cubeSupported)
_overrides.addOverride('WorkspacesView', WorkspacesView.WorkspacesView.prototype, WorkspacesViewCommon);
_overrides.addOverride('WorkspacesDisplay', WorkspacesView.WorkspacesDisplay.prototype, WorkspacesDisplay);
_overrides.addOverride('ExtraWorkspaceView', WorkspacesView.ExtraWorkspaceView.prototype, ExtraWorkspaceView);
if (opt.ORIENTATION) {
// switch internal workspace orientation in GS
global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT, false, -1, 1);
_overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayVertical);
} else {
_overrides.addOverride('SecondaryMonitorDisplay', WorkspacesView.SecondaryMonitorDisplay.prototype, SecondaryMonitorDisplayHorizontal);
}
}
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() {
// replaced in _updateWorkspacesState
/* let workspaceManager = global.workspace_manager;
let active = workspaceManager.get_active_workspace_index();
const fitMode = this._fitModeAdjustment.value;
const singleFitMode = fitMode === FitMode.SINGLE;
for (let w = 0; w < this._workspaces.length; w++) {
let workspace = this._workspaces[w];
if (this._animating || this._gestureActive || !singleFitMode)
workspace.show();
else
workspace.visible = Math.abs(w - active) <= opt.NUMBER_OF_VISIBLE_NEIGHBORS;
}*/
},
// 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;
// define the transition values here to save time in each ws
let scaleX, scaleY;
if (opt.ORIENTATION) { // vertical 1 / horizontal 0
scaleX = 1;
scaleY = 0.1;
} else {
scaleX = 0.1;
scaleY = 1;
}
const wsScrollProgress = adj.value % 1;
const secondaryMonitor = this._monitorIndex !== global.display.get_primary_monitor();
const blockSecondaryAppGrid = opt.OVERVIEW_MODE && currentState > 1;
// Hide inactive workspaces
this._workspaces.forEach((w, index) => {
if (!(blockSecondaryAppGrid && secondaryMonitor))
w.stateAdjustment.value = workspaceMode;
const distanceToCurrentWorkspace = Math.abs(adj.value - index);
const scaleProgress = 1 - Math.clamp(distanceToCurrentWorkspace, 0, 1);
// 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 = (this._animating && wsScrollProgress && distanceToCurrentWorkspace <= (opt.NUMBER_OF_VISIBLE_NEIGHBORS + 1)) || scaleProgress === 1 ||
(opt.WORKSPACE_MAX_SPACING > 340 && 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) ||
(opt.WORKSPACE_MAX_SPACING < 340 && 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, scaling animation will make impression that they move in from outside the monitor
if (!w.visible && distanceToCurrentWorkspace === 1 && initialState === ControlsState.APP_GRID && currentState === ControlsState.WINDOW_PICKER) {
w.scale_x = scaleX;
w.scale_y = scaleY;
w.visible = true;
w.ease({
duration: 100,
scale_x: 1,
scale_y: 1,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
});
} else if (!w.visible && distanceToCurrentWorkspace <= opt.NUMBER_OF_VISIBLE_NEIGHBORS && currentState === ControlsState.WINDOW_PICKER) {
w.set({
scale_x: 1,
scale_y: 1,
});
}
// force ws preview bg corner radiuses where GS doesn't do it
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;
});
},
};
// SecondaryMonitorDisplay Vertical
const SecondaryMonitorDisplayVertical = {
_getThumbnailParamsForState(state) {
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 * (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 };
},
_getThumbnailsWidth(box, spacing) {
if (opt.SEC_WS_TMB_HIDDEN)
return 0;
const [width, height] = box.get_size();
const { expandFraction } = this._thumbnails;
const [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(height - 2 * spacing);
let scaledWidth;
if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED)
scaledWidth = ((height - Main.panel.height) * opt.SEC_MAX_THUMBNAIL_SCALE) * (width / height);
else
scaledWidth = width * opt.SEC_MAX_THUMBNAIL_SCALE;
return Math.min(
thumbnailsWidth * expandFraction,
Math.round(scaledWidth));
},
_getWorkspacesBoxForState(state, box, padding, thumbnailsWidth, spacing) {
// const { ControlsState } = OverviewControls;
const workspaceBox = box.copy();
const [width, height] = workspaceBox.get_size();
let wWidth, wHeight, wsbX, wsbY, offset, yShift;
switch (state) {
case ControlsState.HIDDEN:
break;
case ControlsState.WINDOW_PICKER:
case ControlsState.APP_GRID:
if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)
break;
yShift = 0;
if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) {
if (opt.PANEL_POSITION_TOP)
yShift = Main.panel.height;
else
yShift = -Main.panel.height;
}
wWidth = width - thumbnailsWidth - 5 * spacing;
wHeight = Math.min(wWidth / (width / height) - Math.abs(yShift), height - 4 * spacing);
wWidth = Math.round(wWidth * opt.SEC_WS_PREVIEW_SCALE);
wHeight = Math.round(wHeight * opt.SEC_WS_PREVIEW_SCALE);
offset = Math.round(width - thumbnailsWidth - wWidth) / 2;
if (opt.SEC_WS_TMB_LEFT)
wsbX = thumbnailsWidth + offset;
else
wsbX = offset;
wsbY = Math.round((height - wHeight - Math.abs(yShift)) / 2 + yShift);
workspaceBox.set_origin(wsbX, wsbY);
workspaceBox.set_size(wWidth, wHeight);
break;
}
return workspaceBox;
},
vfunc_allocate(box) {
this.set_allocation(box);
const themeNode = this.get_theme_node();
const contentBox = themeNode.get_content_box(box);
const [width, height] = contentBox.get_size();
const { expandFraction } = this._thumbnails;
const spacing = themeNode.get_length('spacing') * expandFraction;
const padding = Math.round(0.1 * height);
let thumbnailsWidth = this._getThumbnailsWidth(contentBox, spacing);
let [, thumbnailsHeight] = this._thumbnails.get_preferred_custom_height(thumbnailsWidth);
thumbnailsHeight = Math.min(thumbnailsHeight, height - 2 * spacing);
this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN;
if (this._thumbnails.visible) {
let wsTmbX;
if (opt.SEC_WS_TMB_LEFT) { // left
wsTmbX = Math.round(spacing / 4);
this._thumbnails._positionLeft = true;
} else {
wsTmbX = Math.round(width - spacing / 4 - thumbnailsWidth);
this._thumbnails._positionLeft = false;
}
const childBox = new Clutter.ActorBox();
const availSpace = height - thumbnailsHeight - 2 * spacing;
let wsTmbY = availSpace / 2;
wsTmbY -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbY - spacing;
childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY));
childBox.set_size(thumbnailsWidth, thumbnailsHeight);
this._thumbnails.allocate(childBox);
}
const {
currentState, initialState, finalState, transitioning, progress,
} = this._overviewAdjustment.getStateTransitionParams();
let workspacesBox;
const workspaceParams = [contentBox, padding, thumbnailsWidth, 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: SIDE_CONTROLS_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;
},
};
// SecondaryMonitorDisplay Horizontal
const SecondaryMonitorDisplayHorizontal = {
_getThumbnailParamsForState(state) {
// const { ControlsState } = OverviewControls;
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 * (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, padding, thumbnailsHeight, spacing) {
// const { ControlsState } = OverviewControls;
const workspaceBox = box.copy();
const [width, height] = workspaceBox.get_size();
let wWidth, wHeight, wsbX, wsbY, offset, yShift;
switch (state) {
case ControlsState.HIDDEN:
break;
case ControlsState.WINDOW_PICKER:
case ControlsState.APP_GRID:
if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE)
break;
yShift = 0;
if (opt.SEC_WS_PREVIEW_SHIFT && !opt.PANEL_DISABLED) {
if (opt.PANEL_POSITION_TOP)
yShift = Main.panel.height;
else
yShift = -Main.panel.height;
}
wHeight = height - Math.abs(yShift) - (thumbnailsHeight ? thumbnailsHeight + 4 * spacing : padding);
wWidth = Math.min(wHeight * (width / height), width - 5 * spacing);
wWidth = Math.round(wWidth * opt.SEC_WS_PREVIEW_SCALE);
wHeight = Math.round(wHeight * opt.SEC_WS_PREVIEW_SCALE);
offset = Math.round((height - thumbnailsHeight - wHeight - Math.abs(yShift)) / 2);
if (opt.SEC_WS_TMB_TOP)
wsbY = thumbnailsHeight + offset;
else
wsbY = offset;
wsbY += yShift;
wsbX = Math.round((width - wWidth) / 2);
workspaceBox.set_origin(wsbX, wsbY);
workspaceBox.set_size(wWidth, wHeight);
break;
}
return workspaceBox;
},
_getThumbnailsHeight(box) {
if (opt.SEC_WS_TMB_HIDDEN)
return 0;
const [width, height] = box.get_size();
const { expandFraction } = this._thumbnails;
const [thumbnailsHeight] = this._thumbnails.get_preferred_height(width);
return Math.min(
thumbnailsHeight * expandFraction,
height * opt.SEC_MAX_THUMBNAIL_SCALE);
},
vfunc_allocate(box) {
this.set_allocation(box);
const themeNode = this.get_theme_node();
const contentBox = themeNode.get_content_box(box);
const [width, height] = contentBox.get_size();
const { expandFraction } = this._thumbnails;
const spacing = themeNode.get_length('spacing') * expandFraction;
const padding = Math.round(0.1 * height);
let thumbnailsHeight = this._getThumbnailsHeight(contentBox);
let [, thumbnailsWidth] = this._thumbnails.get_preferred_custom_width(thumbnailsHeight);
thumbnailsWidth = Math.min(thumbnailsWidth, width - 2 * spacing);
this._thumbnails.visible = !opt.SEC_WS_TMB_HIDDEN;
if (this._thumbnails.visible) {
let wsTmbY;
if (opt.SEC_WS_TMB_TOP)
wsTmbY = Math.round(spacing / 4);
else
wsTmbY = Math.round(height - spacing / 4 - thumbnailsHeight);
const childBox = new Clutter.ActorBox();
const availSpace = width - thumbnailsWidth - 2 * spacing;
let wsTmbX = availSpace / 2;
wsTmbX -= opt.SEC_WS_TMB_POSITION_ADJUSTMENT * wsTmbX - spacing;
childBox.set_origin(Math.round(wsTmbX), Math.round(wsTmbY));
childBox.set_size(thumbnailsWidth, thumbnailsHeight);
this._thumbnails.allocate(childBox);
}
const {
currentState, initialState, finalState, transitioning, progress,
} = this._overviewAdjustment.getStateTransitionParams();
let workspacesBox;
const workspaceParams = [contentBox, padding, thumbnailsHeight, 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 ExtraWorkspaceView = {
_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;
},
};
const WorkspacesDisplay = {
_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_actor(view);
}
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 (_Util.isShiftPressed()) {
let direction = _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) {
_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;
}
}
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 (_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 (_Util.isCtrlPressed() && _Util.isShiftPressed()) {
_Util.openPreferences();
} else if (_Util.isAltPressed()) {
Main.ctrlAltTabManager._items.forEach(i => {
if (i.sortGroup === 1 && i.name === 'Dash')
Main.ctrlAltTabManager.focusGroup(i);
});
} else if (opt.RECENT_FILES_SEARCH_PROVIDER_ENABLED && _Util.isCtrlPressed()) {
_Util.activateSearchProvider(SEARCH_RECENT_FILES_PREFIX);
} else if (opt.WINDOW_SEARCH_PROVIDER_ENABLED) {
_Util.activateSearchProvider(SEARCH_WINDOWS_PREFIX);
}
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._overview._controls._searchController.searchActive) {
Main.overview.searchEntry.grab_key_focus();
} else if (opt.OVERVIEW_MODE2 && !opt.WORKSPACE_MODE && state === 1) {
// expose windows by "clicking" on ws thumbnail
// in this case overview stateAdjustment will be used for transition
Main.overview._overview.controls._thumbnailsBox._activateThumbnailAtPoint(0, 0, global.get_current_time(), true);
Main.ctrlAltTabManager._items.forEach(i => {
if (i.sortGroup === 1 && i.name === 'Windows')
Main.ctrlAltTabManager.focusGroup(i);
});
} else if (opt.OVERVIEW_MODE && !opt.WORKSPACE_MODE && state === 1) {
// expose windows for OVERVIEW_MODE 1
const adjustment = this._workspacesViews[0]._workspaces[global.workspace_manager.get_active_workspace().index()]._background._stateAdjustment;
opt.WORKSPACE_MODE = 1;
_Util.exposeWindows(adjustment, true);
} else {
if (state === 2)
return Clutter.EVENT_PROPAGATE;
Main.ctrlAltTabManager._items.forEach(i => {
if (i.sortGroup === 1 && i.name === 'Windows')
Main.ctrlAltTabManager.focusGroup(i);
});
}
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 (_Util.isShiftPressed()) {
let direction;
if (which === Meta.MotionDirection.UP || which === Meta.MotionDirection.LEFT)
direction = -1;
else if (which === Meta.MotionDirection.DOWN || which === Meta.MotionDirection.RIGHT)
direction = 1;
if (direction)
_Util.reorderWorkspace(direction);
// 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;
}
}