Compare commits

...

22 Commits

Author SHA1 Message Date
Florian Müllner
4345703c2e Bump version to 45.beta
Update NEWS.
2023-08-07 16:41:23 +02:00
Florian Müllner
a911447375 js: Port to ESM
The shell pulled the trigger and switched to ESM for all its
imports, follow suit.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/269>
2023-08-06 15:59:35 +02:00
Florian Müllner
2d3307c657 window-list: Use InjectionManager instead of custom classes
Once the shell is ported to ESM, it will no longer be possible
to replace entire classes (even when imported). Prepare for that
by overriding methods of the regular WorkspaceBackground class
instead.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-06 13:45:56 +02:00
Florian Müllner
d59bc0b7f0 window-list: Do not inject WindowPicker into Main
This will become impossible once Main is converted to ESM. Instead,
use the Extension class itself to hold the window picker.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-06 13:45:56 +02:00
Florian Müllner
cb8c2eb27f windowsNavigator: Use InjectionManager instead of custom classes
Once the shell is ported to ESM, it will no longer be possible
to replace entire classes (even when exported). Prepare for that
by overriding methods of the regular classes, instead of creating
custom subclasses.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-06 13:45:56 +02:00
Florian Müllner
0544729bba launch-new-instance: Use InjectionManager
The extension uses a straight-forward override that doesn't
benefit a lot from the new InjectionManager class, but let's
use the provided convenience API anyway.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-05 21:32:27 +02:00
Florian Müllner
017c410a6a native-window-placement: Use InjectionManager
The new convenience class was modelled after the code in the
extension, so it's a drop-in replacement.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-05 18:53:41 +02:00
Florian Müllner
f2c73329be extensions: Use new convenience classes
Convenience APIs for extensions are now provided as Extension/Prefs
base classes.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/268>
2023-08-05 18:53:41 +02:00
Efstathios Iosifidis
ce644be96f Update Greek translation 2023-08-01 20:41:56 +00:00
Florian Müllner
e75a1a15ac extensions: Import ExtensionUtils as module
ExtensionUtils has been converted to ESM and split into two modules,
for extensions and prefs respectively.

Adjust to those changes.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/266>
2023-07-15 14:13:25 +02:00
Florian Müllner
1155170c7c window-list: Stop using getCurrentExtension()
The method is no longer exported. There will be a nicer alternative
soon, in the meantime we can just keep track of our main Extension
object ourselves.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/266>
2023-07-15 14:10:27 +02:00
Florian Müllner
6d8f54a20b js: Really use connectObject()
I forgot in two places to change the actual connect() function
to connectObject() 🤦️

Fixes commit 3bfaf6f88a.
2023-07-10 07:22:25 +02:00
Florian Müllner
93a2e7bdba extensions: Stop using global.log()
It has been deprecated since 3.6(!) in favor of the actually
global log().

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/264>
2023-07-10 06:51:45 +02:00
Florian Müllner
3bfaf6f88a js: Use connectObject()
gnome-shell added (dis)connectObject() methods to partially automate
signal handling. It doesn't only save a significant amount of code,
but also makes it harder to miss cleaning up on destroy.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/263>
2023-07-09 18:45:07 +02:00
Florian Müllner
37baccd9fc window-list: Remove some dead code
The code that connected the signal was removed in 9fa522c29a.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/263>
2023-07-09 18:33:52 +02:00
Florian Müllner
9365725246 ci: Use wrapper to run eslint
The eslint job report its results as artifacts in junit format,
so that gitlab can present them in its UI.

However many psople miss that, and unsuccessfully check the logs
instead.

Address this by using a simplified version of gnome-shell's eslint
wrapper, so we can report results both on stdout and in a file
without re-running the linter.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/262>
2023-07-09 16:21:03 +02:00
Florian Müllner
f1257c4523 Ignore some common patterns
Ignore patches, vim session files and project configuration
of GNOME Builder and VSCode.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/261>
2023-07-09 15:59:34 +02:00
Florian Müllner
f0865f039e Clean up .gitignore
Meson enforces a separate build dir, so we no longer have to
care about build artifacts in the source tree. Same applies for
all the generated crap autotools like to spread around.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/261>
2023-07-09 15:59:34 +02:00
Florian Müllner
4955c20669 data: Remove left-over file
We no longer have a separate classic theme that could(*) use
custom assets, so the file is now very officially a left-over.

(*) spoiler alert: The made-up property where the image was
used has been ignored by gnome-shell for years

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/260>
2023-07-09 14:27:44 +02:00
Florian Müllner
cf007dd472 extensions: Turn extensions into modules
As gnome-shell is moving to ESM, it will now load extensions as
standard modules instead of using legacy imports. The change boils
down to exporting the Extension class as default, but we can also
start using standard imports for introspected modules now, so do
that at the same time.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/259>
2023-07-07 00:35:08 +02:00
Florian Müllner
701b14ecbf extensions: Use extension class for all extensions
This will be the only supported entry point when extension loading
switches to dynamic imports, so prepare for that by wrapping the
remaining standalone enable()/disable() methods in Extension classes.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/259>
2023-07-07 00:12:41 +02:00
Florian Müllner
18674b2e35 lint: Migrate eslint-plugin-jsdoc rule
Migrate a removed jsdoc, copied from the corresponding gnome-shell
change.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/259>
2023-07-07 00:11:57 +02:00
29 changed files with 1158 additions and 4523 deletions

30
.gitignore vendored
View File

