📝 Add plenty of comments
This commit is contained in:
+151
-99
@@ -67,8 +67,8 @@ class Extension {
|
|||||||
// Store a reference to the settings object.
|
// Store a reference to the settings object.
|
||||||
this._settings = ExtensionUtils.getSettings();
|
this._settings = ExtensionUtils.getSettings();
|
||||||
|
|
||||||
// This will store an item of ALL_EFFECTS which was used the last time a window was
|
// This will store an item of ALL_EFFECTS array which was used the last time a window
|
||||||
// opened / closed.
|
// was opened / closed.
|
||||||
this._currentEffect = 0;
|
this._currentEffect = 0;
|
||||||
|
|
||||||
// We will use extensionThis to refer to the extension inside the patched methods.
|
// We will use extensionThis to refer to the extension inside the patched methods.
|
||||||
@@ -84,9 +84,52 @@ class Extension {
|
|||||||
this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME;
|
this._origWindowTime = imports.ui.windowManager.DESTROY_WINDOW_ANIMATION_TIME;
|
||||||
this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME;
|
this._origDialogTime = imports.ui.windowManager.DIALOG_DESTROY_WINDOW_ANIMATION_TIME;
|
||||||
|
|
||||||
|
|
||||||
|
// ------------------------------------------------ patching the window-open animation
|
||||||
|
|
||||||
|
// Here we add an effect to the window-open animation. This is done whenever a new
|
||||||
|
// window is created.
|
||||||
|
this._windowCreatedConnection =
|
||||||
|
global.display.connect('window-created', (d, metaWin) => {
|
||||||
|
let actor = metaWin.get_compositor_private();
|
||||||
|
|
||||||
|
// If we are currently in the overview, we add the effect to the original window
|
||||||
|
// actor. The window preview in the overview is basically a Clutter.Clone which
|
||||||
|
// shows the original window.
|
||||||
|
if (Main.overview.visible && !Main.overview.closing) {
|
||||||
|
const id = actor.connect('show', () => {
|
||||||
|
extensionThis._setupEffect(actor, true);
|
||||||
|
actor.disconnect(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
// If a window is created outside of the overview, the transitions are set up in
|
||||||
|
// the async _mapWindow of the WindowManager which can defer the actual showing
|
||||||
|
// of the window significantly, especially when currently leaving the overview:
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1449
|
||||||
|
// AFAIK, overriding this method is not possible as it's called by a signal to
|
||||||
|
// which it is bound via the bind() method. To tweak the async transition
|
||||||
|
// anyways, we override the actors ease() method once - the next time it will be
|
||||||
|
// called by the _mapWindow(), we will intercept it!
|
||||||
|
else {
|
||||||
|
const orig = actor.ease;
|
||||||
|
actor.ease = function(...params) {
|
||||||
|
orig.apply(actor, params);
|
||||||
|
actor.ease = orig;
|
||||||
|
|
||||||
|
extensionThis._setupEffect(actor, true);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Some of the effects require that the window's actor is enlarged to provide a bigger
|
||||||
|
// canvas to draw the effects. Outside the overview we can simply increase the scale
|
||||||
|
// of the actor. However, if we are in the overview, we have to enlarge the clone of
|
||||||
|
// the window as well.
|
||||||
Workspace.prototype._addWindowClone = function(...params) {
|
Workspace.prototype._addWindowClone = function(...params) {
|
||||||
const result = extensionThis._origAddWindowClone.apply(this, params);
|
const result = extensionThis._origAddWindowClone.apply(this, params);
|
||||||
|
|
||||||
|
// The parameters of this method changed a bit through the versions...
|
||||||
let realWindow, clone;
|
let realWindow, clone;
|
||||||
|
|
||||||
if (utils.shellVersionIs(3, 36)) {
|
if (utils.shellVersionIs(3, 36)) {
|
||||||
@@ -121,6 +164,7 @@ class Extension {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is actually need for the window-close animation on GNOME Shell 3.36.
|
||||||
// On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone
|
// On GNOME 3.36, the window clone's 'destroy' handler only calls _removeWindowClone
|
||||||
// but not _doRemoveWindow. The latter is required to trigger the repositioning of
|
// but not _doRemoveWindow. The latter is required to trigger the repositioning of
|
||||||
// the overview window layout. Therefore we call this method in addition.
|
// the overview window layout. Therefore we call this method in addition.
|
||||||
@@ -133,83 +177,28 @@ class Extension {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
this._windowCreatedConnection =
|
|
||||||
global.display.connect('window-created', (d, metaWin) => {
|
|
||||||
let actor = metaWin.get_compositor_private();
|
|
||||||
|
|
||||||
if (Main.overview.visible && !Main.overview.closing) {
|
// ----------------------------------------------- patching the window-close animation
|
||||||
const id = actor.connect('show', () => {
|
|
||||||
extensionThis._setupEffect(actor, true);
|
|
||||||
actor.disconnect(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
// The signal handler below is all which is required outside of the overview. All
|
||||||
const orig = actor.ease;
|
// other hacks further below are just required to defer the window-hiding in the
|
||||||
actor.ease = function(...params) {
|
// overview until the effect is finished.
|
||||||
orig.apply(actor, params);
|
|
||||||
actor.ease = orig;
|
|
||||||
|
|
||||||
extensionThis._setupEffect(actor, true);
|
// The close animation is set up in WindowManager's _destroyWindow:
|
||||||
};
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1549
|
||||||
}
|
// As we cannot monkey-patch the _destroyWindow itself, we connect to the 'destroy'
|
||||||
});
|
// signal of the window manager and tweak the animation to our needs.
|
||||||
|
this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => {
|
||||||
|
this._setupEffect(actor, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// These three method overrides are mega-hacky! Usually, windows are not faded when
|
||||||
|
// closed from the overview (why?). With these overrides we make sure that they are
|
||||||
// This class is only available in GNOME Shell 3.38+. So no overlay-hiding on
|
// actually faded out. To do this, _windowRemoved and _doRemoveWindow now check
|
||||||
// GNOME Shell 3.36 for now.
|
// whether there is a transition ongoing (via extensionThis._shouldDestroy). If that's
|
||||||
if (WindowPreview) {
|
// the case, these methods do nothing. Are the actors removed in the end? I hope so.
|
||||||
this._origDeleteAll = WindowPreview.prototype._deleteAll;
|
// The _destroyWindow of the WindowManager sets the transitions up and should take
|
||||||
this._origRestack = WindowPreview.prototype._restack;
|
// care of removing the actors at the end of the transitions.
|
||||||
this._origInit = WindowPreview.prototype._init;
|
|
||||||
|
|
||||||
// This is required, else WindowPreview's _restack() which is called by the
|
|
||||||
// "this.overlayEnabled = false", sometimes tries to access an already delete
|
|
||||||
// WindowPreview.
|
|
||||||
WindowPreview.prototype._restack = function() {
|
|
||||||
if (!this._closeRequested) {
|
|
||||||
// Call the original method.
|
|
||||||
extensionThis._origRestack.apply(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
WindowPreview.prototype._init = function(...params) {
|
|
||||||
// Call the original method.
|
|
||||||
extensionThis._origInit.apply(this, params);
|
|
||||||
|
|
||||||
const connectionID = this.metaWindow.connect('unmanaged', () => {
|
|
||||||
if (this.window_container) {
|
|
||||||
// Hide the window's icon, name, and close button.
|
|
||||||
this.overlayEnabled = false;
|
|
||||||
this._icon.visible = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Make sure to not call the callback above if the Meta.Window was not unmanaged
|
|
||||||
// before leaving the overview.
|
|
||||||
this.connect('destroy', () => {
|
|
||||||
this.metaWindow.disconnect(connectionID);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// The _deleteAll is called when the user clicks the X in the overview. We should
|
|
||||||
// not attempt to close windows twice. Due to the animation in the overview, the
|
|
||||||
// close button can be clicked twice which normally would lead to a crash.
|
|
||||||
WindowPreview.prototype._deleteAll = function() {
|
|
||||||
if (!this._closeRequested) {
|
|
||||||
extensionThis._origDeleteAll.apply(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// These three method overrides are mega-hacky! They are only required to make the
|
|
||||||
// fire animation work in the overview. Usually, windows are not faded when closed
|
|
||||||
// from the overview (why?). With these overrides we make sure that they are actually
|
|
||||||
// faded out. To do this, _windowRemoved and _doRemoveWindow now check whether there
|
|
||||||
// is a transition ongoing (via extensionThis._shouldDestroy). If that's the case,
|
|
||||||
// these methods do nothing. Are the actors removed in the end? I hope so. The
|
|
||||||
// _destroyWindow of the WindowManager sets the transitions up and should take care of
|
|
||||||
// removing the actors at the end of the transitions.
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1299
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1299
|
||||||
Workspace.prototype._windowRemoved = function(ws, metaWin) {
|
Workspace.prototype._windowRemoved = function(ws, metaWin) {
|
||||||
if (extensionThis._shouldDestroy(this, metaWin)) {
|
if (extensionThis._shouldDestroy(this, metaWin)) {
|
||||||
@@ -238,13 +227,54 @@ class Extension {
|
|||||||
return extensionThis._origShouldAnimateActor.apply(this, params);
|
return extensionThis._origShouldAnimateActor.apply(this, params);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The close animation is set up in WindowManager's _destroyWindow:
|
// With the code below, we hide the window-overlay (icon, label, close button) in the
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1549
|
// overview once the close-animation is running. As the WindowPreview class is only
|
||||||
// As we cannot monkey-patch the _destroyWindow itself, we connect to the 'destroy'
|
// available on GNOME 3.38 and beyond, we cannot hide the overlay on GNOME 3.36.
|
||||||
// signal of the window manager and tweak the animation to our needs.
|
if (WindowPreview) {
|
||||||
this._destroyConnection = global.window_manager.connect('destroy', (wm, actor) => {
|
|
||||||
this._setupEffect(actor, false);
|
// We will monkey-patch these methods.
|
||||||
});
|
this._origDeleteAll = WindowPreview.prototype._deleteAll;
|
||||||
|
this._origRestack = WindowPreview.prototype._restack;
|
||||||
|
this._origInit = WindowPreview.prototype._init;
|
||||||
|
|
||||||
|
// Whenever a WindowPreview is created, we connect to the referenced Meta.Window's
|
||||||
|
// 'unmanaged' signal to hide the overlay.
|
||||||
|
WindowPreview.prototype._init = function(...params) {
|
||||||
|
extensionThis._origInit.apply(this, params);
|
||||||
|
|
||||||
|
// Hide the window's icon, name, and close button.
|
||||||
|
const connectionID = this.metaWindow.connect('unmanaged', () => {
|
||||||
|
if (this.window_container) {
|
||||||
|
this.overlayEnabled = false;
|
||||||
|
this._icon.visible = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure to not call the callback above if the Meta.Window was not unmanaged
|
||||||
|
// before leaving the overview.
|
||||||
|
this.connect('destroy', () => {
|
||||||
|
this.metaWindow.disconnect(connectionID);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// The _deleteAll is called when the user clicks the X in the overview. We should
|
||||||
|
// not attempt to close windows twice. Due to the animation in the overview, the
|
||||||
|
// close button can be clicked twice which normally would lead to a crash.
|
||||||
|
WindowPreview.prototype._deleteAll = function() {
|
||||||
|
if (!this._closeRequested) {
|
||||||
|
extensionThis._origDeleteAll.apply(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is required, else WindowPreview's _restack() which is called by the
|
||||||
|
// "this.overlayEnabled = false", sometimes tries to access an already delete
|
||||||
|
// WindowPreview.
|
||||||
|
WindowPreview.prototype._restack = function() {
|
||||||
|
if (!this._closeRequested) {
|
||||||
|
extensionThis._origRestack.apply(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function could be called after the extension is uninstalled, disabled in GNOME
|
// This function could be called after the extension is uninstalled, disabled in GNOME
|
||||||
@@ -277,7 +307,13 @@ class Extension {
|
|||||||
|
|
||||||
// ----------------------------------------------------------------------- private stuff
|
// ----------------------------------------------------------------------- private stuff
|
||||||
|
|
||||||
|
// This method adds one of the configured effects to the given actor. If forOpening is
|
||||||
|
// set to true, a effect from the enabled window-open animations is chosen, else an
|
||||||
|
// enabled window-close animation is used. This will also tweak the transitions of the
|
||||||
|
// given actor (e.g. scale it up if required).
|
||||||
_setupEffect(actor, forOpening) {
|
_setupEffect(actor, forOpening) {
|
||||||
|
|
||||||
|
// Only add effects to normal windows and dialog windows.
|
||||||
const isNormalWindow = actor.meta_window.window_type == Meta.WindowType.NORMAL;
|
const isNormalWindow = actor.meta_window.window_type == Meta.WindowType.NORMAL;
|
||||||
const isDialogWindow =
|
const isDialogWindow =
|
||||||
actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG ||
|
actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG ||
|
||||||
@@ -290,7 +326,7 @@ class Extension {
|
|||||||
// We do nothing if a dialog got closed and we should not burn them.
|
// We do nothing if a dialog got closed and we should not burn them.
|
||||||
const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs');
|
const shouldDestroyDialogs = this._settings.get_boolean('destroy-dialogs');
|
||||||
|
|
||||||
// If an effect is to be previewed, we have to affect dialogs es well. This is
|
// If an effect is to be previewed however, we have to affect dialogs es well. This is
|
||||||
// because the preview window is a dialog window...
|
// because the preview window is a dialog window...
|
||||||
const action = forOpening ? 'open' : 'close';
|
const action = forOpening ? 'open' : 'close';
|
||||||
const previewNick = this._settings.get_string(action + '-preview-effect');
|
const previewNick = this._settings.get_string(action + '-preview-effect');
|
||||||
@@ -302,8 +338,10 @@ class Extension {
|
|||||||
|
|
||||||
// ------------------------------------------------------------------ choose an effect
|
// ------------------------------------------------------------------ choose an effect
|
||||||
|
|
||||||
|
// Now we chose a random effect from all enabled effects.
|
||||||
this._currentEffect = null;
|
this._currentEffect = null;
|
||||||
|
|
||||||
|
// First we check if an effect is to be previewed.
|
||||||
if (previewNick != '') {
|
if (previewNick != '') {
|
||||||
this._currentEffect = ALL_EFFECTS.find(Effect => {
|
this._currentEffect = ALL_EFFECTS.find(Effect => {
|
||||||
return Effect.getNick() == previewNick;
|
return Effect.getNick() == previewNick;
|
||||||
@@ -312,10 +350,11 @@ class Extension {
|
|||||||
// Only preview the effect once.
|
// Only preview the effect once.
|
||||||
this._settings.set_string(action + '-preview-effect', '');
|
this._settings.set_string(action + '-preview-effect', '');
|
||||||
|
|
||||||
} else {
|
}
|
||||||
|
// Else we choose a random effect from all enabled effects.
|
||||||
|
else {
|
||||||
|
|
||||||
// Else we choose a random effect from all enabled effects. Therefore, we first
|
// Therefore, we first create a list of all currently enabled effects.
|
||||||
// create a list of all currently enabled effects.
|
|
||||||
const enabled = ALL_EFFECTS.filter(Effect => {
|
const enabled = ALL_EFFECTS.filter(Effect => {
|
||||||
return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`);
|
return this._settings.get_boolean(`${Effect.getNick()}-${action}-effect`);
|
||||||
});
|
});
|
||||||
@@ -334,24 +373,27 @@ class Extension {
|
|||||||
|
|
||||||
// ----------------------------------------------------------- tweak actor transitions
|
// ----------------------------------------------------------- tweak actor transitions
|
||||||
|
|
||||||
// This is used to tweak the ongoing transitions of a window actor. This is either the
|
// The following is used to tweak the ongoing transitions of a window actor. Usually
|
||||||
// actual actor of the Meta.Window or a clone in the overview. Usually windows are
|
// windows are faded in / out scaled up / down slightly by GNOME Shell. Here, we allow
|
||||||
// faded in / out scaled up / down slightly by GNOME Shell. Here, we allow
|
// modifications to this behavior by the effects.
|
||||||
// modifications to this behavior by the effects. The given config object is created
|
|
||||||
// by the effect's tweakTransition() method.
|
|
||||||
const config = this._currentEffect.tweakTransition(actor, this._settings, forOpening);
|
const config = this._currentEffect.tweakTransition(actor, this._settings, forOpening);
|
||||||
const duration =
|
const duration =
|
||||||
this._settings.get_int(this._currentEffect.getNick() + '-animation-time');
|
this._settings.get_int(this._currentEffect.getNick() + '-animation-time');
|
||||||
|
|
||||||
|
// All animations are relative to the window's center.
|
||||||
actor.set_pivot_point(0.5, 0.5);
|
actor.set_pivot_point(0.5, 0.5);
|
||||||
|
|
||||||
|
// This goes through all properties given in the config object and tweaks any ongoing
|
||||||
|
// transitions accordingly. If there is no ongoing transition for a given property, a
|
||||||
|
// new one is set up.
|
||||||
for (const property in config) {
|
for (const property in config) {
|
||||||
const from = config[property].from;
|
|
||||||
const to = config[property].to;
|
|
||||||
const mode = config[property].mode;
|
|
||||||
|
|
||||||
let transition = actor.get_transition(property);
|
let transition = actor.get_transition(property);
|
||||||
|
|
||||||
|
// If there is currently no ongoing transition, we create a new one. Clutter does
|
||||||
|
// not like to create transitions with the same start and end value - however, we
|
||||||
|
// need at least one transition for our progress value in the shader. So we trick
|
||||||
|
// Clutter by creating an arbitrary transition first and then modifying the start
|
||||||
|
// and end values according to our config object.
|
||||||
if (!transition) {
|
if (!transition) {
|
||||||
actor.set_property(property, 0);
|
actor.set_property(property, 0);
|
||||||
actor.save_easing_state();
|
actor.save_easing_state();
|
||||||
@@ -359,20 +401,27 @@ class Extension {
|
|||||||
actor.set_property(property, 1);
|
actor.set_property(property, 1);
|
||||||
actor.restore_easing_state();
|
actor.restore_easing_state();
|
||||||
|
|
||||||
|
// Now there should be a transition!
|
||||||
transition = actor.get_transition(property);
|
transition = actor.get_transition(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For some reason, there are rare cases, where no transition is set up. We do not
|
||||||
|
// try to continue here...
|
||||||
if (!transition) {
|
if (!transition) {
|
||||||
|
utils.debug('Failed to set up transitions.');
|
||||||
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
this._fixAnimationTimes(isDialogWindow, forOpening, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tweak the transition according to the config object.
|
||||||
transition.set_duration(duration);
|
transition.set_duration(duration);
|
||||||
transition.set_to(to);
|
transition.set_to(config[property].to);
|
||||||
transition.set_from(from);
|
transition.set_from(config[property].from);
|
||||||
transition.set_progress_mode(mode);
|
transition.set_progress_mode(config[property].mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There should always be a scale-y transitions. Once this is finished, we restore the
|
||||||
|
// original actor size.
|
||||||
const transition = actor.get_transition('scale-y');
|
const transition = actor.get_transition('scale-y');
|
||||||
transition.connect('completed', () => {
|
transition.connect('completed', () => {
|
||||||
actor.scale_x = 1.0;
|
actor.scale_x = 1.0;
|
||||||
@@ -381,10 +430,11 @@ class Extension {
|
|||||||
|
|
||||||
// -------------------------------------------------------------------- add the shader
|
// -------------------------------------------------------------------- add the shader
|
||||||
|
|
||||||
// Add a cool shader to our window actor!
|
// Now add a cool shader to our window actor!
|
||||||
const shader = this._currentEffect.createShader(actor, this._settings, forOpening);
|
const shader = this._currentEffect.createShader(actor, this._settings, forOpening);
|
||||||
|
|
||||||
if (shader) {
|
if (shader) {
|
||||||
|
// First remove any old effect.
|
||||||
actor.remove_effect_by_name(`burn-my-windows-effect`);
|
actor.remove_effect_by_name(`burn-my-windows-effect`);
|
||||||
actor.add_effect_with_name(`burn-my-windows-effect`, shader);
|
actor.add_effect_with_name(`burn-my-windows-effect`, shader);
|
||||||
|
|
||||||
@@ -401,6 +451,8 @@ class Extension {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, ensure that all animation times are set properly so that other extensions
|
||||||
|
// may guess how long it will take until windows are gone :)
|
||||||
this._fixAnimationTimes(isDialogWindow, forOpening, duration);
|
this._fixAnimationTimes(isDialogWindow, forOpening, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user