Merge branch 'main' of github.com:Schneegans/Burn-My-Windows
@@ -116,6 +116,8 @@ jobs:
|
|||||||
- '35'
|
- '35'
|
||||||
- '36'
|
- '36'
|
||||||
- '37'
|
- '37'
|
||||||
|
- '38'
|
||||||
|
- 'rawhide'
|
||||||
session:
|
session:
|
||||||
- 'gnome-xsession'
|
- 'gnome-xsession'
|
||||||
- 'gnome-wayland-nested'
|
- 'gnome-wayland-nested'
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pot: $(JS_FILES) $(UI_FILES)
|
|||||||
|
|
||||||
# This runs several tests in containerized versions of GNOME Shell.
|
# This runs several tests in containerized versions of GNOME Shell.
|
||||||
test:
|
test:
|
||||||
@ for version in 32 33 34 35 36 37 ; do \
|
@ for version in 32 33 34 35 36 37 38 "rawhide" ; do \
|
||||||
for session in "gnome-xsession" "gnome-wayland-nested" ; do \
|
for session in "gnome-xsession" "gnome-wayland-nested" ; do \
|
||||||
echo ; \
|
echo ; \
|
||||||
echo "Running Tests on Fedora $$version ($$session)." ; \
|
echo "Running Tests on Fedora $$version ($$session)." ; \
|
||||||
@@ -59,7 +59,7 @@ test:
|
|||||||
|
|
||||||
# This re-generates all reference images required by the tests.
|
# This re-generates all reference images required by the tests.
|
||||||
references:
|
references:
|
||||||
@ for version in 32 33 34 35 36 37 ; do \
|
@ for version in 32 33 34 35 36 37 38 "rawhide" ; do \
|
||||||
for session in "gnome-xsession" "gnome-wayland-nested" ; do \
|
for session in "gnome-xsession" "gnome-wayland-nested" ; do \
|
||||||
echo ; \
|
echo ; \
|
||||||
echo "Generating References for Fedora $$version ($$session)." ; \
|
echo "Generating References for Fedora $$version ($$session)." ; \
|
||||||
|
|||||||
@@ -133,90 +133,113 @@ class Extension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We will monkey-patch these methods. Let's store the original ones.
|
// We will monkey-patch these methods. Let's store the original ones.
|
||||||
|
this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor;
|
||||||
|
this._origWaitForOverviewToHide = WindowManager.prototype._waitForOverviewToHide;
|
||||||
this._origAddWindowClone = Workspace.prototype._addWindowClone;
|
this._origAddWindowClone = Workspace.prototype._addWindowClone;
|
||||||
this._origWindowRemoved = Workspace.prototype._windowRemoved;
|
this._origWindowRemoved = Workspace.prototype._windowRemoved;
|
||||||
this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow;
|
this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow;
|
||||||
this._origShouldAnimateActor = WindowManager.prototype._shouldAnimateActor;
|
|
||||||
this._origWaitForOverviewToHide = WindowManager.prototype._waitForOverviewToHide;
|
|
||||||
this._origDestroyWindowDone = WindowManager.prototype._destroyWindowDone;
|
|
||||||
|
|
||||||
// ------------------------------------------------ patching the window-open animation
|
// ------------------------------- patching the window animations outside the overview
|
||||||
|
|
||||||
// Here we add an effect to the window-open animation. This is done whenever a new
|
// If a window is created, the transitions are set up in the async _mapWindow() of the
|
||||||
// window is created. Usually, there is no real window animation for opening windows
|
|
||||||
// in the overview - only the window's clone is animated - but thanks to the hacks
|
|
||||||
// below, we can show the real animations in the overview.
|
|
||||||
|
|
||||||
// If a window is created the transitions are set up in the async _mapWindow of the
|
|
||||||
// WindowManager:
|
// WindowManager:
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1478
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1487
|
||||||
// AFAIK, overriding this method is not possible as it's called by a signal to
|
// AFAIK, overriding this method is not possible as it's called by a signal to which
|
||||||
// which it is bound via the bind() method. To tweak the async transition
|
// it is bound via the bind() method. To tweak the async transition anyways, we
|
||||||
// anyways, we override the actors ease() method once - the next time it will be
|
// override the actors ease() method once. We do this in _shouldAnimateActor() which
|
||||||
// called by the _mapWindow(), we will intercept it!
|
// is called right before the ease() in _mapWindow:
|
||||||
this._windowCreatedConnection =
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1472
|
||||||
global.display.connect('window-created', (d, metaWin) => {
|
|
||||||
let actor = metaWin.get_compositor_private();
|
|
||||||
|
|
||||||
const orig = actor.ease;
|
// The same trick is done for the window-close animation. This is set up in a similar
|
||||||
actor.ease = function(...params) {
|
// fashion in the WindowManager's _destroyWindow():
|
||||||
orig.apply(actor, params);
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1558.
|
||||||
actor.ease = orig;
|
// Here is _shouldAnimateActor() also called right before. So we use it again to
|
||||||
|
// monkey-patch the window actor's ease() once.
|
||||||
|
|
||||||
// There are cases where the ease() is called prior to mapping the actor. If
|
// We override WindowManager._shouldAnimateActor() also for another purpose: Usually,
|
||||||
// the actor is not yet mapped, we defer the effect creation.
|
// it returns false when we are in the overview. This prevents the window animations
|
||||||
if (actor.mapped) {
|
// there. To enable animations in the overview, we check inside the method whether it
|
||||||
extensionThis._setupEffect(actor, true);
|
// was called by either _mapWindow or _destroyWindow. If so, we return true. Let's see
|
||||||
} else {
|
// if this breaks stuff left and right...
|
||||||
const connectionID = actor.connect('notify::mapped', () => {
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1120
|
||||||
extensionThis._setupEffect(actor, true);
|
WindowManager.prototype._shouldAnimateActor = function(actor, types) {
|
||||||
actor.disconnect(connectionID);
|
const caller = (new Error()).stack.split('\n')[1];
|
||||||
});
|
const forClosing = caller.includes('_destroyWindow@');
|
||||||
}
|
const forOpening = caller.includes('_mapWindow@');
|
||||||
};
|
|
||||||
|
// This is also called in other cases, for instance when minimizing windows. We are
|
||||||
|
// only interested in window opening and window closing for now.
|
||||||
|
if (forClosing || forOpening) {
|
||||||
|
|
||||||
|
// If there is an applicable effect profile, we intercept the ease() method to
|
||||||
|
// setup our own effect.
|
||||||
|
const chosenEffect = extensionThis._chooseEffect(actor, forOpening);
|
||||||
|
|
||||||
|
if (chosenEffect) {
|
||||||
|
// Store the original ease() method of the actor.
|
||||||
|
const orig = actor.ease;
|
||||||
|
|
||||||
|
// Now intercept the next call to actor.ease().
|
||||||
|
actor.ease = function(...params) {
|
||||||
|
// Quickly restore the original behavior. Nobody noticed, I guess :D
|
||||||
|
actor.ease = orig;
|
||||||
|
|
||||||
|
// And then create the effect!
|
||||||
|
extensionThis._setupEffect(actor, forOpening, chosenEffect.effect,
|
||||||
|
chosenEffect.profile);
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return extensionThis._origShouldAnimateActor.apply(this, [actor, types]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure to remove any effects if requested by the window manager.
|
||||||
|
this._killEffectsSignal =
|
||||||
|
global.window_manager.connect('kill-window-effects', (wm, actor) => {
|
||||||
|
const shader = actor.get_effect('burn-my-windows-effect');
|
||||||
|
if (shader) {
|
||||||
|
shader.endAnimation();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// --------------------------------------------- fix window animations in the overview
|
||||||
|
|
||||||
// Some of the effects require that the window's actor is enlarged to provide a bigger
|
// 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
|
// 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
|
// of the actor. However, if we are in the overview, we have to enlarge the clone of
|
||||||
// the window as well.
|
// the window as well.
|
||||||
Workspace.prototype._addWindowClone = function(...params) {
|
Workspace.prototype._addWindowClone = function(...params) {
|
||||||
const result = extensionThis._origAddWindowClone.apply(this, params);
|
const clone = extensionThis._origAddWindowClone.apply(this, params);
|
||||||
|
|
||||||
// The parameters of this method changed a bit through the versions...
|
// The parameters of this method changed a bit through the versions...
|
||||||
let realWindow, clone;
|
let realWindow, container;
|
||||||
|
|
||||||
if (utils.shellVersionIs(3, 36)) {
|
if (utils.shellVersionIs(3, 36)) {
|
||||||
clone = result[0];
|
container = clone[0];
|
||||||
realWindow = clone.realWindow;
|
realWindow = container.realWindow;
|
||||||
} else if (utils.shellVersionIs(3, 38)) {
|
} else if (utils.shellVersionIs(3, 38)) {
|
||||||
clone = result._windowContainer;
|
container = clone._windowContainer;
|
||||||
realWindow = params[0].get_compositor_private();
|
realWindow = params[0].get_compositor_private();
|
||||||
} else {
|
} else {
|
||||||
clone = result.window_container;
|
container = clone.window_container;
|
||||||
realWindow = params[0].get_compositor_private();
|
realWindow = params[0].get_compositor_private();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syncing the real window's scale with the scale of its clone only works on GNOME
|
// Store the overview clone as temporary members of the real window actor. When we
|
||||||
// Shell 3.38+. So effects cannot scale windows in the overview of GNOME 3.36...
|
// set up the effect, we will check for the existence of these and enlarge the clone
|
||||||
if (utils.shellVersionIsAtLeast(3, 38)) {
|
// as needed.
|
||||||
const xID = realWindow.connect('notify::scale-x', () => {
|
realWindow._bmwOverviewClone = clone;
|
||||||
if (realWindow.scale_x > 0 && clone.allocation.get_size()[0] > 0) {
|
realWindow._bmwOverviewCloneContainer = container;
|
||||||
clone.scale_x = realWindow.scale_x;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const yID = realWindow.connect('notify::scale-y', () => {
|
// Remove the temporary members again once the clone is deleted.
|
||||||
if (realWindow.scale_y > 0 && clone.allocation.get_size()[1] > 0) {
|
container.connect('destroy', () => {
|
||||||
clone.scale_y = realWindow.scale_y;
|
delete realWindow._bmwOverviewClone;
|
||||||
}
|
delete realWindow._bmwOverviewCloneContainer;
|
||||||
});
|
});
|
||||||
|
|
||||||
clone.connect('destroy', () => {
|
|
||||||
realWindow.disconnect(xID);
|
|
||||||
realWindow.disconnect(yID);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is actually needed for the window-close animation on GNOME Shell 3.36.
|
// This is actually needed 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
|
||||||
@@ -225,10 +248,10 @@ class Extension {
|
|||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/workspace.js#L1877
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/gnome-3-36/js/ui/workspace.js#L1877
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1363
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1363
|
||||||
if (utils.shellVersionIs(3, 36)) {
|
if (utils.shellVersionIs(3, 36)) {
|
||||||
clone.connect('destroy', () => this._doRemoveWindow(clone.metaWindow));
|
container.connect('destroy', () => this._doRemoveWindow(container.metaWindow));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return clone;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Usually, windows are faded in after the overview is completely hidden. We enable
|
// Usually, windows are faded in after the overview is completely hidden. We enable
|
||||||
@@ -237,61 +260,11 @@ class Extension {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Here comes the ULTRA-HACK: The method below is called (amongst others) by the
|
|
||||||
// _destroyWindow and _mapWindow methods of the WindowManager. Usually, it returns
|
|
||||||
// false when we are in the overview. This prevents the window animations. As we
|
|
||||||
// cannot monkey-patch the _destroyWindow or _mapWindow methods themselves, we check
|
|
||||||
// inside the method below whether it was called by either of those. If so, we return
|
|
||||||
// true. Let's see if this breaks stuff left and right...
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1124
|
|
||||||
WindowManager.prototype._shouldAnimateActor = function(...params) {
|
|
||||||
const caller = (new Error()).stack.split('\n')[1];
|
|
||||||
if (caller.includes('_destroyWindow@') || caller.includes('_mapWindow@')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return extensionThis._origShouldAnimateActor.apply(this, params);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------------------- patching the window-close animation
|
|
||||||
|
|
||||||
// The signal handler below and the following patch are all which is required outside
|
|
||||||
// of the overview. All other hacks further below are just required to defer the
|
|
||||||
// window-hiding in the overview until the effect is finished.
|
|
||||||
|
|
||||||
// The close animation is set up in WindowManager's _destroyWindow:
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1554
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Once the window-close animation is is finished, the window manager's
|
|
||||||
// _destroyWindowDone is called. We use this to free the effect so that it can be
|
|
||||||
// re-used in future.
|
|
||||||
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1564
|
|
||||||
WindowManager.prototype._destroyWindowDone = function(shellwm, actor) {
|
|
||||||
if (this._destroying.has(actor)) {
|
|
||||||
const shader = actor.get_effect('burn-my-windows-effect');
|
|
||||||
if (shader) {
|
|
||||||
actor.remove_effect(shader);
|
|
||||||
shader.endAnimation();
|
|
||||||
shader.returnToFactory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the original method.
|
|
||||||
extensionThis._origDestroyWindowDone.apply(this, [shellwm, actor]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// These three method overrides are mega-hacky! Usually, windows are not faded when
|
// 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
|
// 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
|
// actually faded out. To do this, _windowRemoved and _doRemoveWindow now check
|
||||||
// whether there is a transition ongoing (via extensionThis._shouldDestroy). If that's
|
// 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 case, these methods do nothing.
|
||||||
// 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#L1258
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1258
|
||||||
Workspace.prototype._windowRemoved = function(ws, metaWin) {
|
Workspace.prototype._windowRemoved = function(ws, metaWin) {
|
||||||
if (extensionThis._shouldDestroy(this, metaWin)) {
|
if (extensionThis._shouldDestroy(this, metaWin)) {
|
||||||
@@ -369,16 +342,14 @@ class Extension {
|
|||||||
// Disable the window-picking D-Bus API.
|
// Disable the window-picking D-Bus API.
|
||||||
this._windowPicker.unexport();
|
this._windowPicker.unexport();
|
||||||
|
|
||||||
// Restore the original window-open and window-close animations.
|
global.window_manager.disconnect(this._killEffectsSignal);
|
||||||
global.window_manager.disconnect(this._destroyConnection);
|
|
||||||
global.display.disconnect(this._windowCreatedConnection);
|
|
||||||
|
|
||||||
|
// Restore the original window-open and window-close animations.
|
||||||
Workspace.prototype._addWindowClone = this._origAddWindowClone;
|
Workspace.prototype._addWindowClone = this._origAddWindowClone;
|
||||||
Workspace.prototype._windowRemoved = this._origWindowRemoved;
|
Workspace.prototype._windowRemoved = this._origWindowRemoved;
|
||||||
Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow;
|
Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow;
|
||||||
WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor;
|
WindowManager.prototype._shouldAnimateActor = this._origShouldAnimateActor;
|
||||||
WindowManager.prototype._waitForOverviewToHide = this._origWaitForOverviewToHide;
|
WindowManager.prototype._waitForOverviewToHide = this._origWaitForOverviewToHide;
|
||||||
WindowManager.prototype._destroyWindowDone = this._origDestroyWindowDone;
|
|
||||||
|
|
||||||
if (WindowPreview) {
|
if (WindowPreview) {
|
||||||
WindowPreview.prototype._deleteAll = this._origDeleteAll;
|
WindowPreview.prototype._deleteAll = this._origDeleteAll;
|
||||||
@@ -427,11 +398,10 @@ class Extension {
|
|||||||
this._profiles.sort((a, b) => b.priority - a.priority);
|
this._profiles.sort((a, b) => b.priority - a.priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method adds one of the configured effects to the given actor. First, a profile
|
// This method selects an effect profile matching the current circumstances. Then a
|
||||||
// matching the current circumstances is chosen. Then a random effect from its enabled
|
// random effect from its enabled effects will be selected. It returns null if no
|
||||||
// effects will be selected. This will also tweak the transitions of the given actor
|
// profile is currently applicable.
|
||||||
// (e.g. scale it up if required).
|
_chooseEffect(actor, forOpening) {
|
||||||
_setupEffect(actor, forOpening) {
|
|
||||||
|
|
||||||
// For now, we only add effects to normal windows and dialog windows.
|
// For now, we 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;
|
||||||
@@ -440,17 +410,7 @@ class Extension {
|
|||||||
actor.meta_window.window_type == Meta.WindowType.DIALOG;
|
actor.meta_window.window_type == Meta.WindowType.DIALOG;
|
||||||
|
|
||||||
if (!isNormalWindow && !isDialogWindow) {
|
if (!isNormalWindow && !isDialogWindow) {
|
||||||
return;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
// There is the weird case where an animation is already ongoing. This happens when a
|
|
||||||
// window is closed which has been created before the session was started (e.g. when
|
|
||||||
// GNOME Shell has been restarted in the meantime).
|
|
||||||
const oldShader = actor.get_effect('burn-my-windows-effect');
|
|
||||||
if (oldShader) {
|
|
||||||
actor.remove_effect(oldShader);
|
|
||||||
oldShader.endAnimation();
|
|
||||||
oldShader.returnToFactory();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------- choose a profile and then an effect
|
// ----------------------------------------------- choose a profile and then an effect
|
||||||
@@ -556,11 +516,23 @@ class Extension {
|
|||||||
|
|
||||||
// If nothing was enabled, we have to do nothing :)
|
// If nothing was enabled, we have to do nothing :)
|
||||||
if (!effect || !profile) {
|
if (!effect || !profile) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {effect: effect, profile: profile};
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------- tweak actor transitions
|
// This method adds the given effect using the settings from the given profile to the
|
||||||
|
// given actor.
|
||||||
|
_setupEffect(actor, forOpening, effect, profile) {
|
||||||
|
|
||||||
|
// There is the weird case where an animation is already ongoing. This happens when a
|
||||||
|
// window is closed which has been created before the session was started (e.g. when
|
||||||
|
// GNOME Shell has been restarted in the meantime).
|
||||||
|
const oldShader = actor.get_effect('burn-my-windows-effect');
|
||||||
|
if (oldShader) {
|
||||||
|
oldShader.endAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
// If we are currently performing integration test, all animations are set to a fixed
|
// If we are currently performing integration test, all animations are set to a fixed
|
||||||
// duration and show a fixed frame from the middle of the animation.
|
// duration and show a fixed frame from the middle of the animation.
|
||||||
@@ -572,97 +544,60 @@ class Extension {
|
|||||||
// actorScale.
|
// actorScale.
|
||||||
const actorScale = effect.getActorScale(profile.settings, forOpening, actor);
|
const actorScale = effect.getActorScale(profile.settings, forOpening, actor);
|
||||||
|
|
||||||
|
// All scaling is relative to the window's center.
|
||||||
|
actor.set_pivot_point(0.5, 0.5);
|
||||||
|
actor.opacity = 255;
|
||||||
|
actor.scale_x = actorScale.x;
|
||||||
|
actor.scale_y = actorScale.y;
|
||||||
|
|
||||||
|
// If we are in the overview, we have to enlarge the window's clone as well. We also
|
||||||
|
// disable the clone's overlay (e.g. its icon, name, and close button) during the
|
||||||
|
// animation.
|
||||||
|
if (actor._bmwOverviewClone && utils.shellVersionIsAtLeast(3, 38)) {
|
||||||
|
actor._bmwOverviewClone.overlayEnabled = false;
|
||||||
|
actor._bmwOverviewCloneContainer.set_pivot_point(0.5, 0.5);
|
||||||
|
actor._bmwOverviewCloneContainer.scale_x = actorScale.x;
|
||||||
|
actor._bmwOverviewCloneContainer.scale_y = actorScale.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now add a cool shader to our window actor!
|
||||||
|
const shader = effect.shaderFactory.getShader();
|
||||||
|
actor.add_effect_with_name('burn-my-windows-effect', shader);
|
||||||
|
|
||||||
|
// At the end of the animation, we restore the scale of the overview clone (if any)
|
||||||
|
// and call the methods which would have been called by the original ease() calls at
|
||||||
|
// the end of the standard fade-in animation.
|
||||||
|
const endID = shader.connect('end-animation', () => {
|
||||||
|
shader.disconnect(endID);
|
||||||
|
|
||||||
|
if (actor._bmwOverviewClone && utils.shellVersionIsAtLeast(3, 38)) {
|
||||||
|
actor._bmwOverviewClone.overlayEnabled = true;
|
||||||
|
actor._bmwOverviewCloneContainer.scale_x = 1.0;
|
||||||
|
actor._bmwOverviewCloneContainer.scale_y = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the shader and mark it being re-usable for future animations.
|
||||||
|
actor.remove_effect(shader);
|
||||||
|
shader.returnToFactory();
|
||||||
|
|
||||||
|
// Finally, once the animation is done or interrupted, we call the methods which
|
||||||
|
// should have been called by the original ease() methods.
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1487
|
||||||
|
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1558.
|
||||||
|
if (forOpening) {
|
||||||
|
Main.wm._mapWindowDone(global.window_manager, actor);
|
||||||
|
} else {
|
||||||
|
Main.wm._destroyWindowDone(global.window_manager, actor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// To make things deterministic during testing, we set the effect duration to 5
|
// To make things deterministic during testing, we set the effect duration to 5
|
||||||
// seconds.
|
// seconds.
|
||||||
const duration =
|
const duration =
|
||||||
testMode ? 5000 : profile.settings.get_int(effect.getNick() + '-animation-time');
|
testMode ? 5000 : profile.settings.get_int(effect.getNick() + '-animation-time');
|
||||||
|
|
||||||
// All animations are relative to the window's center.
|
// Finally start the animation!
|
||||||
actor.set_pivot_point(0.5, 0.5);
|
shader.beginAnimation(profile.settings, forOpening, testMode, duration, actor);
|
||||||
|
|
||||||
// We tweak the opacity and scale of the actor. If there is no ongoing transition for
|
|
||||||
// a property, a new one is set up.
|
|
||||||
const config = {'opacity': 255, 'scale-x': actorScale.x, 'scale-y': actorScale.y};
|
|
||||||
|
|
||||||
for (const property in config) {
|
|
||||||
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) {
|
|
||||||
actor.set_property(property, 0);
|
|
||||||
actor.save_easing_state();
|
|
||||||
actor.set_easing_duration(1000);
|
|
||||||
actor.set_property(property, 1);
|
|
||||||
actor.restore_easing_state();
|
|
||||||
|
|
||||||
// Now there should be a transition!
|
|
||||||
transition = actor.get_transition(property);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tweak the transition according to the config object. For some reason, there are
|
|
||||||
// rare cases, where no transition is set up. This happens from time to time...
|
|
||||||
if (transition) {
|
|
||||||
transition.set_duration(duration);
|
|
||||||
transition.set_to(config[property]);
|
|
||||||
transition.set_from(config[property]);
|
|
||||||
transition.set_progress_mode(Clutter.AnimationMode.LINEAR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Once the transitions are finished, we restore the original actor size.
|
|
||||||
if (forOpening) {
|
|
||||||
const connectionID = actor.connect('transitions-completed', () => {
|
|
||||||
actor.scale_x = 1.0;
|
|
||||||
actor.scale_y = 1.0;
|
|
||||||
actor.disconnect(connectionID);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------- add the shader
|
|
||||||
|
|
||||||
// Now add a cool shader to our window actor!
|
|
||||||
const shader = effect.shaderFactory.getShader();
|
|
||||||
|
|
||||||
// There should always be an opacity transition going on...
|
|
||||||
const transition = actor.get_transition('opacity');
|
|
||||||
|
|
||||||
if (!transition) {
|
|
||||||
utils.debug('Cannot setup shader without opacity transition.')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign the effect to the window actor!
|
|
||||||
actor.add_effect_with_name('burn-my-windows-effect', shader);
|
|
||||||
|
|
||||||
// Set one-time uniforms.
|
|
||||||
shader.beginAnimation(profile.settings, forOpening, testMode, duration * 0.001,
|
|
||||||
actor);
|
|
||||||
|
|
||||||
// Set other uniforms each frame.
|
|
||||||
transition.connect('new-frame', (t) => {
|
|
||||||
if (testMode) {
|
|
||||||
shader.updateAnimation(0.5);
|
|
||||||
} else {
|
|
||||||
shader.updateAnimation(t.get_progress());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the effect if the animation finished or was interrupted.
|
|
||||||
if (forOpening) {
|
|
||||||
transition.connect('stopped', () => {
|
|
||||||
const oldShader = actor.get_effect('burn-my-windows-effect');
|
|
||||||
if (oldShader) {
|
|
||||||
actor.remove_effect(oldShader);
|
|
||||||
oldShader.endAnimation();
|
|
||||||
oldShader.returnToFactory();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is required to enable window-close animations in the overview. See the comment
|
// This is required to enable window-close animations in the overview. See the comment
|
||||||
@@ -676,11 +611,9 @@ class Extension {
|
|||||||
// This was called "realWindow" in GNOME 3.36.
|
// This was called "realWindow" in GNOME 3.36.
|
||||||
const propertyName = utils.shellVersionIs(3, 36) ? 'realWindow' : '_windowActor';
|
const propertyName = utils.shellVersionIs(3, 36) ? 'realWindow' : '_windowActor';
|
||||||
const actor = workspace._windows[index][propertyName];
|
const actor = workspace._windows[index][propertyName];
|
||||||
if (!actor.get_transition('scale-y')) {
|
const shader = actor.get_effect('burn-my-windows-effect');
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return shader == null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
const {Gio, Shell, GObject, Clutter} = imports.gi;
|
const {Gio, Shell, GObject, Clutter} = imports.gi;
|
||||||
const ByteArray = imports.byteArray;
|
const ByteArray = imports.byteArray;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||||
const utils = Me.imports.src.utils;
|
const utils = Me.imports.src.utils;
|
||||||
@@ -36,12 +37,14 @@ const utils = Me.imports.src.utils;
|
|||||||
// hard-coded use of straight alpha (as opposed to premultiplied). This makes the //
|
// hard-coded use of straight alpha (as opposed to premultiplied). This makes the //
|
||||||
// shaders a bit more complicated than required. //
|
// shaders a bit more complicated than required. //
|
||||||
// //
|
// //
|
||||||
// The Shader fires two signals: //
|
// The Shader fires three signals: //
|
||||||
// * begin-animation: This is called each time a new animation is started. It can //
|
// * begin-animation: This is called each time a new animation is started. It can //
|
||||||
// be used to set uniform values which do not change during the //
|
// be used to set uniform values which do not change during the //
|
||||||
// animation. //
|
// animation. //
|
||||||
// * update-animation: This is called at each frame during the animation. It can be //
|
// * update-animation: This is called at each frame during the animation. It can be //
|
||||||
// used to set uniforms which change during the animation. //
|
// used to set uniforms which change during the animation. //
|
||||||
|
// * end-animation: This is called when the animation is stopped. This can be //
|
||||||
|
// used to clean up any resources.
|
||||||
//////////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
var Shader = GObject.registerClass(
|
var Shader = GObject.registerClass(
|
||||||
@@ -77,13 +80,42 @@ var Shader = GObject.registerClass(
|
|||||||
this._uDuration = this.get_uniform_location('uDuration');
|
this._uDuration = this.get_uniform_location('uDuration');
|
||||||
this._uSize = this.get_uniform_location('uSize');
|
this._uSize = this.get_uniform_location('uSize');
|
||||||
this._uPadding = this.get_uniform_location('uPadding');
|
this._uPadding = this.get_uniform_location('uPadding');
|
||||||
|
|
||||||
|
// Create a timeline to drive the animation.
|
||||||
|
this._timeline = new Clutter.Timeline();
|
||||||
|
|
||||||
|
// Call updateAnimation() once a frame.
|
||||||
|
this._timeline.connect('new-frame', (t) => {
|
||||||
|
if (this._testMode) {
|
||||||
|
this.updateAnimation(0.5);
|
||||||
|
} else {
|
||||||
|
this.updateAnimation(t.get_progress());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up if the animation finished or was interrupted.
|
||||||
|
this._timeline.connect('stopped', (t, finished) => {
|
||||||
|
this.endAnimation();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called once each time the shader is used.
|
// This is called once each time the shader is used.
|
||||||
beginAnimation(settings, forOpening, testMode, duration, actor) {
|
beginAnimation(settings, forOpening, testMode, duration, actor) {
|
||||||
|
if (this._timeline.is_playing()) {
|
||||||
|
this._timeline.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// On GNOME 3.36 this method was not yet available.
|
||||||
|
if (this._timeline.set_actor) {
|
||||||
|
this._timeline.set_actor(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._timeline.set_duration(duration);
|
||||||
|
this._timeline.start();
|
||||||
|
|
||||||
// Reset progress value.
|
// Reset progress value.
|
||||||
this._progress = 0;
|
this._progress = 0;
|
||||||
|
this._testMode = testMode;
|
||||||
|
|
||||||
// This is not necessarily symmetric, but I haven't figured out a way to
|
// This is not necessarily symmetric, but I haven't figured out a way to
|
||||||
// get the actual values...
|
// get the actual values...
|
||||||
@@ -91,7 +123,7 @@ var Shader = GObject.registerClass(
|
|||||||
|
|
||||||
this.set_uniform_float(this._uPadding, 1, [padding]);
|
this.set_uniform_float(this._uPadding, 1, [padding]);
|
||||||
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
this.set_uniform_float(this._uForOpening, 1, [forOpening]);
|
||||||
this.set_uniform_float(this._uDuration, 1, [duration]);
|
this.set_uniform_float(this._uDuration, 1, [duration * 0.001]);
|
||||||
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
|
this.set_uniform_float(this._uSize, 2, [actor.width, actor.height]);
|
||||||
|
|
||||||
this.emit('begin-animation', settings, forOpening, testMode, actor);
|
this.emit('begin-animation', settings, forOpening, testMode, actor);
|
||||||
@@ -103,11 +135,18 @@ var Shader = GObject.registerClass(
|
|||||||
// in vfunc_paint_target. We do not emit it here, as the pipeline which may be used
|
// in vfunc_paint_target. We do not emit it here, as the pipeline which may be used
|
||||||
// by handlers must not have been created yet.
|
// by handlers must not have been created yet.
|
||||||
this._progress = progress;
|
this._progress = progress;
|
||||||
|
|
||||||
|
this.queue_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will just emit the end-animation signal. It can be used to clean up any
|
// This will stop any running animation and emit the end-animation signal.
|
||||||
// resources required during the animation.
|
|
||||||
endAnimation() {
|
endAnimation() {
|
||||||
|
// This will call endAnimation() again, so we can return for now.
|
||||||
|
if (this._timeline.is_playing()) {
|
||||||
|
this._timeline.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.emit('end-animation');
|
this.emit('end-animation');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 664 B |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 627 B |
|
After Width: | Height: | Size: 643 B |
|
After Width: | Height: | Size: 643 B |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 424 B |
|
After Width: | Height: | Size: 443 B |
|
After Width: | Height: | Size: 443 B |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 416 B |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 507 B |
|
After Width: | Height: | Size: 507 B |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 500 B |
|
After Width: | Height: | Size: 507 B |
|
After Width: | Height: | Size: 507 B |