@@ -1,29 +1,7 @@
ABOUT-NLS
Makefile
Makefile.in
Makefile.in.in
aclocal.m4
autom4te.cache/
config/
configure
config.log
config.status
data/*.json
m4/
po/*.header
po/*.sed
po/*.sin
po/Makevars.template
po/POTFILES
po/Rules-quot
po/gnome-shell-extensions.pot po/gnome-shell-extensions.pot
po/stamp-it
staging/
zip-files/ zip-files/
*~ *~
*.gmo *.patch
metadata.json *.sw?
*.desktop .buildconfig
*.gschema.valid .vscode
*.session

View File

@@ -107,7 +107,8 @@ eslint:
stage: review stage: review
<<: *prereview_req <<: *prereview_req
script: script:
- eslint -o $LINT_LOG -f junit --resolve-plugins-relative-to $(npm root -g) extensions - export NODE_PATH=$(npm root -g)
- ./.gitlab-ci/run-eslint --output-file ${LINT_LOG} --format junit --stdout
artifacts: artifacts:
paths: paths:
- ${LINT_LOG} - ${LINT_LOG}

54
.gitlab-ci/run-eslint Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env node
const {ESLint} = require('eslint');
console.log(`Running ESLint version ${ESLint.version}...`);
const fs = require('fs');
const path = require('path');
function hasOption(...names) {
return process.argv.some(arg => names.includes(arg));
}
function getOption(...names) {
const optIndex =
process.argv.findIndex(arg => names.includes(arg)) + 1;
if (optIndex === 0)
return undefined;
return process.argv[optIndex];
}
(async function main() {
const outputOption = getOption('--output-file', '-o');
const outputPath = outputOption ? path.resolve(outputOption) : null;
const sourceDir = path.dirname(process.argv[1]);
process.chdir(path.resolve(sourceDir, '..'));
const sources = ['extensions'];
const eslint = new ESLint();
const results = await eslint.lintFiles(sources);
const formatter = await eslint.loadFormatter(getOption('--format', '-f'));
const resultText = formatter.format(results);
if (outputPath) {
fs.mkdirSync(path.dirname(outputPath), {recursive: true});
fs.writeFileSync(outputPath, resultText);
if (hasOption('--stdout')) {
const consoleFormatter = await eslint.loadFormatter();
console.log(consoleFormatter.format(results));
}
} else {
console.log(resultText);
}
process.exitCode = results.some(r => r.errorCount > 0) ? 1 : 0;
})().catch((error) => {
process.exitCode = 1;
console.error(error);
});

11
NEWS
View File

@@ -1,3 +1,14 @@
45.beta
=======
* Port extensions to ESM [Florian; !259, !266, !268, !269]
* Misc. bug fixes and cleanups [Florian; !260, !261, !262, !263, !264]
Contributors:
Florian Müllner
Translators:
Efstathios Iosifidis [el]
45.alpha 45.alpha
======== ========
* window-list: Modernize default styling [Alexander; !253] * window-list: Modernize default styling [Alexander; !253]

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -1,18 +1,22 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported init enable disable */ import Atk from 'gi://Atk';
import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GMenu from 'gi://GMenu';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
import {EventEmitter} from 'resource:///org/gnome/shell/misc/signals.js';
const { import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
Atk, Clutter, Gio, GLib, GMenu, GObject, Gtk, Meta, Shell, St,
} = imports.gi;
const {EventEmitter} = imports.misc.signals;
const DND = imports.ui.dnd; import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
const ExtensionUtils = imports.misc.extensionUtils; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Main = imports.ui.main; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
const PanelMenu = imports.ui.panelMenu; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
const PopupMenu = imports.ui.popupMenu;
const _ = ExtensionUtils.gettext;
const appSys = Shell.AppSystem.get_default(); const appSys = Shell.AppSystem.get_default();
@@ -46,11 +50,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
this.label_actor = appLabel; this.label_actor = appLabel;
let textureCache = St.TextureCache.get_default(); let textureCache = St.TextureCache.get_default();
let iconThemeChangedId = textureCache.connect('icon-theme-changed', textureCache.connectObject('icon-theme-changed',
this._updateIcon.bind(this)); () => this._updateIcon(), this);
this.connect('destroy', () => {
textureCache.disconnect(iconThemeChangedId);
});
this._updateIcon(); this._updateIcon();
this._delegate = this; this._delegate = this;
@@ -269,9 +270,7 @@ class DesktopTarget extends EventEmitter {
_setDesktop(desktop) { _setDesktop(desktop) {
if (this._desktop) { if (this._desktop) {
this._desktop.disconnect(this._desktopDestroyedId); this._desktop.disconnectObject(this);
this._desktopDestroyedId = 0;
delete this._desktop._delegate; delete this._desktop._delegate;
} }
@@ -279,9 +278,9 @@ class DesktopTarget extends EventEmitter {
this.emit('desktop-changed'); this.emit('desktop-changed');
if (this._desktop) { if (this._desktop) {
this._desktopDestroyedId = this._desktop.connect('destroy', () => { this._desktop.connectObject('destroy', () => {
this._setDesktop(null); this._setDesktop(null);
}); }, this);
this._desktop._delegate = this; this._desktop._delegate = this;
} }
} }
@@ -321,10 +320,7 @@ class DesktopTarget extends EventEmitter {
} }
destroy() { destroy() {
if (this._windowAddedId) global.window_group.disconnectObject(this);
global.window_group.disconnect(this._windowAddedId);
this._windowAddedId = 0;
this._setDesktop(null); this._setDesktop(null);
} }
@@ -386,15 +382,14 @@ class ApplicationsButton extends PanelMenu.Button {
this.name = 'panelApplications'; this.name = 'panelApplications';
this.label_actor = this._label; this.label_actor = this._label;
this._showingId = Main.overview.connect('showing', () => { Main.overview.connectObject(
this.add_accessible_state(Atk.StateType.CHECKED); 'showing', () => this.add_accessible_state(Atk.StateType.CHECKED),
}); 'hiding', () => this.remove_accessible_state(Atk.StateType.CHECKED),
this._hidingId = Main.overview.connect('hiding', () => { this);
this.remove_accessible_state(Atk.StateType.CHECKED);
});
Main.wm.addKeybinding( Main.wm.addKeybinding(
'apps-menu-toggle-menu', 'apps-menu-toggle-menu',
ExtensionUtils.getSettings(), Extension.lookupByURL(import.meta.url).getSettings(),
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
() => this.menu.toggle()); () => this.menu.toggle());
@@ -410,15 +405,15 @@ class ApplicationsButton extends PanelMenu.Button {
}); });
this._tree = new GMenu.Tree({menu_basename: 'applications.menu'}); this._tree = new GMenu.Tree({menu_basename: 'applications.menu'});
this._treeChangedId = this._tree.connect('changed', this._tree.connectObject('changed',
this._onTreeChanged.bind(this)); () => this._onTreeChanged(), this);
this._applicationsButtons = new Map(); this._applicationsButtons = new Map();
this.reloadFlag = false; this.reloadFlag = false;
this._createLayout(); this._createLayout();
this._display(); this._display();
this._installedChangedId = appSys.connect('installed-changed', appSys.connectObject('installed-changed',
this._onTreeChanged.bind(this)); () => this._onTreeChanged(), this);
} }
_onTreeChanged() { _onTreeChanged() {
@@ -442,11 +437,7 @@ class ApplicationsButton extends PanelMenu.Button {
_onDestroy() { _onDestroy() {
super._onDestroy(); super._onDestroy();
Main.overview.disconnect(this._showingId); delete this._tree;
Main.overview.disconnect(this._hidingId);
appSys.disconnect(this._installedChangedId);
this._tree.disconnect(this._treeChangedId);
this._tree = null;
Main.wm.removeKeybinding('apps-menu-toggle-menu'); Main.wm.removeKeybinding('apps-menu-toggle-menu');
@@ -677,22 +668,17 @@ class ApplicationsButton extends PanelMenu.Button {
} }
} }
let appsMenuButton; export default class AppsMenuExtension extends Extension {
enable() {
this._appsMenuButton = new ApplicationsButton();
const index = Main.sessionMode.panel.left.indexOf('activities') + 1;
Main.panel.addToStatusArea(
'apps-menu', this._appsMenuButton, index, 'left');
}
/** */ disable() {
function enable() { Main.panel.menuManager.removeMenu(this._appsMenuButton.menu);
appsMenuButton = new ApplicationsButton(); this._appsMenuButton.destroy();
let index = Main.sessionMode.panel.left.indexOf('activities') + 1; delete this._appsMenuButton;
Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left'); }
}
/** */
function disable() {
Main.panel.menuManager.removeMenu(appsMenuButton.menu);
appsMenuButton.destroy();
}
/** */
function init() {
ExtensionUtils.initTranslations();
} }

View File

@@ -1,22 +1,20 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
// Start apps on custom workspaces // Start apps on custom workspaces
/* exported init enable disable */
const {Shell} = imports.gi; import Shell from 'gi://Shell';
const ExtensionUtils = imports.misc.extensionUtils; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
class WindowMover { class WindowMover {
constructor() { constructor(settings) {
this._settings = ExtensionUtils.getSettings(); this._settings = settings;
this._appSystem = Shell.AppSystem.get_default(); this._appSystem = Shell.AppSystem.get_default();
this._appConfigs = new Map(); this._appConfigs = new Map();
this._appData = new Map(); this._appData = new Map();
this._appsChangedId = this._appSystem.connectObject('installed-changed',
this._appSystem.connect('installed-changed', () => this._updateAppData(), this);
this._updateAppData.bind(this));
this._settings.connect('changed', this._updateAppConfigs.bind(this)); this._settings.connect('changed', this._updateAppConfigs.bind(this));
this._updateAppConfigs(); this._updateAppConfigs();
@@ -38,7 +36,7 @@ class WindowMover {
let removedApps = [...this._appData.keys()] let removedApps = [...this._appData.keys()]
.filter(a => !ids.includes(a.id)); .filter(a => !ids.includes(a.id));
removedApps.forEach(app => { removedApps.forEach(app => {
app.disconnect(this._appData.get(app).windowsChangedId); app.disconnectObject(this);
this._appData.delete(app); this._appData.delete(app);
}); });
@@ -46,21 +44,14 @@ class WindowMover {
.map(id => this._appSystem.lookup_app(id)) .map(id => this._appSystem.lookup_app(id))
.filter(app => app && !this._appData.has(app)); .filter(app => app && !this._appData.has(app));
addedApps.forEach(app => { addedApps.forEach(app => {
let data = { app.connectObject('window-changed',
windowsChangedId: app.connect('windows-changed', this._appWindowsChanged.bind(this), this);
this._appWindowsChanged.bind(this)), this._appData.set(app, {windows: app.get_windows()});
moveWindowsId: 0,
windows: app.get_windows(),
};
this._appData.set(app, data);
}); });
} }
destroy() { destroy() {
if (this._appsChangedId) { this._appSystem.disconnectObject(this);
this._appSystem.disconnect(this._appsChangedId);
this._appsChangedId = 0;
}
if (this._settings) { if (this._settings) {
this._settings.run_dispose(); this._settings.run_dispose();
@@ -105,47 +96,41 @@ class WindowMover {
} }
} }
let prevCheckWorkspaces; export default class AutoMoveExtension extends Extension {
let winMover; enable() {
this._prevCheckWorkspaces = Main.wm._workspaceTracker._checkWorkspaces;
/** */ Main.wm._workspaceTracker._checkWorkspaces =
function init() { this._getCheckWorkspaceOverride(this._prevCheckWorkspaces);
ExtensionUtils.initTranslations(); this._windowMover = new WindowMover(this.getSettings());
}
/**
* @returns {bool} - false (used as MetaLater handler)
*/
function myCheckWorkspaces() {
let keepAliveWorkspaces = [];
let foundNonEmpty = false;
for (let i = this._workspaces.length - 1; i >= 0; i--) {
if (!foundNonEmpty) {
foundNonEmpty = this._workspaces[i].list_windows().some(
w => !w.is_on_all_workspaces());
} else if (!this._workspaces[i]._keepAliveId) {
keepAliveWorkspaces.push(this._workspaces[i]);
}
} }
// make sure the original method only removes empty workspaces at the end disable() {
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1)); Main.wm._workspaceTracker._checkWorkspaces = this._prevCheckWorkspaces;
prevCheckWorkspaces.call(this); this._windowMover.destroy();
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId); delete this._windowMover;
}
return false; _getCheckWorkspaceOverride(originalMethod) {
} /* eslint-disable no-invalid-this */
return function () {
/** */ const keepAliveWorkspaces = [];
function enable() { let foundNonEmpty = false;
prevCheckWorkspaces = Main.wm._workspaceTracker._checkWorkspaces; for (let i = this._workspaces.length - 1; i >= 0; i--) {
Main.wm._workspaceTracker._checkWorkspaces = myCheckWorkspaces; if (!foundNonEmpty) {
foundNonEmpty = this._workspaces[i].list_windows().some(
winMover = new WindowMover(); w => !w.is_on_all_workspaces());
} } else if (!this._workspaces[i]._keepAliveId) {
keepAliveWorkspaces.push(this._workspaces[i]);
/** */ }
function disable() { }
Main.wm._workspaceTracker._checkWorkspaces = prevCheckWorkspaces;
winMover.destroy(); // make sure the original method only removes empty workspaces at the end
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1));
originalMethod.call(this);
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId);
return false;
};
/* eslint-enable no-invalid-this */
}
} }

View File

@@ -1,12 +1,13 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
// Start apps on custom workspaces // Start apps on custom workspaces
/* exported init buildPrefsWidget */
const {Adw, Gio, GLib, GObject, Gtk} = imports.gi; import Adw from 'gi://Adw';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
const ExtensionUtils = imports.misc.extensionUtils; import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const _ = ExtensionUtils.gettext;
const SETTINGS_KEY = 'application-list'; const SETTINGS_KEY = 'application-list';
@@ -59,13 +60,14 @@ class RulesList extends GObject.Object {
GObject.registerClass(this); GObject.registerClass(this);
} }
#settings = ExtensionUtils.getSettings(); #settings;
#rules = []; #rules = [];
#changedId; #changedId;
constructor() { constructor(settings) {
super(); super();
this.#settings = settings;
this.#changedId = this.#changedId =
this.#settings.connect(`changed::${SETTINGS_KEY}`, this.#settings.connect(`changed::${SETTINGS_KEY}`,
() => this.#sync()); () => this.#sync());
@@ -147,12 +149,13 @@ class AutoMoveSettingsWidget extends Adw.PreferencesGroup {
(self, name, param) => self._rules.changeWorkspace(...param.deepUnpack())); (self, name, param) => self._rules.changeWorkspace(...param.deepUnpack()));
} }
constructor() { constructor(settings) {
super({ super({
title: _('Workspace Rules'), title: _('Workspace Rules'),
}); });
this._rules = new RulesList(); this._settings = settings;
this._rules = new RulesList(this._settings);
const store = new Gio.ListStore({item_type: Gio.ListModel}); const store = new Gio.ListStore({item_type: Gio.ListModel});
const listModel = new Gtk.FlattenListModel({model: store}); const listModel = new Gtk.FlattenListModel({model: store});
@@ -173,7 +176,7 @@ class AutoMoveSettingsWidget extends Adw.PreferencesGroup {
} }
_addNewRule() { _addNewRule() {
const dialog = new NewRuleDialog(this.get_root()); const dialog = new NewRuleDialog(this.get_root(), this._settings);
dialog.connect('response', (dlg, id) => { dialog.connect('response', (dlg, id) => {
const appInfo = id === Gtk.ResponseType.OK const appInfo = id === Gtk.ResponseType.OK
? dialog.get_widget().get_app_info() : null; ? dialog.get_widget().get_app_info() : null;
@@ -312,13 +315,13 @@ class NewRuleDialog extends Gtk.AppChooserDialog {
GObject.registerClass(this); GObject.registerClass(this);
} }
constructor(parent) { constructor(parent, settings) {
super({ super({
transient_for: parent, transient_for: parent,
modal: true, modal: true,
}); });
this._settings = ExtensionUtils.getSettings(); this._settings = settings;
this.get_widget().set({ this.get_widget().set({
show_all: true, show_all: true,
@@ -338,14 +341,8 @@ class NewRuleDialog extends Gtk.AppChooserDialog {
} }
} }
/** */ export default class AutoMovePrefs extends ExtensionPreferences {
function init() { getPreferencesWidget() {
ExtensionUtils.initTranslations(); return new AutoMoveSettingsWidget(this.getSettings());
} }
/**
* @returns {Gtk.Widget} - the prefs widget
*/
function buildPrefsWidget() {
return new AutoMoveSettingsWidget();
} }

View File

@@ -1,14 +1,16 @@
/* exported init enable disable */
// Drive menu extension // Drive menu extension
const {Clutter, Gio, GObject, Shell, St} = imports.gi; import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Shell from 'gi://Shell';
import St from 'gi://St';
const ExtensionUtils = imports.misc.extensionUtils; import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const ShellMountOperation = imports.ui.shellMountOperation;
const _ = ExtensionUtils.gettext; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
import * as ShellMountOperation from 'resource:///org/gnome/shell/ui/shellMountOperation.js';
Gio._promisify(Gio.File.prototype, 'query_filesystem_info_async'); Gio._promisify(Gio.File.prototype, 'query_filesystem_info_async');
@@ -47,19 +49,11 @@ class MountMenuItem extends PopupMenu.PopupBaseMenuItem {
this.hide(); this.hide();
this._changedId = mount.connect('changed', this._syncVisibility.bind(this)); mount.connectObject('changed',
() => this._syncVisibility(), this);
this._syncVisibility(); this._syncVisibility();
} }
_onDestroy() {
if (this._changedId) {
this.mount.disconnect(this._changedId);
this._changedId = 0;
}
super.destroy();
}
async _isInteresting() { async _isInteresting() {
if (!this.mount.can_eject() && !this.mount.can_unmount()) if (!this.mount.can_eject() && !this.mount.can_unmount())
return false; return false;
@@ -152,12 +146,12 @@ class DriveMenu extends PanelMenu.Button {
this.add_child(icon); this.add_child(icon);
this._monitor = Gio.VolumeMonitor.get(); this._monitor = Gio.VolumeMonitor.get();
this._addedId = this._monitor.connect('mount-added', this._monitor.connectObject(
(monitor, mount) => this._addMount(mount)); 'mount-added', (monitor, mount) => this._addMount(mount),
this._removedId = this._monitor.connect('mount-removed', (monitor, mount) => { 'mount-removed', (monitor, mount) => {
this._removeMount(mount); this._removeMount(mount);
this._updateMenuVisibility(); this._updateMenuVisibility();
}); }, this);
this._mounts = []; this._mounts = [];
@@ -199,33 +193,16 @@ class DriveMenu extends PanelMenu.Button {
} }
log('Removing a mount that was never added to the menu'); log('Removing a mount that was never added to the menu');
} }
}
_onDestroy() { export default class PlaceMenuExtension extends Extension {
if (this._addedId) { enable() {
this._monitor.disconnect(this._addedId); this._indicator = new DriveMenu();
this._monitor.disconnect(this._removedId); Main.panel.addToStatusArea('drive-menu', this._indicator);
this._addedId = 0; }
this._removedId = 0;
}
super._onDestroy(); disable() {
this._indicator.destroy();
delete this._indicator;
} }
} }
/** */
function init() {
ExtensionUtils.initTranslations();
}
let _indicator;
/** */
function enable() {
_indicator = new DriveMenu();
Main.panel.addToStatusArea('drive-menu', _indicator);
}
/** */
function disable() {
_indicator.destroy();
}

View File

@@ -1,17 +1,22 @@
/* exported enable disable */ import {AppIcon} from 'resource:///org/gnome/shell/ui/appDisplay.js';
const AppDisplay = imports.ui.appDisplay; import {InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
let _activateOriginal = null; export default class Extension {
constructor() {
this._injectionManager = new InjectionManager();
}
/** */ enable() {
function enable() { this._injectionManager.overrideMethod(AppIcon.prototype, 'activate',
_activateOriginal = AppDisplay.AppIcon.prototype.activate; originalMethod => {
AppDisplay.AppIcon.prototype.activate = function () { return function () {
_activateOriginal.call(this, 2); // eslint-disable-next-line no-invalid-this
}; originalMethod.call(this, 2);
} };
});
/** */ }
function disable() {
AppDisplay.AppIcon.prototype.activate = _activateOriginal; disable() {
this._injectionManager.clear();
}
} }

View File

@@ -1,4 +1,3 @@
/* exported init */
/* /*
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -16,11 +15,11 @@
* SPDX-License-Identifier: GPL-2.0-or-later * SPDX-License-Identifier: GPL-2.0-or-later
*/ */
const {St} = imports.gi; import St from 'gi://St';
const Main = imports.ui.main; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
class Extension { export default class Extension {
_updateColorScheme(scheme) { _updateColorScheme(scheme) {
Main.sessionMode.colorScheme = scheme; Main.sessionMode.colorScheme = scheme;
St.Settings.get().notify('color-scheme'); St.Settings.get().notify('color-scheme');
@@ -35,8 +34,3 @@ class Extension {
this._updateColorScheme(this._savedColorScheme); this._updateColorScheme(this._savedColorScheme);
} }
} }
/** */
function init() {
return new Extension();
}

View File

@@ -1,11 +1,11 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported enable disable */ import Clutter from 'gi://Clutter';
const {Clutter} = imports.gi;
const ExtensionUtils = imports.misc.extensionUtils; import {Extension, InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main;
const {WindowPreview} = imports.ui.windowPreview; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Workspace = imports.ui.workspace; import {WindowPreview} from 'resource:///org/gnome/shell/ui/windowPreview.js';
import * as Workspace from 'resource:///org/gnome/shell/ui/workspace.js';
// testing settings for natural window placement strategy: // testing settings for natural window placement strategy:
const WINDOW_PLACEMENT_NATURAL_ACCURACY = 20; // accuracy of window translate moves (KDE-default: 20) const WINDOW_PLACEMENT_NATURAL_ACCURACY = 20; // accuracy of window translate moves (KDE-default: 20)
@@ -236,75 +236,64 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
} }
} }
let winInjections, workspaceInjections; export default class NativeWindowPlacementExtension extends Extension {
constructor(metadata) {
super(metadata);
/** */ this._injectionManager = new InjectionManager();
function resetState() { }
winInjections = { };
workspaceInjections = { }; enable() {
} const settings = this.getSettings();
/** */ const layoutProto = Workspace.WorkspaceLayout.prototype;
function enable() { const previewProto = WindowPreview.prototype;
resetState();
this._injectionManager.overrideMethod(layoutProto, '_createBestLayout', () => {
let settings = ExtensionUtils.getSettings(); /* eslint-disable no-invalid-this */
return function () {
workspaceInjections['_createBestLayout'] = Workspace.WorkspaceLayout.prototype._createBestLayout; this._layoutStrategy = new NaturalLayoutStrategy({
Workspace.WorkspaceLayout.prototype._createBestLayout = function (_area) { monitor: Main.layoutManager.monitors[this._monitorIndex],
this._layoutStrategy = new NaturalLayoutStrategy({ }, settings);
monitor: Main.layoutManager.monitors[this._monitorIndex], return this._layoutStrategy.computeLayout(this._sortedWindows);
}, settings); };
return this._layoutStrategy.computeLayout(this._sortedWindows); /* eslint-enable no-invalid-this */
}; });
// position window titles on top of windows in overlay // position window titles on top of windows in overlay
winInjections['_init'] = WindowPreview.prototype._init; this._injectionManager.overrideMethod(previewProto, '_init', originalMethod => {
WindowPreview.prototype._init = function (...args) { /* eslint-disable no-invalid-this */
winInjections['_init'].call(this, ...args); return function (...args) {
originalMethod.call(this, ...args);
if (!settings.get_boolean('window-captions-on-top'))
return; if (!settings.get_boolean('window-captions-on-top'))
return;
const alignConstraint = this._title.get_constraints().find(
c => c.align_axis && c.align_axis === Clutter.AlignAxis.Y_AXIS); const alignConstraint = this._title.get_constraints().find(
alignConstraint.factor = 0; c => c.align_axis && c.align_axis === Clutter.AlignAxis.Y_AXIS);
alignConstraint.factor = 0;
const bindConstraint = this._title.get_constraints().find(
c => c.coordinate && c.coordinate === Clutter.BindCoordinate.Y); const bindConstraint = this._title.get_constraints().find(
bindConstraint.offset = 0; c => c.coordinate && c.coordinate === Clutter.BindCoordinate.Y);
}; bindConstraint.offset = 0;
winInjections['_adjustOverlayOffsets'] = };
WindowPreview.prototype._adjustOverlayOffsets; /* eslint-enable no-invalid-this */
WindowPreview.prototype._adjustOverlayOffsets = function (...args) { });
winInjections['_adjustOverlayOffsets'].call(this, ...args);
this._injectionManager.overrideMethod(previewProto, '_adjustOverlayOffsets', originalMethod => {
if (settings.get_boolean('window-captions-on-top')) /* eslint-disable no-invalid-this */
this._title.translation_y = -this._title.translation_y; return function (...args) {
}; originalMethod.call(this, ...args);
}
if (settings.get_boolean('window-captions-on-top'))
/** this._title.translation_y = -this._title.translation_y;
* @param {object} object - object that was modified };
* @param {object} injection - the map of previous injections /* eslint-enable no-invalid-this */
* @param {string} name - the @injection key that should be removed });
*/ }
function removeInjection(object, injection, name) {
if (injection[name] === undefined) disable() {
delete object[name]; this._injectionManager.clear();
else global.stage.queue_relayout();
object[name] = injection[name]; }
}
/** */
function disable() {
var i;
for (i in workspaceInjections)
removeInjection(Workspace.WorkspaceLayout.prototype, workspaceInjections, i);
for (i in winInjections)
removeInjection(WindowPreview.prototype, winInjections, i);
global.stage.queue_relayout();
resetState();
} }

View File

@@ -1,17 +1,16 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported init enable disable */ import Clutter from 'gi://Clutter';
import GObject from 'gi://GObject';
import St from 'gi://St';
const {Clutter, GObject, St} = imports.gi; import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const ExtensionUtils = imports.misc.extensionUtils; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Main = imports.ui.main; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
const PanelMenu = imports.ui.panelMenu; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
const PopupMenu = imports.ui.popupMenu;
const Me = ExtensionUtils.getCurrentExtension(); import {PlacesManager} from './placeDisplay.js';
const PlaceDisplay = Me.imports.placeDisplay;
const _ = ExtensionUtils.gettext;
const N_ = x => x; const N_ = x => x;
const PLACE_ICON_SIZE = 16; const PLACE_ICON_SIZE = 16;
@@ -53,17 +52,8 @@ class PlaceMenuItem extends PopupMenu.PopupBaseMenuItem {
this.add_child(this._ejectButton); this.add_child(this._ejectButton);
} }
this._changedId = info.connect('changed', info.connectObject('changed',
this._propertiesChanged.bind(this)); this._propertiesChanged.bind(this), this);
}
destroy() {
if (this._changedId) {
this._info.disconnect(this._changedId);
this._changedId = 0;
}
super.destroy();
} }
activate(event) { activate(event) {
@@ -100,7 +90,7 @@ class PlacesMenu extends PanelMenu.Button {
}); });
this.add_actor(label); this.add_actor(label);
this.placesManager = new PlaceDisplay.PlacesManager(); this.placesManager = new PlacesManager();
this._sections = { }; this._sections = { };
@@ -138,24 +128,18 @@ class PlacesMenu extends PanelMenu.Button {
} }
} }
/** */ export default class PlacesMenuExtension extends Extension {
function init() { enable() {
ExtensionUtils.initTranslations(); this._indicator = new PlacesMenu();
}
let pos = Main.sessionMode.panel.left.length;
let _indicator; if ('apps-menu' in Main.panel.statusArea)
pos++;
/** */ Main.panel.addToStatusArea('places-menu', this._indicator, pos, 'left');
function enable() { }
_indicator = new PlacesMenu();
disable() {
let pos = Main.sessionMode.panel.left.length; this._indicator.destroy();
if ('apps-menu' in Main.panel.statusArea) delete this._indicator;
pos++; }
Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left');
}
/** */
function disable() {
_indicator.destroy();
} }

