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>
This commit is contained in:
Florian Müllner
2023-06-02 13:12:54 +02:00
parent 18674b2e35
commit 701b14ecbf
8 changed files with 345 additions and 305 deletions
+16 -12
View File
@@ -677,22 +677,26 @@ class ApplicationsButton extends PanelMenu.Button {
}
}
let appsMenuButton;
class Extension {
constructor() {
ExtensionUtils.initTranslations();
}
/** */
function enable() {
appsMenuButton = new ApplicationsButton();
let index = Main.sessionMode.panel.left.indexOf('activities') + 1;
Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left');
}
enable() {
this._appsMenuButton = new ApplicationsButton();
const index = Main.sessionMode.panel.left.indexOf('activities') + 1;
Main.panel.addToStatusArea(
'apps-menu', this._appsMenuButton, index, 'left');
}
/** */
function disable() {
Main.panel.menuManager.removeMenu(appsMenuButton.menu);
appsMenuButton.destroy();
disable() {
Main.panel.menuManager.removeMenu(this._appsMenuButton.menu);
this._appsMenuButton.destroy();
delete this._appsMenuButton;
}
}
/** */
function init() {
ExtensionUtils.initTranslations();
return new Extension();
}
+39 -40
View File
@@ -105,47 +105,46 @@ class WindowMover {
}
}
let prevCheckWorkspaces;
let winMover;
class Extension {
enable() {
this._prevCheckWorkspaces = Main.wm._workspaceTracker._checkWorkspaces;
Main.wm._workspaceTracker._checkWorkspaces =
this._getCheckWorkspaceOverride(this._prevCheckWorkspaces);
this._windowMover = new WindowMover();
}
disable() {
Main.wm._workspaceTracker._checkWorkspaces = this._prevCheckWorkspaces;
this._windowMover.destroy();
delete this._windowMover;
}
_getCheckWorkspaceOverride(originalMethod) {
/* eslint-disable no-invalid-this */
return function () {
const 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
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1));
originalMethod.call(this);
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId);
return false;
};
/* eslint-enable no-invalid-this */
}
}
/** */
function init() {
ExtensionUtils.initTranslations();
}
/**
* @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
keepAliveWorkspaces.forEach(ws => (ws._keepAliveId = 1));
prevCheckWorkspaces.call(this);
keepAliveWorkspaces.forEach(ws => delete ws._keepAliveId);
return false;
}
/** */
function enable() {
prevCheckWorkspaces = Main.wm._workspaceTracker._checkWorkspaces;
Main.wm._workspaceTracker._checkWorkspaces = myCheckWorkspaces;
winMover = new WindowMover();
}
/** */
function disable() {
Main.wm._workspaceTracker._checkWorkspaces = prevCheckWorkspaces;
winMover.destroy();
return new Extension();
}
+17 -14
View File
@@ -212,20 +212,23 @@ class DriveMenu extends PanelMenu.Button {
}
}
class Extension {
constructor() {
ExtensionUtils.initTranslations();
}
enable() {
this._indicator = new DriveMenu();
Main.panel.addToStatusArea('drive-menu', this._indicator);
}
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();
return new Extension();
}
+18 -10
View File
@@ -1,17 +1,25 @@
/* exported enable disable */
/* exported init */
const AppDisplay = imports.ui.appDisplay;
let _activateOriginal = null;
class Extension {
constructor() {
this._appIconProto = AppDisplay.AppIcon.prototype;
this._activateOriginal = this._appIconProto.activate;
}
/** */
function enable() {
_activateOriginal = AppDisplay.AppIcon.prototype.activate;
AppDisplay.AppIcon.prototype.activate = function () {
_activateOriginal.call(this, 2);
};
enable() {
const {_activateOriginal} = this;
this._appIconProto.activate = function () {
_activateOriginal.call(this, 2);
};
}
disable() {
this._appIconProto.activate = this._activateOriginal;
}
}
/** */
function disable() {
AppDisplay.AppIcon.prototype.activate = _activateOriginal;
function init() {
return new Extension();
}
+88 -67
View File
@@ -236,75 +236,96 @@ class NaturalLayoutStrategy extends Workspace.LayoutStrategy {
}
}
let winInjections, workspaceInjections;
class Extension {
constructor() {
this._savedMethods = new Map();
}
/** */
function resetState() {
winInjections = { };
workspaceInjections = { };
enable() {
const settings = ExtensionUtils.getSettings();
const layoutProto = Workspace.WorkspaceLayout.prototype;
const previewProto = WindowPreview.prototype;
this._overrideMethod(layoutProto, '_createBestLayout', () => {
/* eslint-disable no-invalid-this */
return function () {
this._layoutStrategy = new NaturalLayoutStrategy({
monitor: Main.layoutManager.monitors[this._monitorIndex],
}, settings);
return this._layoutStrategy.computeLayout(this._sortedWindows);
};
/* eslint-enable no-invalid-this */
});
// position window titles on top of windows in overlay
this._overrideMethod(previewProto, '_init', originalMethod => {
/* eslint-disable no-invalid-this */
return function (...args) {
originalMethod.call(this, ...args);
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);
alignConstraint.factor = 0;
const bindConstraint = this._title.get_constraints().find(
c => c.coordinate && c.coordinate === Clutter.BindCoordinate.Y);
bindConstraint.offset = 0;
};
/* eslint-enable no-invalid-this */
});
this._overrideMethod(previewProto, '_adjustOverlayOffsets', originalMethod => {
/* eslint-disable no-invalid-this */
return function (...args) {
originalMethod.call(this, ...args);
if (settings.get_boolean('window-captions-on-top'))
this._title.translation_y = -this._title.translation_y;
};
/* eslint-enable no-invalid-this */
});
}
disable() {
this._restoreMethods();
global.stage.queue_relayout();
}
_saveMethod(prototype, methodName) {
let map = this._savedMethods.get(prototype);
if (!map) {
map = new Map();
this._savedMethods.set(prototype, map);
}
const originalMethod = prototype[methodName];
map.set(methodName, originalMethod);
return originalMethod;
}
_overrideMethod(prototype, methodName, createOverrideFunc) {
const originalMethod = this._saveMethod(prototype, methodName);
prototype[methodName] = createOverrideFunc(originalMethod);
}
_restoreMethods() {
for (const [proto, map] of this._savedMethods) {
for (const [methodName, originalMethod] of map) {
if (originalMethod === undefined)
delete proto[methodName];
else
proto[methodName] = originalMethod;
}
}
this._savedMethods.clear();
}
}
/** */
function enable() {
resetState();
let settings = ExtensionUtils.getSettings();
workspaceInjections['_createBestLayout'] = Workspace.WorkspaceLayout.prototype._createBestLayout;
Workspace.WorkspaceLayout.prototype._createBestLayout = function (_area) {
this._layoutStrategy = new NaturalLayoutStrategy({
monitor: Main.layoutManager.monitors[this._monitorIndex],
}, settings);
return this._layoutStrategy.computeLayout(this._sortedWindows);
};
// position window titles on top of windows in overlay
winInjections['_init'] = WindowPreview.prototype._init;
WindowPreview.prototype._init = function (...args) {
winInjections['_init'].call(this, ...args);
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);
alignConstraint.factor = 0;
const bindConstraint = this._title.get_constraints().find(
c => c.coordinate && c.coordinate === Clutter.BindCoordinate.Y);
bindConstraint.offset = 0;
};
winInjections['_adjustOverlayOffsets'] =
WindowPreview.prototype._adjustOverlayOffsets;
WindowPreview.prototype._adjustOverlayOffsets = function (...args) {
winInjections['_adjustOverlayOffsets'].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
* @param {string} name - the @injection key that should be removed
*/
function removeInjection(object, injection, name) {
if (injection[name] === undefined)
delete object[name];
else
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();
function init() {
return new Extension();
}
+21 -18
View File
@@ -138,24 +138,27 @@ class PlacesMenu extends PanelMenu.Button {
}
}
class Extension {
constructor() {
ExtensionUtils.initTranslations();
}
enable() {
this._indicator = new PlacesMenu();
let pos = Main.sessionMode.panel.left.length;
if ('apps-menu' in Main.panel.statusArea)
pos++;
Main.panel.addToStatusArea('places-menu', this._indicator, pos, 'left');
}
disable() {
this._indicator.destroy();
delete this._indicator;
}
}
/** */
function init() {
ExtensionUtils.initTranslations();
}
let _indicator;
/** */
function enable() {
_indicator = new PlacesMenu();
let pos = Main.sessionMode.panel.left.length;
if ('apps-menu' in Main.panel.statusArea)
pos++;
Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left');
}
/** */
function disable() {
_indicator.destroy();
return new Extension();
}
+129 -130
View File
@@ -26,146 +26,145 @@ const Main = imports.ui.main;
const MESSAGE_FADE_TIME = 2000;
let text;
class Extension {
SIZES = [
[624, 351],
[800, 450],
[1024, 576],
[1200, 675],
[1600, 900],
[360, 654], // Phone portrait maximized
[720, 360], // Phone landscape fullscreen
];
/** */
function hideMessage() {
text.destroy();
text = null;
}
/**
* @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;
_flashMessage(message) {
if (!this._text) {
this._text = new St.Label({style_class: 'screenshot-sizer-message'});
Main.uiGroup.add_actor(this._text);
}
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
let newIndex = (nearestIndex + (backwards ? -1 : 1)) % scaledSizes.length;
let [newWidth, newHeight] = scaledSizes[newIndex];
_hideMessage() {
this._text.destroy();
delete this._text;
}
// Push the window onscreen if it would be resized offscreen
let newX = outerRect.x;
let newY = outerRect.y;
if (newX + newWidth > workArea.x + workArea.width)
newX = Math.max(workArea.x + workArea.width - newWidth);
if (newY + newHeight > workArea.y + workArea.height)
newY = Math.max(workArea.y + workArea.height - newHeight);
/**
* @param {Meta.Display} display - the display
* @param {Meta.Window=} window - for per-window bindings, the window
* @param {Meta.KeyBinding} binding - the key binding
*/
_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;
const id = window.connect('size-changed', () => {
window.disconnect(id);
_notifySizeChange(window);
});
window.move_resize_frame(true, newX, newY, newWidth, newHeight);
}
// Unmaximize first
if (window.get_maximized() !== 0)
window.unmaximize(Meta.MaximizeFlags.BOTH);
/**
* @param {Meta.Window} window - the window whose size changed
*/
function _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);
let workArea = window.get_work_area_current_monitor();
let outerRect = window.get_frame_rect();
// 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);
// Double both axes if on a hidpi display
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
let scaledSizes = this.SIZES.map(size => size.map(wh => wh * scaleFactor))
.filter(([w, h]) => w <= workArea.width && h <= workArea.height);
flashMessage(message);
// 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;
}
}
// get the next size up or down from ideal
let newIndex = (nearestIndex + (backwards ? -1 : 1)) % scaledSizes.length;
let [newWidth, newHeight] = scaledSizes[newIndex];
// Push the window onscreen if it would be resized offscreen
let newX = outerRect.x;
let newY = outerRect.y;
if (newX + newWidth > workArea.x + workArea.width)
newX = Math.max(workArea.x + workArea.width - newWidth);
if (newY + newHeight > workArea.y + workArea.height)
newY = Math.max(workArea.y + workArea.height - newHeight);
const id = window.connect('size-changed', () => {
window.disconnect(id);
this._notifySizeChange(window);
});
window.move_resize_frame(true, newX, newY, newWidth, newHeight);
}
/**
* @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',
ExtensionUtils.getSettings(),
Meta.KeyBindingFlags.PER_WINDOW,
Shell.ActionMode.NORMAL,
this._cycleScreenshotSizes.bind(this));
Main.wm.addKeybinding(
'cycle-screenshot-sizes-backward',
ExtensionUtils.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');
}
}
/** */
function enable() {
Main.wm.addKeybinding(
'cycle-screenshot-sizes',
ExtensionUtils.getSettings(),
Meta.KeyBindingFlags.PER_WINDOW,
Shell.ActionMode.NORMAL,
cycleScreenshotSizes);
Main.wm.addKeybinding(
'cycle-screenshot-sizes-backward',
ExtensionUtils.getSettings(),
Meta.KeyBindingFlags.PER_WINDOW | Meta.KeyBindingFlags.IS_REVERSED,
Shell.ActionMode.NORMAL,
cycleScreenshotSizes);
}
/** */
function disable() {
Main.wm.removeKeybinding('cycle-screenshot-sizes');
Main.wm.removeKeybinding('cycle-screenshot-sizes-backward');
function init() {
return new Extension();
}
+17 -14
View File
@@ -454,20 +454,23 @@ class WorkspaceIndicator extends PanelMenu.Button {
}
}
class Extension {
constructor() {
ExtensionUtils.initTranslations();
}
enable() {
this._indicator = new WorkspaceIndicator();
Main.panel.addToStatusArea('workspace-indicator', this._indicator);
}
disable() {
this._indicator.destroy();
delete this._indicator;
}
}
/** */
function init() {
ExtensionUtils.initTranslations();
}
let _indicator;
/** */
function enable() {
_indicator = new WorkspaceIndicator();
Main.panel.addToStatusArea('workspace-indicator', _indicator);
}
/** */
function disable() {
_indicator.destroy();
return new Extension();
}