211 lines
7.1 KiB
JavaScript
Executable File
211 lines
7.1 KiB
JavaScript
Executable File
import {Atk, Clutter} from './dependencies/gi.js';
|
|
|
|
import {
|
|
Main,
|
|
SearchController,
|
|
Workspace,
|
|
WorkspaceThumbnail,
|
|
} from './dependencies/shell/ui.js';
|
|
|
|
import {Utils} from './imports.js';
|
|
|
|
export class AppSpread {
|
|
constructor() {
|
|
this.app = null;
|
|
this.supported = true;
|
|
this.windows = [];
|
|
|
|
// fail early and do nothing, if mandatory gnome shell functions are missing
|
|
if (Main.overview.isDummy ||
|
|
!Workspace?.Workspace?.prototype._isOverviewWindow ||
|
|
!WorkspaceThumbnail?.WorkspaceThumbnail?.prototype._isOverviewWindow) {
|
|
log('Dash to dock: Unable to temporarily replace shell functions ' +
|
|
'for app spread - using previews instead');
|
|
this.supported = false;
|
|
return;
|
|
}
|
|
|
|
this._signalHandlers = new Utils.GlobalSignalsHandler();
|
|
this._methodInjections = new Utils.InjectionsHandler();
|
|
this._vfuncInjections = new Utils.VFuncInjectionsHandler();
|
|
}
|
|
|
|
get isInAppSpread() {
|
|
return !!this.app;
|
|
}
|
|
|
|
destroy() {
|
|
if (!this.supported)
|
|
return;
|
|
this._hideAppSpread();
|
|
this._signalHandlers.destroy();
|
|
this._methodInjections.destroy();
|
|
this._vfuncInjections.destroy();
|
|
}
|
|
|
|
toggle(app) {
|
|
const newApp = this.app !== app;
|
|
if (this.app)
|
|
Main.overview.hide(); // also triggers hook 'hidden'
|
|
|
|
if (app && newApp)
|
|
this._showAppSpread(app);
|
|
}
|
|
|
|
_updateWindows() {
|
|
this.windows = this.app.get_windows();
|
|
}
|
|
|
|
_restoreDefaultWindows() {
|
|
const {workspaceManager} = global;
|
|
|
|
for (let i = 0; i < workspaceManager.nWorkspaces; i++) {
|
|
const metaWorkspace = workspaceManager.get_workspace_by_index(i);
|
|
metaWorkspace.list_windows().forEach(w => metaWorkspace.emit('window-added', w));
|
|
}
|
|
}
|
|
|
|
_filterWindows() {
|
|
const {workspaceManager} = global;
|
|
|
|
for (let i = 0; i < workspaceManager.nWorkspaces; i++) {
|
|
const metaWorkspace = workspaceManager.get_workspace_by_index(i);
|
|
metaWorkspace.list_windows().filter(w => !this.windows.includes(w)).forEach(
|
|
w => metaWorkspace.emit('window-removed', w));
|
|
}
|
|
}
|
|
|
|
_restoreDefaultOverview() {
|
|
this._hideAppSpread();
|
|
this._restoreDefaultWindows();
|
|
}
|
|
|
|
_showAppSpread(app) {
|
|
if (this.isInAppSpread)
|
|
return;
|
|
|
|
// Checked in overview "hide" event handler _hideAppSpread
|
|
this.app = app;
|
|
this._updateWindows();
|
|
|
|
// we need to hook into overview 'hidden' like this, in case app spread
|
|
// overview is hidden by choosing another app it should then do its
|
|
// cleanup too
|
|
this._signalHandlers.add(Main.overview, 'hidden', () => this._hideAppSpread());
|
|
|
|
const appSpread = this;
|
|
this._methodInjections.add([
|
|
// Filter workspaces to only show current app windows
|
|
Workspace.Workspace.prototype, '_isOverviewWindow',
|
|
function (originalMethod, window) {
|
|
/* eslint-disable no-invalid-this */
|
|
const isOverviewWindow = originalMethod.call(this, window);
|
|
return isOverviewWindow && appSpread.windows.includes(window);
|
|
/* eslint-enable no-invalid-this */
|
|
},
|
|
],
|
|
[
|
|
// Filter thumbnails to only show current app windows
|
|
WorkspaceThumbnail.WorkspaceThumbnail.prototype, '_isOverviewWindow',
|
|
function (originalMethod, windowActor) {
|
|
/* eslint-disable no-invalid-this */
|
|
const isOverviewWindow = originalMethod.call(this, windowActor);
|
|
return isOverviewWindow && appSpread.windows.includes(windowActor.metaWindow);
|
|
/* eslint-enable no-invalid-this */
|
|
},
|
|
]);
|
|
|
|
const activitiesButton = Main.panel.statusArea?.activities;
|
|
|
|
if (activitiesButton) {
|
|
this._signalHandlers.add(Main.overview, 'showing', () => {
|
|
activitiesButton.remove_style_pseudo_class('overview');
|
|
activitiesButton.remove_accessible_state(Atk.StateType.CHECKED);
|
|
});
|
|
|
|
this._vfuncInjections.add([
|
|
activitiesButton.constructor.prototype,
|
|
'event',
|
|
function (event) {
|
|
if (event.type() === Clutter.EventType.TOUCH_END ||
|
|
event.type() === Clutter.EventType.BUTTON_RELEASE) {
|
|
if (Main.overview.shouldToggleByCornerOrButton())
|
|
appSpread._restoreDefaultOverview();
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
},
|
|
],
|
|
[
|
|
activitiesButton.constructor.prototype,
|
|
'key_release_event',
|
|
function (keyEvent) {
|
|
const {keyval} = keyEvent;
|
|
if (keyval === Clutter.KEY_Return || keyval === Clutter.KEY_space) {
|
|
if (Main.overview.shouldToggleByCornerOrButton())
|
|
appSpread._restoreDefaultOverview();
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
},
|
|
]);
|
|
}
|
|
|
|
this._signalHandlers.add(Main.overview.dash.showAppsButton, 'notify::checked', () => {
|
|
if (Main.overview.dash.showAppsButton.checked)
|
|
this._restoreDefaultOverview();
|
|
});
|
|
|
|
// If closing windows in AppSpread, and only one window left:
|
|
// exit app spread and focus remaining window (handled in _hideAppSpread)
|
|
this._signalHandlers.add(this.app, 'windows-changed', () => {
|
|
this._updateWindows();
|
|
|
|
if (this.windows.length <= 1)
|
|
Main.overview.hide();
|
|
});
|
|
|
|
this._disableSearch();
|
|
|
|
Main.overview.show();
|
|
}
|
|
|
|
_hideAppSpread() {
|
|
if (!this.isInAppSpread)
|
|
return;
|
|
|
|
if (Main.overview.visible) {
|
|
Main.panel.statusArea?.activities.add_style_pseudo_class('overview');
|
|
Main.panel.statusArea?.activities.add_accessible_state(Atk.StateType.CHECKED);
|
|
}
|
|
|
|
// Restore original behaviour
|
|
this.app = null;
|
|
this._enableSearch();
|
|
this._methodInjections.clear();
|
|
this._signalHandlers.clear();
|
|
this._vfuncInjections.clear();
|
|
|
|
// Check reason for leaving AppSpread was closing app windows and only one window left...
|
|
if (this.windows.length === 1)
|
|
Main.activateWindow(this.windows[0]);
|
|
|
|
this.windows = [];
|
|
}
|
|
|
|
_disableSearch() {
|
|
if (!SearchController.SearchController.prototype._shouldTriggerSearch)
|
|
return;
|
|
|
|
if (Main.overview.searchEntry)
|
|
Main.overview.searchEntry.opacity = 0;
|
|
|
|
this._methodInjections.add(
|
|
SearchController.SearchController.prototype,
|
|
'_shouldTriggerSearch', () => false);
|
|
}
|
|
|
|
_enableSearch() {
|
|
if (Main.overview.searchEntry)
|
|
Main.overview.searchEntry.opacity = 255;
|
|
}
|
|
}
|