View File

@@ -1,14 +1,14 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PlacesManager */ import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import Shell from 'gi://Shell';
import {EventEmitter} from 'resource:///org/gnome/shell/misc/signals.js';
const {Gio, GLib, Shell} = imports.gi; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const {EventEmitter} = imports.misc.signals;
const ExtensionUtils = imports.misc.extensionUtils; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Main = imports.ui.main; import * as ShellMountOperation from 'resource:///org/gnome/shell/ui/shellMountOperation.js';
const ShellMountOperation = imports.ui.shellMountOperation;
const _ = ExtensionUtils.gettext;
const N_ = x => x; const N_ = x => x;
Gio._promisify(Gio.AppInfo, 'launch_default_for_uri_async'); Gio._promisify(Gio.AppInfo, 'launch_default_for_uri_async');
@@ -248,7 +248,7 @@ const DEFAULT_DIRECTORIES = [
GLib.UserDirectory.DIRECTORY_VIDEOS, GLib.UserDirectory.DIRECTORY_VIDEOS,
]; ];
var PlacesManager = class extends EventEmitter { export class PlacesManager extends EventEmitter {
constructor() { constructor() {
super(); super();
@@ -260,15 +260,25 @@ var PlacesManager = class extends EventEmitter {
}; };
this._settings = new Gio.Settings({schema_id: BACKGROUND_SCHEMA}); this._settings = new Gio.Settings({schema_id: BACKGROUND_SCHEMA});
this._showDesktopIconsChangedId = this._settings.connect( this._settings.connectObject('changed::show-desktop-icons',
'changed::show-desktop-icons', this._updateSpecials.bind(this)); () => this._updateSpecials(), this);
this._updateSpecials(); this._updateSpecials();
/* /*
* Show devices, code more or less ported from nautilus-places-sidebar.c * Show devices, code more or less ported from nautilus-places-sidebar.c
*/ */
this._volumeMonitor = Gio.VolumeMonitor.get(); this._volumeMonitor = Gio.VolumeMonitor.get();
this._connectVolumeMonitorSignals(); this._volumeMonitor.connectObject(
'volume-added', () => this._updateMounts(),
'volume-removed', () => this._updateMounts(),
'volume-changed', () => this._updateMounts(),
'mount-added', () => this._updateMounts(),
'mount-removed', () => this._updateMounts(),
'mount-changed', () => this._updateMounts(),
'drive-connected', () => this._updateMounts(),
'drive-disconnected', () => this._updateMounts(),
'drive-changed', () => this._updateMounts(),
this);
this._updateMounts(); this._updateMounts();
this._bookmarksFile = this._findBookmarksFile(); this._bookmarksFile = this._findBookmarksFile();
@@ -293,34 +303,11 @@ var PlacesManager = class extends EventEmitter {
} }
} }
_connectVolumeMonitorSignals() {
const signals = [
'volume-added',
'volume-removed',
'volume-changed',
'mount-added',
'mount-removed',
'mount-changed',
'drive-connected',
'drive-disconnected',
'drive-changed',
];
this._volumeMonitorSignals = [];
let func = this._updateMounts.bind(this);
for (let i = 0; i < signals.length; i++) {
let id = this._volumeMonitor.connect(signals[i], func);
this._volumeMonitorSignals.push(id);
}
}
destroy() { destroy() {
if (this._settings) this._settings?.disconnectObject(this);
this._settings.disconnect(this._showDesktopIconsChangedId);
this._settings = null; this._settings = null;
for (let i = 0; i < this._volumeMonitorSignals.length; i++) this._volumeMonitor.disconnectObject(this);
this._volumeMonitor.disconnect(this._volumeMonitorSignals[i]);
if (this._monitor) if (this._monitor)
this._monitor.cancel(); this._monitor.cancel();
@@ -546,4 +533,4 @@ var PlacesManager = class extends EventEmitter {
get(kind) { get(kind) {
return this._places[kind]; return this._places[kind];
} }
}; }

View File

@@ -1,4 +1,3 @@
/* exported enable disable */
/* Screenshot Window Sizer for Gnome Shell /* Screenshot Window Sizer for Gnome Shell
* *
* Copyright (c) 2013 Owen Taylor <otaylor@redhat.com> * Copyright (c) 2013 Owen Taylor <otaylor@redhat.com>
@@ -19,153 +18,151 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
const {Clutter, Meta, Shell, St} = imports.gi; import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
const ExtensionUtils = imports.misc.extensionUtils; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main;
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const MESSAGE_FADE_TIME = 2000; const MESSAGE_FADE_TIME = 2000;
let text; export default class ScreenshotWindowSizerExtension extends Extension {
SIZES = [
[624, 351],
[800, 450],
[1024, 576],
[1200, 675],
[1600, 900],
[360, 654], // Phone portrait maximized
[720, 360], // Phone landscape fullscreen
];
/** */ _flashMessage(message) {
function hideMessage() { if (!this._text) {
text.destroy(); this._text = new St.Label({style_class: 'screenshot-sizer-message'});
text = null; Main.uiGroup.add_actor(this._text);
}
/**
* @param {string} message - the message to flash
*/
function flashMessage(message) {
if (!text) {
text = new St.Label({style_class: 'screenshot-sizer-message'});
Main.uiGroup.add_actor(text);
}
text.remove_all_transitions();
text.text = message;
text.opacity = 255;
let monitor = Main.layoutManager.primaryMonitor;
text.set_position(
monitor.x + Math.floor(monitor.width / 2 - text.width / 2),
monitor.y + Math.floor(monitor.height / 2 - text.height / 2));
text.ease({
opacity: 0,
duration: MESSAGE_FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: hideMessage,
});
}
let SIZES = [
[624, 351],
[800, 450],
[1024, 576],
[1200, 675],
[1600, 900],
[360, 654], // Phone portrait maximized
[720, 360], // Phone landscape fullscreen
];
/**
* @param {Meta.Display} display - the display
* @param {Meta.Window=} window - for per-window bindings, the window
* @param {Meta.KeyBinding} binding - the key binding
*/
function cycleScreenshotSizes(display, window, binding) {
// Probably this isn't useful with 5 sizes, but you can decrease instead
// of increase by holding down shift.
let modifiers = binding.get_modifiers();
let backwards = (modifiers & Meta.VirtualModifier.SHIFT_MASK) !== 0;
// Unmaximize first
if (window.get_maximized() !== 0)
window.unmaximize(Meta.MaximizeFlags.BOTH);
let workArea = window.get_work_area_current_monitor();
let outerRect = window.get_frame_rect();
// Double both axes if on a hidpi display
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let scaledSizes = SIZES.map(size => size.map(wh => wh * scaleFactor))
.filter(([w, h]) => w <= workArea.width && h <= workArea.height);
// Find the nearest 16:9 size for the current window size
let nearestIndex;
let nearestError;
for (let i = 0; i < scaledSizes.length; i++) {
let [width, height] = scaledSizes[i];
// get the best initial window size
let error = Math.abs(width - outerRect.width) + Math.abs(height - outerRect.height);
if (nearestIndex === undefined || error < nearestError) {
nearestIndex = i;
nearestError = error;
} }
this._text.remove_all_transitions();
this._text.text = message;
this._text.opacity = 255;
const monitor = Main.layoutManager.primaryMonitor;
this._text.set_position(
monitor.x + Math.floor(monitor.width / 2 - this._text.width / 2),
monitor.y + Math.floor(monitor.height / 2 - this._text.height / 2));
this._text.ease({
opacity: 0,
duration: MESSAGE_FADE_TIME,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onComplete: () => this._hideMessage(),
});
} }
// get the next size up or down from ideal _hideMessage() {
let newIndex = (nearestIndex + (backwards ? -1 : 1)) % scaledSizes.length; this._text.destroy();
let [newWidth, newHeight] = scaledSizes[newIndex]; delete this._text;
}
// Push the window onscreen if it would be resized offscreen /**
let newX = outerRect.x; * @param {Meta.Display} display - the display
let newY = outerRect.y; * @param {Meta.Window=} window - for per-window bindings, the window
if (newX + newWidth > workArea.x + workArea.width) * @param {Meta.KeyBinding} binding - the key binding
newX = Math.max(workArea.x + workArea.width - newWidth); */
if (newY + newHeight > workArea.y + workArea.height) _cycleScreenshotSizes(display, window, binding) {
newY = Math.max(workArea.y + workArea.height - newHeight); // Probably this isn't useful with 5 sizes, but you can decrease instead
// of increase by holding down shift.
let modifiers = binding.get_modifiers();
let backwards = (modifiers & Meta.VirtualModifier.SHIFT_MASK) !== 0;
const id = window.connect('size-changed', () => { // Unmaximize first
window.disconnect(id); if (window.get_maximized() !== 0)
_notifySizeChange(window); window.unmaximize(Meta.MaximizeFlags.BOTH);
});
window.move_resize_frame(true, newX, newY, newWidth, newHeight); let workArea = window.get_work_area_current_monitor();
} let outerRect = window.get_frame_rect();
/** // Double both axes if on a hidpi display
* @param {Meta.Window} window - the window whose size changed let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
*/ let scaledSizes = this.SIZES.map(size => size.map(wh => wh * scaleFactor))
function _notifySizeChange(window) { .filter(([w, h]) => w <= workArea.width && h <= workArea.height);
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
let newOuterRect = window.get_frame_rect(); // Find the nearest 16:9 size for the current window size
let message = '%d×%d'.format( let nearestIndex;
newOuterRect.width / scaleFactor, let nearestError;
newOuterRect.height / scaleFactor);
for (let i = 0; i < scaledSizes.length; i++) {
// The new size might have been constrained by geometry hints (e.g. for let [width, height] = scaledSizes[i];
// a terminal) - in that case, include the actual ratio to the message
// we flash // get the best initial window size
let actualNumerator = 9 * newOuterRect.width / newOuterRect.height; let error = Math.abs(width - outerRect.width) + Math.abs(height - outerRect.height);
if (Math.abs(actualNumerator - 16) > 0.01) if (nearestIndex === undefined || error < nearestError) {
message += ' (%.2f:9)'.format(actualNumerator); nearestIndex = i;
nearestError = error;
flashMessage(message); }
} }
/** */ // get the next size up or down from ideal
function enable() { let newIndex = (nearestIndex + (backwards ? -1 : 1)) % scaledSizes.length;
Main.wm.addKeybinding( let [newWidth, newHeight] = scaledSizes[newIndex];
'cycle-screenshot-sizes',
ExtensionUtils.getSettings(), // Push the window onscreen if it would be resized offscreen
Meta.KeyBindingFlags.PER_WINDOW, let newX = outerRect.x;
Shell.ActionMode.NORMAL, let newY = outerRect.y;
cycleScreenshotSizes); if (newX + newWidth > workArea.x + workArea.width)
Main.wm.addKeybinding( newX = Math.max(workArea.x + workArea.width - newWidth);
'cycle-screenshot-sizes-backward', if (newY + newHeight > workArea.y + workArea.height)
ExtensionUtils.getSettings(), newY = Math.max(workArea.y + workArea.height - newHeight);
Meta.KeyBindingFlags.PER_WINDOW | Meta.KeyBindingFlags.IS_REVERSED,
Shell.ActionMode.NORMAL, const id = window.connect('size-changed', () => {
cycleScreenshotSizes); window.disconnect(id);
} this._notifySizeChange(window);
});
/** */ window.move_resize_frame(true, newX, newY, newWidth, newHeight);
function disable() { }
Main.wm.removeKeybinding('cycle-screenshot-sizes');
Main.wm.removeKeybinding('cycle-screenshot-sizes-backward'); /**
* @param {Meta.Window} window - the window whose size changed
*/
_notifySizeChange(window) {
const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage);
let newOuterRect = window.get_frame_rect();
let message = '%d×%d'.format(
newOuterRect.width / scaleFactor,
newOuterRect.height / scaleFactor);
// The new size might have been constrained by geometry hints (e.g. for
// a terminal) - in that case, include the actual ratio to the message
// we flash
let actualNumerator = 9 * newOuterRect.width / newOuterRect.height;
if (Math.abs(actualNumerator - 16) > 0.01)
message += ' (%.2f:9)'.format(actualNumerator);
this._flashMessage(message);
}
enable() {
Main.wm.addKeybinding(
'cycle-screenshot-sizes',
this.getSettings(),
Meta.KeyBindingFlags.PER_WINDOW,
Shell.ActionMode.NORMAL,
this._cycleScreenshotSizes.bind(this));
Main.wm.addKeybinding(
'cycle-screenshot-sizes-backward',
this.getSettings(),
Meta.KeyBindingFlags.PER_WINDOW | Meta.KeyBindingFlags.IS_REVERSED,
Shell.ActionMode.NORMAL,
this._cycleScreenshotSizes.bind(this));
}
disable() {
Main.wm.removeKeybinding('cycle-screenshot-sizes');
Main.wm.removeKeybinding('cycle-screenshot-sizes-backward');
}
} }

View File

