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>
This commit is contained in:
Florian Müllner
2023-08-06 13:20:38 +02:00
parent 0544729bba
commit cb8c2eb27f
+271 -244
View File
@@ -1,264 +1,291 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
import Clutter from 'gi://Clutter';
import Graphene from 'gi://Graphene';
import GObject from 'gi://GObject';
import St from 'gi://St';
import {InjectionManager} from 'resource:///org/gnome/shell/extensions/extension.js';
const Main = imports.ui.main;
const OverviewControls = imports.ui.overviewControls;
const Workspace = imports.ui.workspace;
const WorkspacesView = imports.ui.workspacesView;
const {WindowPreview} = imports.ui.windowPreview;
const {Workspace} = imports.ui.workspace;
const {WorkspacesView} = imports.ui.workspacesView;
const WINDOW_SLOT = 4;
class MyWorkspace extends Workspace.Workspace {
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;
global.stage.connectObject(
'key-press-event', this._onKeyPress.bind(this),
'key-release-event', this._onKeyRelease.bind(this),
this);
}
_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();
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;
}
}
export default class Extension {
constructor() {
this._origWorkspace = Workspace.Workspace;
this._origWorkspacesView = WorkspacesView.WorkspacesView;
this._injectionManager = new InjectionManager();
}
enable() {
Workspace.Workspace = MyWorkspace;
WorkspacesView.WorkspacesView = MyWorkspacesView;
const previewProto = WindowPreview.prototype;
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() {
Workspace.Workspace = this._origWorkspace;
WorkspacesView.WorkspacesView = this._origWorkspacesView;
this._injectionManager.clear();
}
}