diff options
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/extension.js')
| -rw-r--r-- | raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/extension.js | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/extension.js b/raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/extension.js new file mode 100644 index 0000000..cff39d8 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/burn-my-windows@schneegans.github.com/extension.js @@ -0,0 +1,631 @@ +////////////////////////////////////////////////////////////////////////////////////////// +// ) ( // +// ( /( ( ( ) ( ( ( ( )\ ) ( ( // +// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // +// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // +// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // +// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // +// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // +// |__/ // +////////////////////////////////////////////////////////////////////////////////////////// + +// SPDX-FileCopyrightText: Simon Schneegans <code@simonschneegans.de> +// SPDX-License-Identifier: GPL-3.0-or-later + +'use strict'; + +import Gio from 'gi://Gio'; +import Meta from 'gi://Meta'; + +import {fromVersion26} from './src/migrate.js'; +import {ProfileManager} from './src/ProfileManager.js'; +import {WindowPicker} from './src/WindowPicker.js'; +import * as utils from './src/utils.js'; + +import Apparition from './src/effects/Apparition.js'; +import AuraGlow from './src/effects/AuraGlow.js'; +import BrokenGlass from './src/effects/BrokenGlass.js'; +import Doom from './src/effects/Doom.js'; +import EnergizeA from './src/effects/EnergizeA.js'; +import EnergizeB from './src/effects/EnergizeB.js'; +import Fire from './src/effects/Fire.js'; +import Focus from './src/effects/Focus.js'; +import Glide from './src/effects/Glide.js'; +import Glitch from './src/effects/Glitch.js'; +import Hexagon from './src/effects/Hexagon.js'; +import Incinerate from './src/effects/Incinerate.js'; +import Matrix from './src/effects/Matrix.js'; +import Mushroom from './src/effects/Mushroom.js'; +import PaintBrush from './src/effects/PaintBrush.js'; +import Pixelate from './src/effects/Pixelate.js'; +import PixelWheel from './src/effects/PixelWheel.js'; +import PixelWipe from './src/effects/PixelWipe.js'; +import Portal from './src/effects/Portal.js'; +import RGBWarp from './src/effects/RGBWarp.js'; +import SnapOfDisintegration from './src/effects/SnapOfDisintegration.js'; +import TeamRocket from './src/effects/TeamRocket.js'; +import TRexAttack from './src/effects/TRexAttack.js'; +import TVEffect from './src/effects/TVEffect.js'; +import TVGlitch from './src/effects/TVGlitch.js'; +import Wisps from './src/effects/Wisps.js'; + +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import {Workspace} from 'resource:///org/gnome/shell/ui/workspace.js'; +import {WindowPreview} from 'resource:///org/gnome/shell/ui/windowPreview.js'; +import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; + + +////////////////////////////////////////////////////////////////////////////////////////// +// This extensions modifies the window-close and window-open animations with all kinds // +// of effects. The effects are implemented using GLSL shaders which are applied to the // +// window's Clutter.Actor. The extension is actually very simple, much of the // +// complexity comes from the fact that GNOME Shell usually does not show an animation // +// when a window is closed in the overview. Several methods need to be monkey-patched // +// to get this working. For more details, read the other comments in this file... // +////////////////////////////////////////////////////////////////////////////////////////// + +export default class BurnMyWindows extends Extension { + + // ------------------------------------------------------------------------ public stuff + + // This function could be called after the extension is enabled, which could be done + // from GNOME Tweaks, when you log in or when the screen is unlocked. + enable() { + + // New effects must be registered here and in prefs.js. + this._ALL_EFFECTS = [ + new Apparition(), new AuraGlow(), new BrokenGlass(), new Doom(), + new EnergizeA(), new EnergizeB(), new Fire(), new Focus(), + new Glide(), new Glitch(), new Hexagon(), new Incinerate(), + new Matrix(), new PaintBrush(), new Pixelate(), new PixelWheel(), + new PixelWipe(), new Portal(), new RGBWarp(), new SnapOfDisintegration(), + new TeamRocket(), new TRexAttack(), new TVEffect(), new TVGlitch(), + new Wisps(), new Mushroom() + ]; + + // Load all of our resources. + this._resources = + Gio.Resource.load(this.path + '/resources/burn-my-windows.gresource'); + Gio.resources_register(this._resources); + + // Store a reference to the settings object. + this._settings = this.getSettings(); + + // Now we check whether the extension settings need to be migrated from a previous + // version. If this is the case, we defer the profile loading until this is finished. + const lastVersion = this._settings.get_int('last-extension-version'); + if (lastVersion < this.metadata.version) { + if (lastVersion <= 26) { + // If the profile migration fails for some reason, the callback will create a + // default profile instead. + fromVersion26().finally(() => { + this._loadProfiles(); + this._settings.set_int('last-extension-version', this.metadata.version); + }); + } else { + this._loadProfiles(); + } + + } else { + this._loadProfiles(); + } + + // We reload all effect profiles whenever the currently edited profile in the + // preferences dialog changes. This is most likely a bit too often, but it will also + // happen whenever a new profile is created and whenever an old profile is deleted. + this._settings.connect('changed::active-profile', () => { + this._loadProfiles(); + }); + + // This is used to get the desktop's color scheme. + this._shellSettings = new Gio.Settings({schema: 'org.gnome.desktop.interface'}); + + // Enable the window-picking D-Bus API for the preferences dialog. + this._windowPicker = new WindowPicker(); + this._windowPicker.export(); + + // We will use extensionThis to refer to the extension inside the patched methods. + const extensionThis = this; + + // This is used to get the battery state. + const UPowerProxy = Gio.DBusProxy.makeProxyWrapper( + utils.getStringResource('/interfaces/org.freedesktop.UPower.xml')); + this._upowerProxy = new UPowerProxy(Gio.DBus.system, 'org.freedesktop.UPower', + '/org/freedesktop/UPower'); + + // This is used to get the current power profile. + try { + const PowerProfilesProxy = Gio.DBusProxy.makeProxyWrapper( + utils.getStringResource('/interfaces/net.hadess.PowerProfiles.xml')); + this._powerProfilesProxy = new PowerProfilesProxy( + Gio.DBus.system, 'net.hadess.PowerProfiles', '/net/hadess/PowerProfiles'); + } catch (e) { + // Maybe the service is masked... + } + + // We will monkey-patch these methods. Let's store the original ones. + this._origShouldAnimateActor = Main.wm._shouldAnimateActor; + this._origWaitForOverviewToHide = Main.wm._waitForOverviewToHide; + this._origAddWindowClone = Workspace.prototype._addWindowClone; + this._origWindowRemoved = Workspace.prototype._windowRemoved; + this._origDoRemoveWindow = Workspace.prototype._doRemoveWindow; + + // ------------------------------- patching the window animations outside the overview + + // If a window is created, the transitions are set up in the async _mapWindow() of the + // WindowManager: + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1436 + // 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. We do this in _shouldAnimateActor() which + // is called right before the ease() in _mapWindow: + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1465 + + // The same trick is done for the window-close animation. This is set up in a similar + // fashion in the WindowManager's _destroyWindow(): + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/windowManager.js#L1525 + // Here is _shouldAnimateActor() also called right before. So we use it again to + // monkey-patch the window actor's ease() once. + + // We override WindowManager._shouldAnimateActor() also for another purpose: Usually, + // it returns false when we are in the overview. This prevents the window animations + // there. To enable animations in the overview, we check inside the method whether it + // was called by either _mapWindow or _destroyWindow. If so, we return true. Let's see + // if this breaks stuff left and right... + Main.wm._shouldAnimateActor = function(actor, types) { + const stack = (new Error()).stack; + const forClosing = stack.includes('_destroyWindow@'); + const forOpening = stack.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) { + // There is a really weird issue in GNOME Shell 44: A few non-GTK windows are + // resized directly after they are mapped on X11. This happens for instance + // for keepassxc after it was closed in the maximized state. As the + // _mapWindow() method is called asynchronously, the window is not yet visible + // when the resize happens. Hence, our ease-override is called for the resize + // animation instead of the window-open or window-close animation. This is not + // what we want. So we check again whether the ease() call is for the + // window-open or window-close animation. If not, we just call the original + // ease() method. See also: + // https://github.com/Schneegans/Burn-My-Windows/issues/335 + const stack = (new Error()).stack; + const forClosing = stack.includes('_destroyWindow@'); + const forOpening = stack.includes('_mapWindow@'); + + if (forClosing || forOpening) { + // 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); + } else { + orig.apply(this, params); + } + }; + + 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 + // 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) { + const clone = extensionThis._origAddWindowClone.apply(this, params); + const container = clone.window_container; + const realWindow = params[0].get_compositor_private(); + + // Store the overview clone as temporary members of the real window actor. When we + // set up the effect, we will check for the existence of these and enlarge the clone + // as needed. + realWindow._bmwOverviewClone = clone; + realWindow._bmwOverviewCloneContainer = container; + + // Remove the temporary members again once the clone is deleted. + container.connect('destroy', () => { + delete realWindow._bmwOverviewClone; + delete realWindow._bmwOverviewCloneContainer; + }); + + return clone; + }; + + // Usually, windows are faded in after the overview is completely hidden. We enable + // window-open animations by not waiting for this. + Main.wm._waitForOverviewToHide = async function() { + return Promise.resolve(); + }; + + // 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 + // 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. + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1258 + Workspace.prototype._windowRemoved = function(ws, metaWin) { + if (extensionThis._shouldDestroy(this, metaWin)) { + extensionThis._origWindowRemoved.apply(this, [ws, metaWin]); + } + }; + + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/workspace.js#L1137 + Workspace.prototype._doRemoveWindow = function(metaWin) { + if (extensionThis._shouldDestroy(this, metaWin)) { + extensionThis._origDoRemoveWindow.apply(this, [metaWin]); + } + }; + + // With the code below, we hide the window-overlay (icon, label, close button) in the + // overview once the close-animation is running. + + // 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 + // Tweaks, when you log out or when the screen locks. + disable() { + + // Free all effect resources. + this._ALL_EFFECTS = []; + + // Unregister our resources. + Gio.resources_unregister(this._resources); + + // Disable the window-picking D-Bus API. + this._windowPicker.unexport(); + + global.window_manager.disconnect(this._killEffectsSignal); + + // Restore the original window-open and window-close animations. + Workspace.prototype._addWindowClone = this._origAddWindowClone; + Workspace.prototype._windowRemoved = this._origWindowRemoved; + Workspace.prototype._doRemoveWindow = this._origDoRemoveWindow; + Main.wm._shouldAnimateActor = this._origShouldAnimateActor; + Main.wm._waitForOverviewToHide = this._origWaitForOverviewToHide; + + WindowPreview.prototype._deleteAll = this._origDeleteAll; + WindowPreview.prototype._restack = this._origRestack; + WindowPreview.prototype._init = this._origInit; + + this._settings = null; + } + + // ----------------------------------------------------------------------- private stuff + + // This loads all effect profiles and assigns a priority to each profile. Whenever a + // window is opened or closed, the matching effect profile with the highest priority + // will be chosen. + // This method is called whenever the currently edited profile in the/ preferences + // dialog changes. This is most likely a bit too often, but it will also happen whenever + // a new profile is created and whenever an old profile is deleted. + _loadProfiles() { + + // Get all currently available profiles. + const profileManager = new ProfileManager(this.metadata); + this._profiles = profileManager.getProfiles(); + + // Whenever the properties of a profile is changed in the settings, we may have to + // resort all profiles according to their priority. + const updatePriority = (p) => { + p.priority = profileManager.getProfilePriority(p.settings); + this._profiles.sort((a, b) => b.priority - a.priority); + }; + + // For each profile, assign an initial priority and update the priority whenever a + // related setting changes. + this._profiles.forEach(p => { + p.priority = profileManager.getProfilePriority(p.settings); + p.settings.connect('changed::profile-app', () => updatePriority(p)); + p.settings.connect('changed::profile-animation-type', () => updatePriority(p)); + p.settings.connect('changed::profile-window-type', () => updatePriority(p)); + p.settings.connect('changed::profile-color-scheme', () => updatePriority(p)); + p.settings.connect('changed::profile-power-mode', () => updatePriority(p)); + p.settings.connect('changed::profile-power-profile', () => updatePriority(p)); + p.settings.connect('changed::profile-high-priority', () => updatePriority(p)); + }); + + // Sort all profiles initially according to their initial priority. + this._profiles.sort((a, b) => b.priority - a.priority); + } + + // This method selects an effect profile matching the current circumstances. Then a + // random effect from its enabled effects will be selected. It returns null if no + // profile is currently applicable. + _chooseEffect(actor, forOpening) { + + // For now, we only add effects to normal windows and dialog windows. + const isNormalWindow = actor.meta_window.window_type == Meta.WindowType.NORMAL; + const isDialogWindow = + actor.meta_window.window_type == Meta.WindowType.MODAL_DIALOG || + actor.meta_window.window_type == Meta.WindowType.DIALOG; + + if (!isNormalWindow && !isDialogWindow) { + return null; + } + + // ----------------------------------------------- choose a profile and then an effect + + // Usually, we use the effect profile with the highest priority which matches the + // current circumstances. From this profile, we choose a random effect. However, if an + // effect is to be previewed, we choose the currently edited profile regardless of the + // circumstances. + let profile = null; + let effect = null; + + // Hence, we first check if an effect is to be previewed. + const previewNick = this._settings.get_string('preview-effect'); + + if (previewNick != '') { + const activeProfile = this._settings.get_string('active-profile'); + effect = + this._ALL_EFFECTS.find(effect => effect.constructor.getNick() == previewNick); + profile = this._profiles.find(p => p.path == activeProfile); + + // Only preview the effect until the preview window is closed. + if (!profile || !forOpening) { + this._settings.set_string('preview-effect', ''); + } + } + // If no effect is previewed, we use the effect profile with the highest priority + // which matches the current circumstances. From this profile, we choose a random + // effect. + else { + + // These numbers match the indices in the Gtk.StringLists defined in the UI files + // (e.g. resources/ui/adw/prefs.ui). + const animationType = forOpening ? 1 : 2; + const windowType = isNormalWindow ? 1 : 2; + const powerMode = this._upowerProxy.OnBattery ? 1 : 2; + + // Get the first profile whose constraints match the circumstances. The list is + // sorted by priority, so we are good to take the first match. + profile = this._profiles.find(p => { + const profileApp = p.settings.get_string('profile-app'); + const profileAnimationType = p.settings.get_int('profile-animation-type'); + const profileWindowType = p.settings.get_int('profile-window-type'); + const profilePowerMode = p.settings.get_int('profile-power-mode'); + const profileColorScheme = p.settings.get_int('profile-color-scheme'); + const profilePowerProfile = p.settings.get_int('profile-power-profile'); + + // First we check whether the animation type, window type, and power mode are + // matching. + let matches = + (profileAnimationType == 0 || profileAnimationType == animationType) && + (profileWindowType == 0 || profileWindowType == windowType) && + (profilePowerMode == 0 || profilePowerMode == powerMode); + + // If that was the case, we also check the application name. + if (matches && profileApp != '') { + const wmClass = actor.meta_window.get_wm_class(); + + if (wmClass) { + const app = wmClass.toLowerCase(); + + // Split app names at |, remove any whitespace, and transform to lower case. + const profileApps = + profileApp.split('|').map(item => item.trim().toLowerCase()); + matches = profileApps.includes(app); + } else { + matches = false; + } + } + + // If the profile is still matching, we also check the color scheme. + if (matches && profileColorScheme != 0) { + const colorScheme = this._shellSettings.get_string('color-scheme'); + matches &= (profileColorScheme == 1 && colorScheme == 'default') || + (profileColorScheme == 2 && colorScheme == 'prefer-dark'); + } + + // Finally, we may also have to check the power profile. + if (matches && profilePowerProfile != 0 && this._powerProfilesProxy) { + const powerProfile = this._powerProfilesProxy.ActiveProfile; + + // To understand the numbers, please refer to the indices in the Gtk.StringList + // of the profile-power-profile Adw.ComboRow in resources/ui/adw/prefs.ui. + if (powerProfile == 'power-saver') { + matches &= profilePowerProfile == 1 || profilePowerProfile == 4; + } else if (powerProfile == 'balanced') { + matches &= profilePowerProfile == 2 || profilePowerProfile == 4 || + profilePowerProfile == 5; + } else { + matches &= profilePowerProfile == 3 || profilePowerProfile == 5; + } + } + + return matches; + }); + + // If we found a matching profile, choose a random effect from it. + if (profile) { + + // Create a list of all enabled effects of this profile. + const enabled = this._ALL_EFFECTS.filter(effect => { + return profile.settings.get_boolean( + `${effect.constructor.getNick()}-enable-effect`); + }); + + // And then choose a random effect. + if (enabled.length > 0) { + effect = enabled[Math.floor(Math.random() * enabled.length)]; + } + } + } + + // If nothing was enabled, we have to do nothing :) + if (!effect || !profile) { + return null; + } + + return {effect: effect, profile: profile}; + } + + // 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 + // duration and show a fixed frame from the middle of the animation. + const testMode = this._settings.get_boolean('test-mode'); + + // The following is used to tweak the ongoing transitions of a window actor. Usually + // windows are faded in / out scaled up / down slightly by GNOME Shell. Here, we tweak + // the transitions so that nothing changes. The window stays opaque and is scaled to + // actorScale. + const actorScale = + effect.constructor.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) { + 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) { + actor._bmwOverviewClone.overlayEnabled = true; + actor._bmwOverviewCloneContainer.scale_x = 1.0; + actor._bmwOverviewCloneContainer.scale_y = 1.0; + } + + // Restore the original scale of the window actor. + actor.scale_x = 1.0; + actor.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 8 + // seconds. + const duration = testMode ? + 8000 : + profile.settings.get_int(effect.constructor.getNick() + '-animation-time'); + + // Finally start the animation! + shader.beginAnimation(profile.settings, forOpening, testMode, duration, actor); + } + + // This is required to enable window-close animations in the overview. See the comment + // for Workspace.prototype._windowRemoved above for an explanation. + _shouldDestroy(workspace, metaWindow) { + const index = workspace._lookupIndex(metaWindow); + if (index == -1) { + return true; + } + + const actor = workspace._windows[index]._windowActor; + const shader = actor.get_effect('burn-my-windows-effect'); + + return shader == null; + } +} |