@@ -1,20 +1,19 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
// Load shell theme from ~/.local/share/themes/name/gnome-shell // Load shell theme from ~/.local/share/themes/name/gnome-shell
/* exported init */
const {Gio} = imports.gi; import Gio from 'gi://Gio';
const ExtensionUtils = imports.misc.extensionUtils; import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main;
const Me = ExtensionUtils.getCurrentExtension(); import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Util = Me.imports.util;
import {getThemeDirs, getModeThemeDirs} from './util.js';
const SETTINGS_KEY = 'name'; const SETTINGS_KEY = 'name';
class ThemeManager { export default class ThemeManager extends Extension {
enable() { enable() {
this._settings = ExtensionUtils.getSettings(); this._settings = this.getSettings();
this._settings.connect(`changed::${SETTINGS_KEY}`, this._changeTheme.bind(this)); this._settings.connect(`changed::${SETTINGS_KEY}`, this._changeTheme.bind(this));
this._changeTheme(); this._changeTheme();
} }
@@ -32,10 +31,10 @@ class ThemeManager {
let themeName = this._settings.get_string(SETTINGS_KEY); let themeName = this._settings.get_string(SETTINGS_KEY);
if (themeName) { if (themeName) {
const stylesheetPaths = Util.getThemeDirs() const stylesheetPaths = getThemeDirs()
.map(dir => `${dir}/${themeName}/gnome-shell/gnome-shell.css`); .map(dir => `${dir}/${themeName}/gnome-shell/gnome-shell.css`);
stylesheetPaths.push(...Util.getModeThemeDirs() stylesheetPaths.push(...getModeThemeDirs()
.map(dir => `${dir}/${themeName}.css`)); .map(dir => `${dir}/${themeName}.css`));
stylesheet = stylesheetPaths.find(path => { stylesheet = stylesheetPaths.find(path => {
@@ -45,17 +44,10 @@ class ThemeManager {
} }
if (stylesheet) if (stylesheet)
global.log(`loading user theme: ${stylesheet}`); log(`loading user theme: ${stylesheet}`);
else else
global.log('loading default theme (Adwaita)'); log('loading default theme (Adwaita)');
Main.setThemeStylesheet(stylesheet); Main.setThemeStylesheet(stylesheet);
Main.loadTheme(); Main.loadTheme();
} }
} }
/**
* @returns {ThemeManager} - the extension state object
*/
function init() {
return new ThemeManager();
}

View File

@@ -1,15 +1,17 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported init buildPrefsWidget */
// we use async/await here to not block the mainloop, not to parallelize // we use async/await here to not block the mainloop, not to parallelize
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
const {Adw, Gio, GLib, GObject, Gtk} = imports.gi; import Adw from 'gi://Adw';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
const ExtensionUtils = imports.misc.extensionUtils; import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const Me = ExtensionUtils.getCurrentExtension(); import {getThemeDirs, getModeThemeDirs} from './util.js';
const Util = Me.imports.util;
Gio._promisify(Gio.File.prototype, 'enumerate_children_async'); Gio._promisify(Gio.File.prototype, 'enumerate_children_async');
Gio._promisify(Gio.File.prototype, 'query_info_async'); Gio._promisify(Gio.File.prototype, 'query_info_async');
@@ -20,13 +22,13 @@ class UserThemePrefsWidget extends Adw.PreferencesGroup {
GObject.registerClass(this); GObject.registerClass(this);
} }
constructor() { constructor(settings) {
super({title: 'Themes'}); super({title: 'Themes'});
this._actionGroup = new Gio.SimpleActionGroup(); this._actionGroup = new Gio.SimpleActionGroup();
this.insert_action_group('theme', this._actionGroup); this.insert_action_group('theme', this._actionGroup);
this._settings = ExtensionUtils.getSettings(); this._settings = settings;
this._actionGroup.add_action( this._actionGroup.add_action(
this._settings.create_action('name')); this._settings.create_action('name'));
@@ -39,7 +41,7 @@ class UserThemePrefsWidget extends Adw.PreferencesGroup {
} }
async _collectThemes() { async _collectThemes() {
for (const dirName of Util.getThemeDirs()) { for (const dirName of getThemeDirs()) {
const dir = Gio.File.new_for_path(dirName); const dir = Gio.File.new_for_path(dirName);
for (const name of await this._enumerateDir(dir)) { for (const name of await this._enumerateDir(dir)) {
if (this._rows.has(name)) if (this._rows.has(name))
@@ -60,7 +62,7 @@ class UserThemePrefsWidget extends Adw.PreferencesGroup {
} }
} }
for (const dirName of Util.getModeThemeDirs()) { for (const dirName of getModeThemeDirs()) {
const dir = Gio.File.new_for_path(dirName); const dir = Gio.File.new_for_path(dirName);
for (const filename of await this._enumerateDir(dir)) { for (const filename of await this._enumerateDir(dir)) {
if (!filename.endsWith('.css')) if (!filename.endsWith('.css'))
@@ -125,13 +127,8 @@ class ThemeRow extends Adw.ActionRow {
} }
} }
/** */ export default class UserThemePrefs extends ExtensionPreferences {
function init() { getPreferencesWidget() {
} return new UserThemePrefsWidget(this.getSettings());
}
/**
* @returns {Gtk.Widget} - the prefs widget
*/
function buildPrefsWidget() {
return new UserThemePrefsWidget();
} }

View File

@@ -1,12 +1,11 @@
/* exported getThemeDirs getModeThemeDirs */ import GLib from 'gi://GLib';
const {GLib} = imports.gi;
const fn = (...args) => GLib.build_filenamev(args); const fn = (...args) => GLib.build_filenamev(args);
/** /**
* @returns {string[]} - an ordered list of theme directories * @returns {string[]} - an ordered list of theme directories
*/ */
function getThemeDirs() { export function getThemeDirs() {
return [ return [
fn(GLib.get_home_dir(), '.themes'), fn(GLib.get_home_dir(), '.themes'),
fn(GLib.get_user_data_dir(), 'themes'), fn(GLib.get_user_data_dir(), 'themes'),
@@ -17,7 +16,7 @@ function getThemeDirs() {
/** /**
* @returns {string[]} - an ordered list of mode theme directories * @returns {string[]} - an ordered list of mode theme directories
*/ */
function getModeThemeDirs() { export function getModeThemeDirs() {
return GLib.get_system_data_dirs() return GLib.get_system_data_dirs()
.map(dir => fn(dir, 'gnome-shell', 'theme')); .map(dir => fn(dir, 'gnome-shell', 'theme'));
} }

View File

@@ -1,17 +1,21 @@
/* exported init */ import Clutter from 'gi://Clutter';
const {Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St} = imports.gi; import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Meta from 'gi://Meta';
import Shell from 'gi://Shell';
import St from 'gi://St';
const DND = imports.ui.dnd; import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu;
const Me = ExtensionUtils.getCurrentExtension(); import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
const {WindowPicker, WindowPickerToggle} = Me.imports.windowPicker; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const {WorkspaceIndicator} = Me.imports.workspaceIndicator; import * as Overview from 'resource:///org/gnome/shell/ui/overview.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
const _ = ExtensionUtils.gettext; import {WindowPicker, WindowPickerToggle} from './windowPicker.js';
import {WorkspaceIndicator} from './workspaceIndicator.js';
const ICON_TEXTURE_SIZE = 24; const ICON_TEXTURE_SIZE = 24;
const DND_ACTIVATE_TIMEOUT = 500; const DND_ACTIVATE_TIMEOUT = 500;
@@ -51,10 +55,6 @@ class WindowContextMenu extends PopupMenu.PopupMenu {
}); });
this.addMenuItem(this._minimizeItem); this.addMenuItem(this._minimizeItem);
this._notifyMinimizedId = this._metaWindow.connect(
'notify::minimized', this._updateMinimizeItem.bind(this));
this._updateMinimizeItem();
this._maximizeItem = new PopupMenu.PopupMenuItem(''); this._maximizeItem = new PopupMenu.PopupMenuItem('');
this._maximizeItem.connect('activate', () => { this._maximizeItem.connect('activate', () => {
if (this._metaWindow.get_maximized() === Meta.MaximizeFlags.BOTH) if (this._metaWindow.get_maximized() === Meta.MaximizeFlags.BOTH)
@@ -64,21 +64,20 @@ class WindowContextMenu extends PopupMenu.PopupMenu {
}); });
this.addMenuItem(this._maximizeItem); this.addMenuItem(this._maximizeItem);
this._notifyMaximizedHId = this._metaWindow.connect(
'notify::maximized-horizontally',
this._updateMaximizeItem.bind(this));
this._notifyMaximizedVId = this._metaWindow.connect(
'notify::maximized-vertically',
this._updateMaximizeItem.bind(this));
this._updateMaximizeItem();
this._closeItem = new PopupMenu.PopupMenuItem(_('Close')); this._closeItem = new PopupMenu.PopupMenuItem(_('Close'));
this._closeItem.connect('activate', () => { this._closeItem.connect('activate', () => {
this._metaWindow.delete(global.get_current_time()); this._metaWindow.delete(global.get_current_time());
}); });
this.addMenuItem(this._closeItem); this.addMenuItem(this._closeItem);
this.actor.connect('destroy', this._onDestroy.bind(this)); this._metaWindow.connectObject(
'notify::minimized', this._updateMinimizeItem.bind(this),
'notify::maximized-horizontally', this._updateMaximizeItem.bind(this),
'notify::maximized-vertically', this._updateMaximizeItem.bind(this),
this);
this._updateMinimizeItem();
this._updateMaximizeItem();
this.connect('open-state-changed', () => { this.connect('open-state-changed', () => {
if (!this.isOpen) if (!this.isOpen)
@@ -101,12 +100,6 @@ class WindowContextMenu extends PopupMenu.PopupMenu {
this._maximizeItem.label.text = maximized this._maximizeItem.label.text = maximized
? _('Unmaximize') : _('Maximize'); ? _('Unmaximize') : _('Maximize');
} }
_onDestroy() {
this._metaWindow.disconnect(this._notifyMinimizedId);
this._metaWindow.disconnect(this._notifyMaximizedHId);
this._metaWindow.disconnect(this._notifyMaximizedVId);
}
} }
class WindowTitle extends St.BoxLayout { class WindowTitle extends St.BoxLayout {
@@ -130,20 +123,19 @@ class WindowTitle extends St.BoxLayout {
this.add(this.label_actor); this.add(this.label_actor);
this._textureCache = St.TextureCache.get_default(); this._textureCache = St.TextureCache.get_default();
this._iconThemeChangedId = this._textureCache.connect( this._textureCache.connectObject('icon-theme-changed',
'icon-theme-changed', this._updateIcon.bind(this)); () => this._updateIcon(), this);
this._notifyWmClass = this._metaWindow.connect_after(
'notify::wm-class', this._updateIcon.bind(this)); this._metaWindow.connectObject(
this._notifyAppId = this._metaWindow.connect_after( 'notify::wm-class',
'notify::gtk-application-id', this._updateIcon.bind(this)); () => this._updateIcon(), GObject.ConnectFlags.AFTER,
'notify::gtk-application-id',
() => this._updateIcon(), GObject.ConnectFlags.AFTER,
'notify::title', () => this._updateTitle(),
'notify::minimized', () => this._minimizedChanged(),
this);
this._updateIcon(); this._updateIcon();
this.connect('destroy', this._onDestroy.bind(this));
this._notifyTitleId = this._metaWindow.connect(
'notify::title', this._updateTitle.bind(this));
this._notifyMinimizedId = this._metaWindow.connect(
'notify::minimized', this._minimizedChanged.bind(this));
this._minimizedChanged(); this._minimizedChanged();
} }
@@ -173,14 +165,6 @@ class WindowTitle extends St.BoxLayout {
}); });
} }
} }
_onDestroy() {
this._textureCache.disconnect(this._iconThemeChangedId);
this._metaWindow.disconnect(this._notifyTitleId);
this._metaWindow.disconnect(this._notifyMinimizedId);
this._metaWindow.disconnect(this._notifyWmClass);
this._metaWindow.disconnect(this._notifyAppId);
}
} }
class BaseButton extends St.Button { class BaseButton extends St.Button {
@@ -216,16 +200,16 @@ class BaseButton extends St.Button {
this._contextMenuManager = new PopupMenu.PopupMenuManager(this); this._contextMenuManager = new PopupMenu.PopupMenuManager(this);
this._switchWorkspaceId = global.window_manager.connect( global.window_manager.connectObject('switch-workspace',
'switch-workspace', this._updateVisibility.bind(this)); () => this._updateVisibility(), this);
if (this._perMonitor) { if (this._perMonitor) {
this._windowEnteredMonitorId = global.display.connect( global.display.connectObject(
'window-entered-monitor', 'window-entered-monitor',
this._windowEnteredOrLeftMonitor.bind(this)); this._windowEnteredOrLeftMonitor.bind(this),
this._windowLeftMonitorId = global.display.connect(
'window-left-monitor', 'window-left-monitor',
this._windowEnteredOrLeftMonitor.bind(this)); this._windowEnteredOrLeftMonitor.bind(this),
this);
} }
this._tooltip = new Tooltip(this, { this._tooltip = new Tooltip(this, {
@@ -334,9 +318,11 @@ class BaseButton extends St.Button {
if (isOpen) if (isOpen)
return; return;
const extension = Extension.lookupByURL(import.meta.url);
let [x, y] = global.get_pointer(); let [x, y] = global.get_pointer();
let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
if (Me.stateObj.someWindowListContains(actor)) if (extension.someWindowListContains(actor))
actor.sync_hover(); actor.sync_hover();
} }
@@ -391,16 +377,6 @@ class BaseButton extends St.Button {
} }
_onDestroy() { _onDestroy() {
global.window_manager.disconnect(this._switchWorkspaceId);
if (this._windowEnteredMonitorId)
global.display.disconnect(this._windowEnteredMonitorId);
this._windowEnteredMonitorId = 0;
if (this._windowLeftMonitorId)
global.display.disconnect(this._windowLeftMonitorId);
this._windowLeftMonitorId = 0;
this._tooltip.destroy(); this._tooltip.destroy();
} }
} }
@@ -414,9 +390,11 @@ class WindowButton extends BaseButton {
super(perMonitor, monitorIndex); super(perMonitor, monitorIndex);
this.metaWindow = metaWindow; this.metaWindow = metaWindow;
this._skipTaskbarId = metaWindow.connect('notify::skip-taskbar', () => { metaWindow.connectObject(
this._updateVisibility(); 'notify::skip-taskbar', () => this._updateVisibility(),
}); 'workspace-changed', () => this._updateVisibility(),
this);
this._updateVisibility(); this._updateVisibility();
this._windowTitle = new WindowTitle(this.metaWindow); this._windowTitle = new WindowTitle(this.metaWindow);
@@ -430,11 +408,8 @@ class WindowButton extends BaseButton {
this._contextMenuManager.addMenu(this._contextMenu); this._contextMenuManager.addMenu(this._contextMenu);
Main.uiGroup.add_actor(this._contextMenu.actor); Main.uiGroup.add_actor(this._contextMenu.actor);
this._workspaceChangedId = this.metaWindow.connect( global.display.connectObject('notify::focus-window',
'workspace-changed', this._updateVisibility.bind(this)); () => this._updateStyle(), this);
this._notifyFocusId = global.display.connect(
'notify::focus-window', this._updateStyle.bind(this));
this._updateStyle(); this._updateStyle();
} }
@@ -478,9 +453,6 @@ class WindowButton extends BaseButton {
_onDestroy() { _onDestroy() {
super._onDestroy(); super._onDestroy();
this.metaWindow.disconnect(this._skipTaskbarId);
this.metaWindow.disconnect(this._workspaceChangedId);
global.display.disconnect(this._notifyFocusId);
this._contextMenu.destroy(); this._contextMenu.destroy();
} }
} }
@@ -597,18 +569,17 @@ class AppButton extends BaseButton {
Main.uiGroup.add_actor(this._appContextMenu.actor); Main.uiGroup.add_actor(this._appContextMenu.actor);
this._textureCache = St.TextureCache.get_default(); this._textureCache = St.TextureCache.get_default();
this._iconThemeChangedId = this._textureCache.connectObject('icon-theme-changed', () => {
this._textureCache.connect('icon-theme-changed', () => { this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE);
this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); }, this);
});
this._windowsChangedId = this.app.connect( this.app.connectObject('windows-changed',
'windows-changed', this._windowsChanged.bind(this)); () => this._windowsChanged(), this);
this._windowsChanged(); this._windowsChanged();
this._windowTracker = Shell.WindowTracker.get_default(); this._windowTracker = Shell.WindowTracker.get_default();
this._notifyFocusId = this._windowTracker.connect( this._windowTracker.connectObject('notify::focus-app',
'notify::focus-app', this._updateStyle.bind(this)); () => this._updateStyle(), this);
this._updateStyle(); this._updateStyle();
} }
@@ -728,9 +699,6 @@ class AppButton extends BaseButton {
_onDestroy() { _onDestroy() {
super._onDestroy(); super._onDestroy();
this._textureCache.disconnect(this._iconThemeChangedId);
this._windowTracker.disconnect(this._notifyFocusId);
this.app.disconnect(this._windowsChangedId);
this._menu.destroy(); this._menu.destroy();
} }
} }
@@ -740,7 +708,7 @@ class WindowList extends St.Widget {
GObject.registerClass(this); GObject.registerClass(this);
} }
constructor(perMonitor, monitor) { constructor(perMonitor, monitor, settings) {
super({ super({
name: 'panel', name: 'panel',
style_class: 'bottom-panel solid', style_class: 'bottom-panel solid',
@@ -787,12 +755,12 @@ class WindowList extends St.Widget {
indicatorsBox.add_child(this._workspaceIndicator.container); indicatorsBox.add_child(this._workspaceIndicator.container);
this._mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'}); this._mutterSettings = new Gio.Settings({schema_id: 'org.gnome.mutter'});
this._workspacesOnlyOnPrimaryChangedId = this._mutterSettings.connect( this._mutterSettings.connectObject(
'changed::workspaces-only-on-primary', 'changed::workspaces-only-on-primary',
this._updateWorkspaceIndicatorVisibility.bind(this)); () => this._updateWorkspaceIndicatorVisibility(),
this._dynamicWorkspacesChangedId = this._mutterSettings.connect(
'changed::dynamic-workspaces', 'changed::dynamic-workspaces',
this._updateWorkspaceIndicatorVisibility.bind(this)); () => this._updateWorkspaceIndicatorVisibility(),
this);
this._updateWorkspaceIndicatorVisibility(); this._updateWorkspaceIndicatorVisibility();
this._menuManager = new PopupMenu.PopupMenuManager(this); this._menuManager = new PopupMenu.PopupMenuManager(this);
@@ -810,59 +778,58 @@ class WindowList extends St.Widget {
this._updatePosition(); this._updatePosition();
this._appSystem = Shell.AppSystem.get_default(); this._appSystem = Shell.AppSystem.get_default();
this._appStateChangedId = this._appSystem.connect( this._appSystem.connectObject('app-state-changed',
'app-state-changed', this._onAppStateChanged.bind(this)); this._onAppStateChanged.bind(this), this);
// Hack: OSK gesture is tied to visibility, piggy-back on that // Hack: OSK gesture is tied to visibility, piggy-back on that
this._keyboardVisiblechangedId = Main.keyboard._bottomDragAction.connectObject('notify::enabled',
Main.keyboard._bottomDragAction.connect('notify::enabled', action => {
action => { const visible = !action.enabled;
const visible = !action.enabled; if (visible) {
if (visible) { Main.uiGroup.set_child_above_sibling(
Main.uiGroup.set_child_above_sibling( this, Main.layoutManager.keyboardBox);
this, Main.layoutManager.keyboardBox); } else {
} else { Main.uiGroup.set_child_above_sibling(
Main.uiGroup.set_child_above_sibling( this, Main.layoutManager.panelBox);
this, Main.layoutManager.panelBox); }
} this._updateKeyboardAnchor();
this._updateKeyboardAnchor(); }, this);
});
let workspaceManager = global.workspace_manager; let workspaceManager = global.workspace_manager;
this._nWorkspacesChangedId = workspaceManager.connect( workspaceManager.connectObject('notify::n-workspaces',
'notify::n-workspaces', this._updateWorkspaceIndicatorVisibility.bind(this)); () => this._updateWorkspaceIndicatorVisibility(), this);
this._updateWorkspaceIndicatorVisibility(); this._updateWorkspaceIndicatorVisibility();
this._switchWorkspaceId = global.window_manager.connect( global.window_manager.connectObject('switch-workspace',
'switch-workspace', this._checkGrouping.bind(this)); () => this._checkGrouping(), this);
this._overviewShowingId = Main.overview.connect('showing', () => { Main.overview.connectObject(
this.hide(); 'showing', () => {
this._updateKeyboardAnchor(); this.hide();
});
this._overviewHidingId = Main.overview.connect('hidden', () => {
this.visible = !this._monitor.inFullscreen;
this._updateKeyboardAnchor();
});
this._fullscreenChangedId =
global.display.connect('in-fullscreen-changed', () => {
// Work-around for initial change from unknown to !fullscreen
if (Main.overview.visible)
this.hide();
this._updateKeyboardAnchor(); this._updateKeyboardAnchor();
}); },
'hidden', () => {
this.visible = !this._monitor.inFullscreen;
this._updateKeyboardAnchor();
}, this);
global.display.connectObject('in-fullscreen-changed', () => {
// Work-around for initial change from unknown to !fullscreen
if (Main.overview.visible)
this.hide();
this._updateKeyboardAnchor();
}, this);
this._windowSignals = new Map(); this._windowSignals = new Map();
this._windowCreatedId = global.display.connect( this._windowCreatedId = global.display.connect(
'window-created', (dsp, win) => this._addWindow(win)); 'window-created', (dsp, win) => this._addWindow(win));
this._dragBeginId = Main.xdndHandler.connect('drag-begin', Main.xdndHandler.connectObject(
this._monitorDrag.bind(this)); 'drag-begin', () => this._monitorDrag(),
this._dragEndId = Main.xdndHandler.connect('drag-end', 'drag-end', () => this._stopMonitoringDrag(),
this._stopMonitoringDrag.bind(this)); this);
this._dragMonitor = { this._dragMonitor = {
dragMotion: this._onDragMotion.bind(this), dragMotion: this._onDragMotion.bind(this),
}; };
@@ -870,7 +837,7 @@ class WindowList extends St.Widget {
this._dndTimeoutId = 0; this._dndTimeoutId = 0;
this._dndWindow = null; this._dndWindow = null;
this._settings = ExtensionUtils.getSettings(); this._settings = settings;
this._settings.connect('changed::grouping-mode', this._settings.connect('changed::grouping-mode',
() => this._groupingModeChanged()); () => this._groupingModeChanged());
this._grouped = undefined; this._grouped = undefined;
@@ -911,7 +878,8 @@ class WindowList extends St.Widget {
} }
_updateWindowListVisibility() { _updateWindowListVisibility() {
let visible = !Main.windowPicker.visible; const {windowPicker} = Extension.lookupByURL(import.meta.url);
const visible = !windowPicker.visible;
this._windowList.ease({ this._windowList.ease({
opacity: visible ? 255 : 0, opacity: visible ? 255 : 0,
@@ -1109,37 +1077,14 @@ class WindowList extends St.Widget {
} }
_onDestroy() { _onDestroy() {
this._mutterSettings.disconnect(this._workspacesOnlyOnPrimaryChangedId);
this._mutterSettings.disconnect(this._dynamicWorkspacesChangedId);
this._workspaceIndicator.destroy(); this._workspaceIndicator.destroy();
Main.ctrlAltTabManager.removeGroup(this); Main.ctrlAltTabManager.removeGroup(this);
this._appSystem.disconnect(this._appStateChangedId);
this._appStateChangedId = 0;
Main.keyboard._bottomDragAction.disconnect(this._keyboardVisiblechangedId);
this._keyboardVisiblechangedId = 0;
global.workspace_manager.disconnect(this._nWorkspacesChangedId);
this._nWorkspacesChangedId = 0;
global.window_manager.disconnect(this._switchWorkspaceId);
this._switchWorkspaceId = 0;
this._windowSignals.forEach((id, win) => win.disconnect(id)); this._windowSignals.forEach((id, win) => win.disconnect(id));
this._windowSignals.clear(); this._windowSignals.clear();
Main.overview.disconnect(this._overviewShowingId);
Main.overview.disconnect(this._overviewHidingId);
global.display.disconnect(this._fullscreenChangedId);
global.display.disconnect(this._windowCreatedId);
this._stopMonitoringDrag(); this._stopMonitoringDrag();
Main.xdndHandler.disconnect(this._dragBeginId);
Main.xdndHandler.disconnect(this._dragEndId);
this._settings.run_dispose(); this._settings.run_dispose();
@@ -1149,9 +1094,9 @@ class WindowList extends St.Widget {
} }
} }
class Extension { export default class WindowListExtension extends Extension {
constructor() { constructor(metadata) {
ExtensionUtils.initTranslations(); super(metadata);
this._windowLists = null; this._windowLists = null;
this._hideOverviewOrig = Main.overview.hide; this._hideOverviewOrig = Main.overview.hide;
@@ -1160,17 +1105,17 @@ class Extension {
enable() { enable() {
this._windowLists = []; this._windowLists = [];
this._settings = ExtensionUtils.getSettings(); this._settings = this.getSettings();
this._showOnAllMonitorsChangedId = this._settings.connect( this._settings.connectObject('changed::show-on-all-monitors',
'changed::show-on-all-monitors', this._buildWindowLists.bind(this)); () => this._buildWindowLists(), this);
this._monitorsChangedId = Main.layoutManager.connect( Main.layoutManager.connectObject('monitors-changed',
'monitors-changed', this._buildWindowLists.bind(this)); () => this._buildWindowLists(), this);
Main.windowPicker = new WindowPicker(); this.windowPicker = new WindowPicker();
Main.overview.hide = () => { Main.overview.hide = () => {
Main.windowPicker.close(); this.windowPicker.close();
this._hideOverviewOrig.call(Main.overview); this._hideOverviewOrig.call(Main.overview);
}; };
@@ -1185,7 +1130,7 @@ class Extension {
Main.layoutManager.monitors.forEach(monitor => { Main.layoutManager.monitors.forEach(monitor => {
if (showOnAllMonitors || monitor === Main.layoutManager.primaryMonitor) if (showOnAllMonitors || monitor === Main.layoutManager.primaryMonitor)
this._windowLists.push(new WindowList(showOnAllMonitors, monitor)); this._windowLists.push(new WindowList(showOnAllMonitors, monitor, this.getSettings()));
}); });
} }
@@ -1193,11 +1138,8 @@ class Extension {
if (!this._windowLists) if (!this._windowLists)
return; return;
this._settings.disconnect(this._showOnAllMonitorsChangedId); this._settings.disconnectObject(this);
this._showOnAllMonitorsChangedId = 0; Main.layoutManager.disconnectObject(this);
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
this._windowLists.forEach(windowList => { this._windowLists.forEach(windowList => {
windowList.hide(); windowList.hide();
@@ -1205,8 +1147,8 @@ class Extension {
}); });
this._windowLists = null; this._windowLists = null;
Main.windowPicker.destroy(); this.windowPicker.destroy();
delete Main.windowPicker; delete this.windowPicker;
Main.overview.hide = this._hideOverviewOrig; Main.overview.hide = this._hideOverviewOrig;
} }
@@ -1279,10 +1221,3 @@ class Tooltip extends St.Label {
}); });
} }
} }
/**
* @returns {Extension} - the extension's state object
*/
function init() {
return new Extension();
}

View File

@@ -1,29 +1,24 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported init buildPrefsWidget */ import Adw from 'gi://Adw';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
const {Adw, Gio, GLib, GObject, Gtk} = imports.gi; import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const ExtensionUtils = imports.misc.extensionUtils;
const _ = ExtensionUtils.gettext;
/** */
function init() {
ExtensionUtils.initTranslations();
}
class WindowListPrefsWidget extends Adw.PreferencesPage { class WindowListPrefsWidget extends Adw.PreferencesPage {
static { static {
GObject.registerClass(this); GObject.registerClass(this);
} }
constructor() { constructor(settings) {
super(); super();
this._actionGroup = new Gio.SimpleActionGroup(); this._actionGroup = new Gio.SimpleActionGroup();
this.insert_action_group('window-list', this._actionGroup); this.insert_action_group('window-list', this._actionGroup);
this._settings = ExtensionUtils.getSettings(); this._settings = settings;
this._actionGroup.add_action( this._actionGroup.add_action(
this._settings.create_action('grouping-mode')); this._settings.create_action('grouping-mode'));
this._actionGroup.add_action( this._actionGroup.add_action(
@@ -84,9 +79,8 @@ class WindowListPrefsWidget extends Adw.PreferencesPage {
} }
} }
/** export default class WindowListPrefs extends ExtensionPreferences {
* @returns {Gtk.Widget} - the prefs widget getPreferencesWidget() {
*/ return new WindowListPrefsWidget(this.getSettings());
function buildPrefsWidget() { }
return new WindowListPrefsWidget();
} }

View File

@@ -1,17 +1,20 @@
/* exported WindowPicker, WindowPickerToggle */ import Clutter from 'gi://Clutter';
const {Clutter, GObject, Shell, St} = imports.gi; import GObject from 'gi://GObject';
import Shell from 'gi://Shell';
import St from 'gi://St';
const Layout = imports.ui.layout; import {Extension, InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main; import * as Layout from 'resource:///org/gnome/shell/ui/layout.js';
const {WorkspacesDisplay} = imports.ui.workspacesView; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Workspace = imports.ui.workspace; import {WorkspacesDisplay} from 'resource:///org/gnome/shell/ui/workspacesView.js';
import * as Workspace from 'resource:///org/gnome/shell/ui/workspace.js';
const {VIGNETTE_BRIGHTNESS} = imports.ui.lightbox; import {VIGNETTE_BRIGHTNESS} from 'resource:///org/gnome/shell/ui/lightbox.js';
const { import {
SIDE_CONTROLS_ANIMATION_TIME, SIDE_CONTROLS_ANIMATION_TIME,
OverviewAdjustment, OverviewAdjustment,
ControlsState, ControlsState
} = imports.ui.overviewControls; } from 'resource:///org/gnome/shell/ui/overviewControls.js';
class MyWorkspacesDisplay extends WorkspacesDisplay { class MyWorkspacesDisplay extends WorkspacesDisplay {
static { static {
@@ -32,12 +35,13 @@ class MyWorkspacesDisplay extends WorkspacesDisplay {
super(controls, workspaceAdjustment, overviewAdjustment); super(controls, workspaceAdjustment, overviewAdjustment);
this._windowPicker = controls;
this._workspaceAdjustment = workspaceAdjustment; this._workspaceAdjustment = workspaceAdjustment;
this._workspaceAdjustment.actor = this; this._workspaceAdjustment.actor = this;
this._nWorkspacesChangedId = workspaceManager.connectObject('notify::n-workspaces',
workspaceManager.connect('notify::n-workspaces', () => this._updateAdjustment(), this);
this._updateAdjustment.bind(this));
this.add_constraint( this.add_constraint(
new Layout.MonitorConstraint({ new Layout.MonitorConstraint({
@@ -48,7 +52,7 @@ class MyWorkspacesDisplay extends WorkspacesDisplay {
prepareToEnterOverview(...args) { prepareToEnterOverview(...args) {
if (!this._scrollEventId) { if (!this._scrollEventId) {
this._scrollEventId = Main.windowPicker.connect('scroll-event', this._scrollEventId = this._windowPicker.connect('scroll-event',
this._onScrollEvent.bind(this)); this._onScrollEvent.bind(this));
} }
@@ -57,7 +61,7 @@ class MyWorkspacesDisplay extends WorkspacesDisplay {
vfunc_hide(...args) { vfunc_hide(...args) {
if (this._scrollEventId > 0) if (this._scrollEventId > 0)
Main.windowPicker.disconnect(this._scrollEventId); this._windowPicker.disconnect(this._scrollEventId);
this._scrollEventId = 0; this._scrollEventId = 0;
super.vfunc_hide(...args); super.vfunc_hide(...args);
@@ -70,86 +74,9 @@ class MyWorkspacesDisplay extends WorkspacesDisplay {
value: workspaceManager.get_active_workspace_index(), value: workspaceManager.get_active_workspace_index(),
}); });
} }
_onDestroy() {
if (this._nWorkspacesChangedId)
global.workspace_manager.disconnect(this._nWorkspacesChangedId);
this._nWorkspacesChangedId = 0;
super._onDestroy();
}
} }
class MyWorkspace extends Workspace.Workspace { export class WindowPicker extends Clutter.Actor {
static {
GObject.registerClass(this);
}
constructor(...args) {
super(...args);
this._adjChangedId =
this._overviewAdjustment.connect('notify::value', () => {
const {value: progress} = this._overviewAdjustment;
const brightness = 1 - (1 - VIGNETTE_BRIGHTNESS) * progress;
for (const bg of this._background?._backgroundGroup ?? []) {
bg.content.set({
vignette: true,
brightness,
});
}
});
}
_onDestroy() {
super._onDestroy();
if (this._adjChangedId)
this._overviewAdjustment.disconnect(this._adjChangedId);
this._adjChangedId = 0;
}
}
class MyWorkspaceBackground extends Workspace.WorkspaceBackground {
static {
GObject.registerClass(this);
}
_updateBorderRadius() {
}
vfunc_allocate(box) {
this.set_allocation(box);
const themeNode = this.get_theme_node();
const contentBox = themeNode.get_content_box(box);
this._bin.allocate(contentBox);
const [contentWidth, contentHeight] = contentBox.get_size();
const monitor = Main.layoutManager.monitors[this._monitorIndex];
const xRatio = contentWidth / this._workarea.width;
const yRatio = contentHeight / this._workarea.height;
const right = area => area.x + area.width;
const bottom = area => area.y + area.height;
const offsets = {
left: xRatio * (this._workarea.x - monitor.x),
right: xRatio * (right(monitor) - right(this._workarea)),
top: yRatio * (this._workarea.y - monitor.y),
bottom: yRatio * (bottom(monitor) - bottom(this._workarea)),
};
contentBox.set_origin(-offsets.left, -offsets.top);
contentBox.set_size(
offsets.left + contentWidth + offsets.right,
offsets.top + contentHeight + offsets.bottom);
this._backgroundGroup.allocate(contentBox);
}
}
var WindowPicker = class WindowPicker extends Clutter.Actor {
static [GObject.signals] = { static [GObject.signals] = {
'open-state-changed': {param_types: [GObject.TYPE_BOOLEAN]}, 'open-state-changed': {param_types: [GObject.TYPE_BOOLEAN]},
}; };
@@ -164,11 +91,11 @@ var WindowPicker = class WindowPicker extends Clutter.Actor {
this._visible = false; this._visible = false;
this._modal = false; this._modal = false;
this._overlayKeyId = 0;
this._stageKeyPressId = 0; this._stageKeyPressId = 0;
this._adjustment = new OverviewAdjustment(this); this._adjustment = new OverviewAdjustment(this);
this._injectionManager = new InjectionManager();
this.connect('destroy', this._onDestroy.bind(this)); this.connect('destroy', this._onDestroy.bind(this));
global.bind_property('screen-width', global.bind_property('screen-width',
@@ -186,21 +113,78 @@ var WindowPicker = class WindowPicker extends Clutter.Actor {
if (!Main.sessionMode.hasOverview) { if (!Main.sessionMode.hasOverview) {
this._injectBackgroundShade(); this._injectBackgroundShade();
this._overlayKeyId = global.display.connect('overlay-key', () => { global.display.connectObject('overlay-key', () => {
if (!this._visible) if (!this._visible)
this.open(); this.open();
else else
this.close(); this.close();
}); }, this);
} }
} }
_injectBackgroundShade() { _injectBackgroundShade() {
this._origWorkspace = Workspace.Workspace; const backgroundProto = Workspace.WorkspaceBackground.prototype;
this._origWorkspaceBackground = Workspace.WorkspaceBackground; this._injectionManager.overrideMethod(backgroundProto, '_updateBorderRadius',
() => {
return function () {};
});
this._injectionManager.overrideMethod(backgroundProto, 'vfunc_allocate',
() => {
/* eslint-disable no-invalid-this */
return function (box) {
this.set_allocation(box);
Workspace.Workspace = MyWorkspace; const themeNode = this.get_theme_node();
Workspace.WorkspaceBackground = MyWorkspaceBackground; const contentBox = themeNode.get_content_box(box);
this._bin.allocate(contentBox);
const [contentWidth, contentHeight] = contentBox.get_size();
const monitor = Main.layoutManager.monitors[this._monitorIndex];
const xRatio = contentWidth / this._workarea.width;
const yRatio = contentHeight / this._workarea.height;
const right = area => area.x + area.width;
const bottom = area => area.y + area.height;
const offsets = {
left: xRatio * (this._workarea.x - monitor.x),
right: xRatio * (right(monitor) - right(this._workarea)),
top: yRatio * (this._workarea.y - monitor.y),
bottom: yRatio * (bottom(monitor) - bottom(this._workarea)),
};
contentBox.set_origin(-offsets.left, -offsets.top);
contentBox.set_size(
offsets.left + contentWidth + offsets.right,
offsets.top + contentHeight + offsets.bottom);
this._backgroundGroup.allocate(contentBox);
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(backgroundProto, 'vfunc_parent_set',
() => {
/* eslint-disable no-invalid-this */
return function () {
setTimeout(() => {
const parent = this.get_parent();
if (!parent)
return;
parent._overviewAdjustment.connectObject('notify::value', () => {
const {value: progress} = parent._overviewAdjustment;
const brightness = 1 - (1 - VIGNETTE_BRIGHTNESS) * progress;
for (const bg of this._backgroundGroup ?? []) {
bg.content.set({
vignette: true,
brightness,
});
}
}, this);
});
};
/* eslint-enable */
});
} }
get visible() { get visible() {
@@ -306,27 +290,15 @@ var WindowPicker = class WindowPicker extends Clutter.Actor {
} }
_onDestroy() { _onDestroy() {
if (this._origWorkspace) this._injectionManager.clear();
Workspace.Workspace = this._origWorkspace;
if (this._origWorkspaceBackground)
Workspace.WorkspaceBackground = this._origWorkspaceBackground;
if (this._monitorsChangedId)
Main.layoutManager.disconnect(this._monitorsChangedId);
this._monitorsChangedId = 0;
if (this._overlayKeyId)
global.display.disconnect(this._overlayKeyId);
this._overlayKeyId = 0;
if (this._stageKeyPressId) if (this._stageKeyPressId)
global.stage.disconnect(this._stageKeyPressId); global.stage.disconnect(this._stageKeyPressId);
this._stageKeyPressId = 0; this._stageKeyPressId = 0;
} }
}; }
var WindowPickerToggle = class WindowPickerToggle extends St.Button { export class WindowPickerToggle extends St.Button {
static { static {
GObject.registerClass(this); GObject.registerClass(this);
} }
@@ -350,15 +322,16 @@ var WindowPickerToggle = class WindowPickerToggle extends St.Button {
toggle_mode: true, toggle_mode: true,
}); });
const {windowPicker} = Extension.lookupByURL(import.meta.url);
this.connect('notify::checked', () => { this.connect('notify::checked', () => {
if (this.checked) if (this.checked)
Main.windowPicker.open(); windowPicker.open();
else else
Main.windowPicker.close(); windowPicker.close();
}); });
Main.windowPicker.connect('open-state-changed', () => { windowPicker.connect('open-state-changed', () => {
this.checked = Main.windowPicker.visible; this.checked = windowPicker.visible;
}); });
} }
}; }

View File

@@ -1,13 +1,15 @@
/* exported WorkspaceIndicator */ import Clutter from 'gi://Clutter';
const {Clutter, Gio, GObject, Meta, St} = imports.gi; import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import St from 'gi://St';
const DND = imports.ui.dnd; import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const _ = ExtensionUtils.gettext; import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
const TOOLTIP_OFFSET = 6; const TOOLTIP_OFFSET = 6;
const TOOLTIP_ANIMATION_TIME = 150; const TOOLTIP_ANIMATION_TIME = 150;
@@ -29,20 +31,17 @@ class WindowPreview extends St.Button {
this._window = window; this._window = window;
this.connect('destroy', this._onDestroy.bind(this)); this._window.connectObject(
'size-changed', () => this.queue_relayout(),
this._sizeChangedId = this._window.connect('size-changed', 'position-changed', () => {
() => this.queue_relayout());
this._positionChangedId = this._window.connect('position-changed',
() => {
this._updateVisible(); this._updateVisible();
this.queue_relayout(); this.queue_relayout();
}); },
this._minimizedChangedId = this._window.connect('notify::minimized', 'notify::minimized', this._updateVisible.bind(this),
this._updateVisible.bind(this)); this);
this._focusChangedId = global.display.connect('notify::focus-window', global.display.connectObject('notify::focus-window',
this._onFocusChanged.bind(this)); this._onFocusChanged.bind(this), this);
this._onFocusChanged(); this._onFocusChanged();
} }
@@ -51,13 +50,6 @@ class WindowPreview extends St.Button {
return this._window; return this._window;
} }
_onDestroy() {
this._window.disconnect(this._sizeChangedId);
this._window.disconnect(this._positionChangedId);
this._window.disconnect(this._minimizedChangedId);
global.display.disconnect(this._focusChangedId);
}
_onFocusChanged() { _onFocusChanged() {
if (global.display.focus_window === this._window) if (global.display.focus_window === this._window)
this.add_style_class_name('active'); this.add_style_class_name('active');
@@ -138,16 +130,13 @@ class WorkspaceThumbnail extends St.Button {
let workspaceManager = global.workspace_manager; let workspaceManager = global.workspace_manager;
this._workspace = workspaceManager.get_workspace_by_index(index); this._workspace = workspaceManager.get_workspace_by_index(index);
this._windowAddedId = this._workspace.connect('window-added', this._workspace.connectObject(
(ws, window) => { 'window-added', (ws, window) => this._addWindow(window),
this._addWindow(window); 'window-removed', (ws, window) => this._removeWindow(window),
}); this);
this._windowRemovedId = this._workspace.connect('window-removed',
(ws, window) => { global.display.connectObject('restacked',
this._removeWindow(window); this._onRestacked.bind(this), this);
});
this._restackedId = global.display.connect('restacked',
this._onRestacked.bind(this));
this._workspace.list_windows().forEach(w => this._addWindow(w)); this._workspace.list_windows().forEach(w => this._addWindow(w));
this._onRestacked(); this._onRestacked();
@@ -245,14 +234,10 @@ class WorkspaceThumbnail extends St.Button {
_onDestroy() { _onDestroy() {
this._tooltip.destroy(); this._tooltip.destroy();
this._workspace.disconnect(this._windowAddedId);
this._workspace.disconnect(this._windowRemovedId);
global.display.disconnect(this._restackedId);
} }
} }
var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { export class WorkspaceIndicator extends PanelMenu.Button {
static { static {
GObject.registerClass(this); GObject.registerClass(this);
} }
@@ -295,14 +280,11 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button {
this._workspacesItems = []; this._workspacesItems = [];
this._workspaceManagerSignals = [ workspaceManager.connectObject(
workspaceManager.connect('notify::n-workspaces', 'notify::n-workspaces', this._nWorkspacesChanged.bind(this), GObject.ConnectFlags.AFTER,
this._nWorkspacesChanged.bind(this)), 'workspace-switched', this._onWorkspaceSwitched.bind(this), GObject.ConnectFlags.AFTER,
workspaceManager.connect_after('workspace-switched', 'notify::layout-rows', this._updateThumbnailVisibility.bind(this),
this._onWorkspaceSwitched.bind(this)), this);
workspaceManager.connect('notify::layout-rows',
this._updateThumbnailVisibility.bind(this)),
];
this.connect('scroll-event', this._onScrollEvent.bind(this)); this.connect('scroll-event', this._onScrollEvent.bind(this));
this._updateMenu(); this._updateMenu();
@@ -310,20 +292,8 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button {
this._updateThumbnailVisibility(); this._updateThumbnailVisibility();
this._settings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); this._settings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'});
this._settingsChangedId = this._settings.connect( this._settings.connectObject('changed::workspace-names',
'changed::workspace-names', this._updateMenuLabels.bind(this)); () => this._updateMenuLabels(), this);
}
_onDestroy() {
for (let i = 0; i < this._workspaceManagerSignals.length; i++)
global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
if (this._settingsChangedId) {
this._settings.disconnect(this._settingsChangedId);
this._settingsChangedId = 0;
}
super._onDestroy();
} }
_updateThumbnailVisibility() { _updateThumbnailVisibility() {
@@ -447,4 +417,4 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button {
let newIndex = this._currentWorkspace + diff; let newIndex = this._currentWorkspace + diff;
this._activate(newIndex); this._activate(newIndex);
} }
}; }

View File

@@ -1,276 +1,290 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported init */ import Clutter from 'gi://Clutter';
const {Clutter, Graphene, GObject, St} = imports.gi; import Graphene from 'gi://Graphene';
import St from 'gi://St';
const Main = imports.ui.main; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const OverviewControls = imports.ui.overviewControls; import * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js';
const Workspace = imports.ui.workspace; import {InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
const WorkspacesView = imports.ui.workspacesView; import {WindowPreview} from 'resource:///org/gnome/shell/ui/windowPreview.js';
import {Workspace} from 'resource:///org/gnome/shell/ui/workspace.js';
import {WorkspacesView} from 'resource:///org/gnome/shell/ui/workspacesView.js';
const WINDOW_SLOT = 4; const WINDOW_SLOT = 4;
class MyWorkspace extends Workspace.Workspace { export default class Extension {
static {
GObject.registerClass(this);
}
constructor(...args) {
super(...args);
if (this.metaWorkspace && this.metaWorkspace.index() < 9) {
this._tip = new St.Label({
style_class: 'extension-windowsNavigator-window-tooltip',
visible: false,
});
this.add_actor(this._tip);
this.connect('notify::scale-x', () => {
this._tip.set_scale(1 / this.scale_x, 1 / this.scale_x);
});
} else {
this._tip = null;
}
}
vfunc_allocate(box) {
super.vfunc_allocate(box);
if (this._tip)
this._tip.allocate_preferred_size(0, 0);
}
showTooltip() {
if (!this._tip)
return;
this._tip.text = (this.metaWorkspace.index() + 1).toString();
this._tip.show();
this.set_child_below_sibling(this._tip, null);
}
hideTooltip() {
if (this._tip)
this._tip.hide();
}
getWindowWithTooltip(id) {
const {layoutManager} = this._container;
const slot = layoutManager._windowSlots[id - 1];
return slot ? slot[WINDOW_SLOT].metaWindow : null;
}
showWindowsTooltips() {
const {layoutManager} = this._container;
for (let i = 0; i < layoutManager._windowSlots.length; i++) {
if (layoutManager._windowSlots[i])
layoutManager._windowSlots[i][WINDOW_SLOT].showTooltip(`${i + 1}`);
}
}
hideWindowsTooltips() {
const {layoutManager} = this._container;
for (let i in layoutManager._windowSlots) {
if (layoutManager._windowSlots[i])
layoutManager._windowSlots[i][WINDOW_SLOT].hideTooltip();
}
}
// overriding _addWindowClone to apply the tooltip patch on the cloned
// windowPreview
_addWindowClone(metaWindow) {
const clone = super._addWindowClone(metaWindow);
// appling the tooltip patch
(function patchPreview() {
this._text = new St.Label({
style_class: 'extension-windowsNavigator-window-tooltip',
visible: false,
});
this._text.add_constraint(new Clutter.BindConstraint({
source: this.windowContainer,
coordinate: Clutter.BindCoordinate.POSITION,
}));
this._text.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.X_AXIS,
pivot_point: new Graphene.Point({x: 0.5, y: -1}),
factor: this._closeButtonSide === St.Side.LEFT ? 1 : 0,
}));
this._text.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.Y_AXIS,
pivot_point: new Graphene.Point({x: -1, y: 0.5}),
factor: 0,
}));
this.add_child(this._text);
}).call(clone);
clone.showTooltip = function (text) {
this._text.set({text});
this._text.show();
};
clone.hideTooltip = function () {
if (this._text && this._text.visible)
this._text.hide();
};
return clone;
}
}
class MyWorkspacesView extends WorkspacesView.WorkspacesView {
static {
GObject.registerClass(this);
}
constructor(...args) {
super(...args);
this._pickWorkspace = false;
this._pickWindow = false;
this._keyPressEventId =
global.stage.connect('key-press-event', this._onKeyPress.bind(this));
this._keyReleaseEventId =
global.stage.connect('key-release-event', this._onKeyRelease.bind(this));
}
_onDestroy() {
super._onDestroy();
global.stage.disconnect(this._keyPressEventId);
global.stage.disconnect(this._keyReleaseEventId);
}
_hideTooltips() {
if (global.stage.get_key_focus() === global.stage)
global.stage.set_key_focus(this._prevFocusActor);
this._pickWindow = false;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].hideWindowsTooltips();
}
_hideWorkspacesTooltips() {
global.stage.set_key_focus(this._prevFocusActor);
this._pickWorkspace = false;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].hideTooltip();
}
_onKeyRelease(s, o) {
if (this._pickWindow &&
(o.get_key_symbol() === Clutter.KEY_Alt_L ||
o.get_key_symbol() === Clutter.KEY_Alt_R))
this._hideTooltips();
if (this._pickWorkspace &&
(o.get_key_symbol() === Clutter.KEY_Control_L ||
o.get_key_symbol() === Clutter.KEY_Control_R))
this._hideWorkspacesTooltips();
}
_onKeyPress(s, o) {
const {ControlsState} = OverviewControls;
if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER)
return false;
let workspaceManager = global.workspace_manager;
if ((o.get_key_symbol() === Clutter.KEY_Alt_L ||
o.get_key_symbol() === Clutter.KEY_Alt_R) &&
!this._pickWorkspace) {
this._prevFocusActor = global.stage.get_key_focus();
global.stage.set_key_focus(null);
this._active = workspaceManager.get_active_workspace_index();
this._pickWindow = true;
this._workspaces[workspaceManager.get_active_workspace_index()].showWindowsTooltips();
return true;
}
if ((o.get_key_symbol() === Clutter.KEY_Control_L ||
o.get_key_symbol() === Clutter.KEY_Control_R) &&
!this._pickWindow) {
this._prevFocusActor = global.stage.get_key_focus();
global.stage.set_key_focus(null);
this._pickWorkspace = true;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].showTooltip();
return true;
}
if (global.stage.get_key_focus() !== global.stage)
return false;
// ignore shift presses, they're required to get numerals in azerty keyboards
if ((this._pickWindow || this._pickWorkspace) &&
(o.get_key_symbol() === Clutter.KEY_Shift_L ||
o.get_key_symbol() === Clutter.KEY_Shift_R))
return true;
if (this._pickWindow) {
if (this._active !== workspaceManager.get_active_workspace_index()) {
this._hideTooltips();
return false;
}
let c = o.get_key_symbol() - Clutter.KEY_KP_0;
if (c > 9 || c <= 0) {
c = o.get_key_symbol() - Clutter.KEY_0;
if (c > 9 || c <= 0) {
this._hideTooltips();
global.log(c);
return false;
}
}
let win = this._workspaces[this._active].getWindowWithTooltip(c);
this._hideTooltips();
if (win)
Main.activateWindow(win, global.get_current_time());
return true;
}
if (this._pickWorkspace) {
let c = o.get_key_symbol() - Clutter.KEY_KP_0;
if (c > 9 || c <= 0) {
c = o.get_key_symbol() - Clutter.KEY_0;
if (c > 9 || c <= 0) {
this._hideWorkspacesTooltips();
return false;
}
}
let workspace = this._workspaces[c - 1];
if (workspace !== undefined)
workspace.metaWorkspace.activate(global.get_current_time());
this._hideWorkspacesTooltips();
return true;
}
return false;
}
}
class Extension {
constructor() { constructor() {
this._origWorkspace = Workspace.Workspace; this._injectionManager = new InjectionManager();
this._origWorkspacesView = WorkspacesView.WorkspacesView;
} }
enable() { enable() {
Workspace.Workspace = MyWorkspace; const previewProto = WindowPreview.prototype;
WorkspacesView.WorkspacesView = MyWorkspacesView;
this._injectionManager.overrideMethod(previewProto, '_init', originalMethod => {
/* eslint-disable no-invalid-this */
return function (...args) {
originalMethod.call(this, ...args);
this._text = new St.Label({
style_class: 'extension-windowsNavigator-window-tooltip',
visible: false,
});
this._text.add_constraint(new Clutter.BindConstraint({
source: this.windowContainer,
coordinate: Clutter.BindCoordinate.POSITION,
}));
this._text.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.X_AXIS,
pivot_point: new Graphene.Point({x: 0.5, y: -1}),
factor: this._closeButtonSide === St.Side.LEFT ? 1 : 0,
}));
this._text.add_constraint(new Clutter.AlignConstraint({
source: this.windowContainer,
align_axis: Clutter.AlignAxis.Y_AXIS,
pivot_point: new Graphene.Point({x: -1, y: 0.5}),
factor: 0,
}));
this.add_child(this._text);
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(previewProto, 'showTooltip', () => {
/* eslint-disable no-invalid-this */
return function (text) {
this._text.set({text});
this._text.show();
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(previewProto, 'hideTooltip', () => {
/* eslint-disable no-invalid-this */
return function () {
this._text?.hide();
};
/* eslint-enable */
});
const workspaceProto = Workspace.prototype;
this._injectionManager.overrideMethod(workspaceProto, '_init', originalMethod => {
/* eslint-disable no-invalid-this */
return function (...args) {
originalMethod.call(this, ...args);
if (this.metaWorkspace && this.metaWorkspace.index() < 9) {
this._tip = new St.Label({
style_class: 'extension-windowsNavigator-window-tooltip',
visible: false,
});
this.add_actor(this._tip);
this.connect('notify::scale-x', () => {
this._tip.set_scale(1 / this.scale_x, 1 / this.scale_x);
});
} else {
this._tip = null;
}
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'vfunc_allocate', originalMethod => {
/* eslint-disable no-invalid-this */
return function (box) {
originalMethod.call(this, box);
this._tip?.allocate_preferred_size(0, 0);
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'showTooltip', () => {
/* eslint-disable no-invalid-this */
return function () {
if (!this._tip)
return;
this._tip.text = (this.metaWorkspace.index() + 1).toString();
this._tip.show();
this.set_child_below_sibling(this._tip, null);
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'hideTooltip', () => {
/* eslint-disable no-invalid-this */
return function () {
this._tip?.hide();
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'getWindowWithTooltip', () => {
/* eslint-disable no-invalid-this */
return function (id) {
const {layoutManager} = this._container;
const slot = layoutManager._windowSlots[id - 1];
return slot ? slot[WINDOW_SLOT].metaWindow : null;
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'showWindowsTooltips', () => {
/* eslint-disable no-invalid-this */
return function () {
const {layoutManager} = this._container;
for (let i = 0; i < layoutManager._windowSlots.length; i++) {
if (layoutManager._windowSlots[i])
layoutManager._windowSlots[i][WINDOW_SLOT].showTooltip(`${i + 1}`);
}
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(workspaceProto, 'hideWindowsTooltips', () => {
/* eslint-disable no-invalid-this */
return function () {
const {layoutManager} = this._container;
for (let i in layoutManager._windowSlots) {
if (layoutManager._windowSlots[i])
layoutManager._windowSlots[i][WINDOW_SLOT].hideTooltip();
}
};
/* eslint-enable */
});
const viewProto = WorkspacesView.prototype;
this._injectionManager.overrideMethod(viewProto, '_init', originalMethod => {
/* eslint-disable no-invalid-this */
return function (...args) {
originalMethod.call(this, ...args);
this._pickWorkspace = false;
this._pickWindow = false;
global.stage.connectObject(
'key-press-event', this._onKeyPress.bind(this),
'key-release-event', this._onKeyRelease.bind(this),
this);
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(viewProto, '_hideTooltips', () => {
/* eslint-disable no-invalid-this */
return function () {
if (global.stage.get_key_focus() === global.stage)
global.stage.set_key_focus(this._prevFocusActor);
this._pickWindow = false;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].hideWindowsTooltips();
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(viewProto, '_hideWorkspacesTooltips', () => {
/* eslint-disable no-invalid-this */
return function () {
global.stage.set_key_focus(this._prevFocusActor);
this._pickWorkspace = false;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].hideTooltip();
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(viewProto, '_onKeyRelease', () => {
/* eslint-disable no-invalid-this */
return function (actor, event) {
if (this._pickWindow &&
(event.get_key_symbol() === Clutter.KEY_Alt_L ||
event.get_key_symbol() === Clutter.KEY_Alt_R))
this._hideTooltips();
if (this._pickWorkspace &&
(event.get_key_symbol() === Clutter.KEY_Control_L ||
event.get_key_symbol() === Clutter.KEY_Control_R))
this._hideWorkspacesTooltips();
};
/* eslint-enable */
});
this._injectionManager.overrideMethod(viewProto, '_onKeyPress', () => {
/* eslint-disable no-invalid-this */
return function (actor, event) {
const {ControlsState} = OverviewControls;
if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER)
return false;
let workspaceManager = global.workspace_manager;
if ((event.get_key_symbol() === Clutter.KEY_Alt_L ||
event.get_key_symbol() === Clutter.KEY_Alt_R) &&
!this._pickWorkspace) {
this._prevFocusActor = global.stage.get_key_focus();
global.stage.set_key_focus(null);
this._active = workspaceManager.get_active_workspace_index();
this._pickWindow = true;
this._workspaces[workspaceManager.get_active_workspace_index()].showWindowsTooltips();
return true;
}
if ((event.get_key_symbol() === Clutter.KEY_Control_L ||
event.get_key_symbol() === Clutter.KEY_Control_R) &&
!this._pickWindow) {
this._prevFocusActor = global.stage.get_key_focus();
global.stage.set_key_focus(null);
this._pickWorkspace = true;
for (let i = 0; i < this._workspaces.length; i++)
this._workspaces[i].showTooltip();
return true;
}
if (global.stage.get_key_focus() !== global.stage)
return false;
// ignore shift presses, they're required to get numerals in azerty keyboards
if ((this._pickWindow || this._pickWorkspace) &&
(event.get_key_symbol() === Clutter.KEY_Shift_L ||
event.get_key_symbol() === Clutter.KEY_Shift_R))
return true;
if (this._pickWindow) {
if (this._active !== workspaceManager.get_active_workspace_index()) {
this._hideTooltips();
return false;
}
let c = event.get_key_symbol() - Clutter.KEY_KP_0;
if (c > 9 || c <= 0) {
c = event.get_key_symbol() - Clutter.KEY_0;
if (c > 9 || c <= 0) {
this._hideTooltips();
log(c);
return false;
}
}
let win = this._workspaces[this._active].getWindowWithTooltip(c);
this._hideTooltips();
if (win)
Main.activateWindow(win, global.get_current_time());
return true;
}
if (this._pickWorkspace) {
let c = event.get_key_symbol() - Clutter.KEY_KP_0;
if (c > 9 || c <= 0) {
c = event.get_key_symbol() - Clutter.KEY_0;
if (c > 9 || c <= 0) {
this._hideWorkspacesTooltips();
return false;
}
}
let workspace = this._workspaces[c - 1];
if (workspace !== undefined)
workspace.metaWorkspace.activate(global.get_current_time());
this._hideWorkspacesTooltips();
return true;
}
return false;
};
/* eslint-enable */
});
} }
disable() { disable() {
Workspace.Workspace = this._origWorkspace; this._injectionManager.clear();
WorkspacesView.WorkspacesView = this._origWorkspacesView;
} }
} }
/**
* @returns {Extension} - the extension's state object
*/
function init() {
return new Extension();
}

View File

@@ -1,15 +1,16 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported init enable disable */ import Clutter from 'gi://Clutter';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import St from 'gi://St';
const {Clutter, Gio, GObject, Meta, St} = imports.gi; import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
const DND = imports.ui.dnd; import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
const ExtensionUtils = imports.misc.extensionUtils; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
const Main = imports.ui.main; import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
const PanelMenu = imports.ui.panelMenu; import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
const PopupMenu = imports.ui.popupMenu;
const _ = ExtensionUtils.gettext;
const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
const WORKSPACE_KEY = 'workspace-names'; const WORKSPACE_KEY = 'workspace-names';
@@ -34,20 +35,17 @@ class WindowPreview extends St.Button {
this._window = window; this._window = window;
this.connect('destroy', this._onDestroy.bind(this)); this._window.connectObject(
'size-changed', () => this.queue_relayout(),
this._sizeChangedId = this._window.connect('size-changed', 'position-changed', () => {
() => this.queue_relayout());
this._positionChangedId = this._window.connect('position-changed',
() => {
this._updateVisible(); this._updateVisible();
this.queue_relayout(); this.queue_relayout();
}); },
this._minimizedChangedId = this._window.connect('notify::minimized', 'notify::minimized', this._updateVisible.bind(this),
this._updateVisible.bind(this)); this);
this._focusChangedId = global.display.connect('notify::focus-window', global.display.connectObject('notify::focus-window',
this._onFocusChanged.bind(this)); this._onFocusChanged.bind(this), this);
this._onFocusChanged(); this._onFocusChanged();
} }
@@ -56,13 +54,6 @@ class WindowPreview extends St.Button {
return this._window; return this._window;
} }
_onDestroy() {
this._window.disconnect(this._sizeChangedId);
this._window.disconnect(this._positionChangedId);
this._window.disconnect(this._minimizedChangedId);
global.display.disconnect(this._focusChangedId);
}
_onFocusChanged() { _onFocusChanged() {
if (global.display.focus_window === this._window) if (global.display.focus_window === this._window)
this.add_style_class_name('active'); this.add_style_class_name('active');
@@ -143,16 +134,13 @@ class WorkspaceThumbnail extends St.Button {
let workspaceManager = global.workspace_manager; let workspaceManager = global.workspace_manager;
this._workspace = workspaceManager.get_workspace_by_index(index); this._workspace = workspaceManager.get_workspace_by_index(index);
this._windowAddedId = this._workspace.connect('window-added', this._workspace.connectObject(
(ws, window) => { 'window-added', (ws, window) => this._addWindow(window),
this._addWindow(window); 'window-removed', (ws, window) => this._removeWindow(window),
}); this);
this._windowRemovedId = this._workspace.connect('window-removed',
(ws, window) => { global.display.connectObject('restacked',
this._removeWindow(window); this._onRestacked.bind(this), this);
});
this._restackedId = global.display.connect('restacked',
this._onRestacked.bind(this));
this._workspace.list_windows().forEach(w => this._addWindow(w)); this._workspace.list_windows().forEach(w => this._addWindow(w));
this._onRestacked(); this._onRestacked();
@@ -250,10 +238,6 @@ class WorkspaceThumbnail extends St.Button {
_onDestroy() { _onDestroy() {
this._tooltip.destroy(); this._tooltip.destroy();
this._workspace.disconnect(this._windowAddedId);
this._workspace.disconnect(this._windowRemovedId);
global.display.disconnect(this._restackedId);
} }
} }
@@ -295,14 +279,11 @@ class WorkspaceIndicator extends PanelMenu.Button {
this._workspaceSection = new PopupMenu.PopupMenuSection(); this._workspaceSection = new PopupMenu.PopupMenuSection();
this.menu.addMenuItem(this._workspaceSection); this.menu.addMenuItem(this._workspaceSection);
this._workspaceManagerSignals = [ workspaceManager.connectObject(
workspaceManager.connect_after('notify::n-workspaces', 'notify::n-workspaces', this._nWorkspacesChanged.bind(this), GObject.ConnectFlags.AFTER,
this._nWorkspacesChanged.bind(this)), 'workspace-switched', this._onWorkspaceSwitched.bind(this), GObject.ConnectFlags.AFTER,
workspaceManager.connect_after('workspace-switched', 'notify::layout-rows', this._updateThumbnailVisibility.bind(this),
this._onWorkspaceSwitched.bind(this)), this);
workspaceManager.connect('notify::layout-rows',
this._updateThumbnailVisibility.bind(this)),
];
this.connect('scroll-event', this._onScrollEvent.bind(this)); this.connect('scroll-event', this._onScrollEvent.bind(this));
this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this));
@@ -311,20 +292,11 @@ class WorkspaceIndicator extends PanelMenu.Button {
this._updateThumbnailVisibility(); this._updateThumbnailVisibility();
this._settings = new Gio.Settings({schema_id: WORKSPACE_SCHEMA}); this._settings = new Gio.Settings({schema_id: WORKSPACE_SCHEMA});
this._settingsChangedId = this._settings.connect( this._settings.connectObject(`changed::${WORKSPACE_KEY}`,
`changed::${WORKSPACE_KEY}`, this._updateMenuLabels.bind(this), this);
this._updateMenuLabels.bind(this));
} }
_onDestroy() { _onDestroy() {
for (let i = 0; i < this._workspaceManagerSignals.length; i++)
global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
if (this._settingsChangedId) {
this._settings.disconnect(this._settingsChangedId);
this._settingsChangedId = 0;
}
Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
super._onDestroy(); super._onDestroy();
@@ -454,20 +426,14 @@ class WorkspaceIndicator extends PanelMenu.Button {
} }
} }
/** */ export default class WorkspaceIndicatorExtension extends Extension {
function init() { enable() {
ExtensionUtils.initTranslations(); this._indicator = new WorkspaceIndicator();
} Main.panel.addToStatusArea('workspace-indicator', this._indicator);
}
let _indicator; disable() {
this._indicator.destroy();
/** */ delete this._indicator;
function enable() { }
_indicator = new WorkspaceIndicator();
Main.panel.addToStatusArea('workspace-indicator', _indicator);
}
/** */
function disable() {
_indicator.destroy();
} }

View File

@@ -1,11 +1,13 @@
// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
/* exported init buildPrefsWidget */ import Adw from 'gi://Adw';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
import Pango from 'gi://Pango';
const {Adw, Gio, GLib, GObject, Gtk, Pango} = imports.gi; import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
const ExtensionUtils = imports.misc.extensionUtils;
const _ = ExtensionUtils.gettext;
const N_ = e => e; const N_ = e => e;
const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
@@ -256,14 +258,8 @@ class NewWorkspaceRow extends Adw.PreferencesRow {
} }
} }
/** */ export default class WorkspaceIndicatorPrefs extends ExtensionPreferences {
function init() { getPreferencesWidget() {
ExtensionUtils.initTranslations(); return new WorkspaceSettingsWidget();
} }
/**
* @returns {Gtk.Widget} - the prefs widget
*/
function buildPrefsWidget() {
return new WorkspaceSettingsWidget();
} }

View File

@@ -68,7 +68,10 @@ rules:
jsdoc/check-tag-names: error jsdoc/check-tag-names: error
jsdoc/check-types: error jsdoc/check-types: error
jsdoc/implements-on-classes: error jsdoc/implements-on-classes: error
jsdoc/newline-after-description: error jsdoc/tag-lines:
- error
- any
- startLines: 1
jsdoc/require-jsdoc: error jsdoc/require-jsdoc: error
jsdoc/require-param: error jsdoc/require-param: error
jsdoc/require-param-description: error jsdoc/require-param-description: error

View File

@@ -10,3 +10,5 @@ rules:
prefer-arrow-callback: error prefer-arrow-callback: error
globals: globals:
global: readonly global: readonly
parserOptions:
sourceType: module

View File

@@ -1,5 +1,5 @@
project('gnome-shell-extensions', project('gnome-shell-extensions',
version: '45.alpha', version: '45.beta',
meson_version: '>= 0.58.0', meson_version: '>= 0.58.0',
license: 'GPL2+' license: 'GPL2+'
) )

101
po/el.po
View File

@@ -12,8 +12,8 @@ msgstr ""
"Project-Id-Version: gnome-shell-extensions master\n" "Project-Id-Version: gnome-shell-extensions master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/"
"issues\n" "issues\n"
"POT-Creation-Date: 2020-05-28 00:55+0000\n" "POT-Creation-Date: 2023-02-18 15:10+0000\n"
"PO-Revision-Date: 2020-07-14 00:40+0300\n" "PO-Revision-Date: 2023-08-01 23:41+0300\n"
"Last-Translator: Efstathios Iosifidis <eiosifidis@gnome.org>\n" "Last-Translator: Efstathios Iosifidis <eiosifidis@gnome.org>\n"
"Language-Team: Greek, Modern (1453-) <gnome-el-list@gnome.org>\n" "Language-Team: Greek, Modern (1453-) <gnome-el-list@gnome.org>\n"
"Language: el\n" "Language: el\n"
@@ -21,22 +21,31 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 1.8.7.1\n" "X-Generator: Poedit 3.3.2\n"
"X-Project-Style: gnome\n" "X-Project-Style: gnome\n"
#: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 #: data/gnome-classic.desktop.in:3
msgid "GNOME Classic" msgid "GNOME Classic"
msgstr "GNOME Classic" msgstr "GNOME Classic"
#: data/gnome-classic.desktop.in:4 #: data/gnome-classic.desktop.in:4 data/gnome-classic-wayland.desktop.in:4
#: data/gnome-classic-xorg.desktop.in:4
msgid "This session logs you into GNOME Classic" msgid "This session logs you into GNOME Classic"
msgstr "Αυτή η συνεδρία σας συνδέει στο GNOME Classic" msgstr "Αυτή η συνεδρία σας συνδέει στο GNOME Classic"
#: extensions/apps-menu/extension.js:113 #: data/gnome-classic-wayland.desktop.in:3
msgid "GNOME Classic on Wayland"
msgstr "GNOME Classic σε Wayland"
#: data/gnome-classic-xorg.desktop.in:3
msgid "GNOME Classic on Xorg"
msgstr "GNOME Classic σε Xorg"
#: extensions/apps-menu/extension.js:118
msgid "Favorites" msgid "Favorites"
msgstr "Αγαπημένα" msgstr "Αγαπημένα"
#: extensions/apps-menu/extension.js:369 #: extensions/apps-menu/extension.js:380
msgid "Applications" msgid "Applications"
msgstr "Εφαρμογές" msgstr "Εφαρμογές"
@@ -53,26 +62,26 @@ msgstr ""
"(όνομα αρχείου επιφάνειας εργασίας), ακολουθούμενη από άνω-κάτω τελεία και " "(όνομα αρχείου επιφάνειας εργασίας), ακολουθούμενη από άνω-κάτω τελεία και "
"τον αριθμό του χώρου εργασίας" "τον αριθμό του χώρου εργασίας"
#: extensions/auto-move-windows/prefs.js:35 #: extensions/auto-move-windows/prefs.js:152
msgid "Workspace Rules" msgid "Workspace Rules"
msgstr "Κανόνες χώρων εργασίας" msgstr "Κανόνες χώρων εργασίας"
#: extensions/auto-move-windows/prefs.js:243 #: extensions/auto-move-windows/prefs.js:306
msgid "Add Rule" msgid "Add Rule"
msgstr "Προσθήκη κανόνα" msgstr "Προσθήκη κανόνα"
#. TRANSLATORS: %s is the filesystem name #. TRANSLATORS: %s is the filesystem name
#: extensions/drive-menu/extension.js:112 #: extensions/drive-menu/extension.js:126
#: extensions/places-menu/placeDisplay.js:233 #: extensions/places-menu/placeDisplay.js:212
#, javascript-format #, javascript-format
msgid "Ejecting drive “%s” failed:" msgid "Ejecting drive “%s” failed:"
msgstr "Αποτυχία εξαγωγής του δίσκου «%s»:" msgstr "Αποτυχία εξαγωγής του δίσκου «%s»:"
#: extensions/drive-menu/extension.js:128 #: extensions/drive-menu/extension.js:145
msgid "Removable devices" msgid "Removable devices"
msgstr "Αφαιρούμενες συσκευές" msgstr "Αφαιρούμενες συσκευές"
#: extensions/drive-menu/extension.js:155 #: extensions/drive-menu/extension.js:167
msgid "Open Files" msgid "Open Files"
msgstr "Άνοιγμα αρχείων" msgstr "Άνοιγμα αρχείων"
@@ -106,31 +115,31 @@ msgstr ""
"στο κάτω μέρος. Η αλλαγή αυτής της ρύθμισης απαιτεί επανεκκίνηση του " "στο κάτω μέρος. Η αλλαγή αυτής της ρύθμισης απαιτεί επανεκκίνηση του "
"κελύφους για να υπάρξει κάποιο αποτέλεσμα." "κελύφους για να υπάρξει κάποιο αποτέλεσμα."
#: extensions/places-menu/extension.js:89 #: extensions/places-menu/extension.js:94
#: extensions/places-menu/extension.js:93 #: extensions/places-menu/extension.js:97
msgid "Places" msgid "Places"
msgstr "Τοποθεσίες" msgstr "Τοποθεσίες"
#: extensions/places-menu/placeDisplay.js:46 #: extensions/places-menu/placeDisplay.js:52
#, javascript-format #, javascript-format
msgid "Failed to launch “%s”" msgid "Failed to launch “%s”"
msgstr "Αποτυχία εκκίνησης «%s»" msgstr "Αποτυχία εκκίνησης «%s»"
#: extensions/places-menu/placeDisplay.js:61 #: extensions/places-menu/placeDisplay.js:67
#, javascript-format #, javascript-format
msgid "Failed to mount volume for “%s”" msgid "Failed to mount volume for “%s”"
msgstr "Αποτυχία προσάρτησης τόμου για «%s»" msgstr "Αποτυχία προσάρτησης τόμου για «%s»"
#: extensions/places-menu/placeDisplay.js:148 #: extensions/places-menu/placeDisplay.js:127
#: extensions/places-menu/placeDisplay.js:171 #: extensions/places-menu/placeDisplay.js:150
msgid "Computer" msgid "Computer"
msgstr "Υπολογιστής" msgstr "Υπολογιστής"
#: extensions/places-menu/placeDisplay.js:359 #: extensions/places-menu/placeDisplay.js:340
msgid "Home" msgid "Home"
msgstr "Προσωπικός φάκελος" msgstr "Προσωπικός φάκελος"
#: extensions/places-menu/placeDisplay.js:404 #: extensions/places-menu/placeDisplay.js:385
msgid "Browse Network" msgid "Browse Network"
msgstr "Περιήγηση δικτύου" msgstr "Περιήγηση δικτύου"
@@ -151,47 +160,47 @@ msgid "The name of the theme, to be loaded from ~/.themes/name/gnome-shell"
msgstr "" msgstr ""
"Το όνομα του θέματος που θα φορτωθεί από το ~ /.themes/name/gnome-shell" "Το όνομα του θέματος που θα φορτωθεί από το ~ /.themes/name/gnome-shell"
#: extensions/window-list/extension.js:98 #: extensions/window-list/extension.js:72
msgid "Close" msgid "Close"
msgstr "Κλείσιμο" msgstr "Κλείσιμο"
#: extensions/window-list/extension.js:118 #: extensions/window-list/extension.js:92
msgid "Unminimize" msgid "Unminimize"
msgstr "Αποελαχιστοποίηση" msgstr "Αποελαχιστοποίηση"
#: extensions/window-list/extension.js:118 #: extensions/window-list/extension.js:92
msgid "Minimize" msgid "Minimize"
msgstr "Ελαχιστοποίηση" msgstr "Ελαχιστοποίηση"
#: extensions/window-list/extension.js:125 #: extensions/window-list/extension.js:99
msgid "Unmaximize" msgid "Unmaximize"
msgstr "Απομεγιστοποίηση" msgstr "Απομεγιστοποίηση"
#: extensions/window-list/extension.js:125 #: extensions/window-list/extension.js:99
msgid "Maximize" msgid "Maximize"
msgstr "Μεγιστοποίηση" msgstr "Μεγιστοποίηση"
#: extensions/window-list/extension.js:428 #: extensions/window-list/extension.js:483
msgid "Minimize all" msgid "Minimize all"
msgstr "Ελαχιστοποίηση όλων" msgstr "Ελαχιστοποίηση όλων"
#: extensions/window-list/extension.js:434 #: extensions/window-list/extension.js:489
msgid "Unminimize all" msgid "Unminimize all"
msgstr "Αποελαχιστοποίηση όλων" msgstr "Αποελαχιστοποίηση όλων"
#: extensions/window-list/extension.js:440 #: extensions/window-list/extension.js:495
msgid "Maximize all" msgid "Maximize all"
msgstr "Μεγιστοποίηση όλων" msgstr "Μεγιστοποίηση όλων"
#: extensions/window-list/extension.js:448 #: extensions/window-list/extension.js:503
msgid "Unmaximize all" msgid "Unmaximize all"
msgstr "Απομεγιστοποίηση όλων" msgstr "Απομεγιστοποίηση όλων"
#: extensions/window-list/extension.js:456 #: extensions/window-list/extension.js:511
msgid "Close all" msgid "Close all"
msgstr "Κλείσιμο όλων" msgstr "Κλείσιμο όλων"
#: extensions/window-list/extension.js:734 #: extensions/window-list/extension.js:795
msgid "Window List" msgid "Window List"
msgstr "Λίστα παραθύρου" msgstr "Λίστα παραθύρου"
@@ -209,7 +218,7 @@ msgstr ""
"«always» (πάντα)." "«always» (πάντα)."
#: extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml:20 #: extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml:20
#: extensions/window-list/prefs.js:100 #: extensions/window-list/prefs.js:79
msgid "Show windows from all workspaces" msgid "Show windows from all workspaces"
msgstr "Εμφάνιση των παραθύρων από όλους τους χώρους εργασίας" msgstr "Εμφάνιση των παραθύρων από όλους τους χώρους εργασίας"
@@ -230,41 +239,41 @@ msgstr ""
"Αν θα εμφανίζεται ο κατάλογος παραθύρων όλων των συνδεμένων οθονών ή μόνο " "Αν θα εμφανίζεται ο κατάλογος παραθύρων όλων των συνδεμένων οθονών ή μόνο "
"της κύριας οθόνης." "της κύριας οθόνης."
#: extensions/window-list/prefs.js:29 #: extensions/window-list/prefs.js:35
msgid "Window Grouping" msgid "Window Grouping"
msgstr "Ομαδοποίηση παραθύρου" msgstr "Ομαδοποίηση παραθύρου"
#: extensions/window-list/prefs.js:58 #: extensions/window-list/prefs.js:40
msgid "Never group windows" msgid "Never group windows"
msgstr "Να μη γίνεται ποτέ ομαδοποίηση παραθύρων" msgstr "Να μη γίνεται ποτέ ομαδοποίηση παραθύρων"
#: extensions/window-list/prefs.js:59 #: extensions/window-list/prefs.js:41
msgid "Group windows when space is limited" msgid "Group windows when space is limited"
msgstr "Ομαδοποίηση παραθύρων όταν ο χώρος είναι περιορισμένος" msgstr "Ομαδοποίηση παραθύρων όταν ο χώρος είναι περιορισμένος"
#: extensions/window-list/prefs.js:60 #: extensions/window-list/prefs.js:42
msgid "Always group windows" msgid "Always group windows"
msgstr "Να γίνεται πάντα ομαδοποίηση παραθύρων" msgstr "Να γίνεται πάντα ομαδοποίηση παραθύρων"
#: extensions/window-list/prefs.js:94 #: extensions/window-list/prefs.js:66
msgid "Show on all monitors" msgid "Show on all monitors"
msgstr "Να εμφανίζεται σε όλες τις οθόνες" msgstr "Να εμφανίζεται σε όλες τις οθόνες"
#: extensions/window-list/workspaceIndicator.js:207 #: extensions/window-list/workspaceIndicator.js:261
#: extensions/workspace-indicator/extension.js:213 #: extensions/workspace-indicator/extension.js:266
msgid "Workspace Indicator" msgid "Workspace Indicator"
msgstr "Δείκτης χώρου εργασίας" msgstr "Δείκτης χώρου εργασίας"
#: extensions/workspace-indicator/prefs.js:34 #: extensions/workspace-indicator/prefs.js:62
msgid "Workspace Names"
msgstr "Ονόματα χώρων εργασίας:"
#: extensions/workspace-indicator/prefs.js:67
#, javascript-format #, javascript-format
msgid "Workspace %d" msgid "Workspace %d"
msgstr "Χώρος εργασίας %d" msgstr "Χώρος εργασίας %d"
#: extensions/workspace-indicator/prefs.js:218 #: extensions/workspace-indicator/prefs.js:129
msgid "Workspace Names"
msgstr "Ονόματα χώρων εργασίας"
#: extensions/workspace-indicator/prefs.js:255
msgid "Add Workspace" msgid "Add Workspace"
msgstr "Προσθήκη χώρου εργασίας" msgstr "Προσθήκη χώρου εργασίας"