Adding vertical-workspaces version 23.5 [f987be5].
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
279d497ce7
commit
f6b3c6d29a
38 changed files with 11161 additions and 0 deletions
358
extensions/vertical-workspaces/windowSearchProvider.js
Normal file
358
extensions/vertical-workspaces/windowSearchProvider.js
Normal file
|
@ -0,0 +1,358 @@
|
|||
/**
|
||||
* Vertical Workspaces
|
||||
* windowSearchProvider.js
|
||||
*
|
||||
* @author GdH <G-dH@github.com>
|
||||
* @copyright 2022 -2023
|
||||
* @license GPL-3.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { GLib, GObject, Gio, Gtk, Meta, St, Shell } = imports.gi;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
const Me = ExtensionUtils.getCurrentExtension();
|
||||
const Settings = Me.imports.settings;
|
||||
const _ = Me.imports.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 init() {
|
||||
}
|
||||
|
||||
function getOverviewSearchResult() {
|
||||
return Main.overview._overview.controls._searchController._searchResults;
|
||||
}
|
||||
|
||||
function update(reset = false) {
|
||||
opt = Me.imports.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 == null) {
|
||||
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 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.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 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': appName,
|
||||
'windowTitle': windowTitle,
|
||||
'window': window
|
||||
}
|
||||
}
|
||||
|
||||
const closeSelectedRegex = /^\/x!$/;
|
||||
const closeAllResultsRegex = /^\/xa!$/;
|
||||
const moveToWsRegex = /^\/m[0-9]+$/;
|
||||
const moveAllToWsRegex = /^\/ma[0-9]+$/;
|
||||
|
||||
var WindowSearchProvider = class WindowSearchProvider {
|
||||
constructor(gOptions) {
|
||||
this._gOptions = gOptions;
|
||||
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 = () => `${Me.metadata.uuid} ${this.title}`;
|
||||
this.appInfo.get_icon = () => Gio.icon_new_for_string('focus-windows-symbolic');
|
||||
this.appInfo.should_show = () => true;
|
||||
this.title = 'Window Search Provider',
|
||||
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 (this._gOptions.get('searchFuzzy')) {
|
||||
m = fuzzyMatch(term, candidates[key].name);
|
||||
} else {
|
||||
m = 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 [,,state] = global.get_pointer();
|
||||
|
||||
const isCtrlPressed = (state & ModifierType.CONTROL_MASK) != 0;
|
||||
const isShiftPressed = (state & ModifierType.SHIFT_MASK) != 0;
|
||||
|
||||
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) {
|
||||
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()}`)
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (shellVersion >= 43) {
|
||||
return new Promise(resolve => resolve(this._getResultSet(terms)));
|
||||
} else {
|
||||
callback(this._getResultSet(terms));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
createResultOjbect(resultMeta) {
|
||||
const app = Shell.WindowTracker.get_default().get_window_app(resultMeta.id);
|
||||
return new AppIcon(app);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue