# 🔥 Creating a new Effect for Burn-My-Windows The extension is very modular and with a bit of creativity and GLSL knowledge, you can easily create your own effects. If you are happy with your results, please open a pull request so that we can include your effect in the next version of Burn-My-Windows! ### Before you Start First you should [fork the repository](https://github.com/Schneegans/Burn-My-Windows/fork) and clone it to your PC. Afterwards, use a terminal in the directory of the cloned repository to install the extension. ```bash make install ``` _:information_source: Whenever you changed something in the source code, you will have to call this command and restart GNOME Shell with Alt + F2, r + Enter. Or logout / login if you are on Wayland to see the changes._ ### Debugging Debugging GNOME Shell extensions is a bit difficult. Most of the time, you will print messages to a log in order to understand what is going on. To make this a bit more convenient, Burn-My-Windows includes the method `utils.debug("Hello World")`. With this, each line will be prefixed with the extension's name, which will make spotting log messages more easy. You can use the following command to watch the log output; it will highlight all messages from Burn-My-Windows: ```bash journalctl -f -o cat | grep -E 'burn-my-windows|' ``` ## Adding a new Effect For the sake of this tutorial, we will create a **"Simple Fade"** effect. Of course, you can replace any occurrence of this name and its nick `simple-fade` with your custom effect name. Three simple steps are required to create a new effect. You will have to ... 1. add a preferences key for enabling the effect, 2. create one additional effects class, and finally 3. register the new effect in two places. ### 1. Expanding the Schema For enabling the new effect, the boolean settings key `simple-fade-close-effect` is required. In this example, we also add an integer valued settings key for storing the animation time of the new effect and a floating point value for storing another property of the effect - we will use them later in the tutorial. Just copy the XML code below to the file [`schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml`](schemas/org.gnome.shell.extensions.burn-my-windows.gschema.xml). Just remember to replace `simple-fade` with your custom name! ```xml false Simple Fade Close Effect Use the Simple Fade effect for window closing. 1500 Simple Fade Animation Time The time the Simple Fade effect takes. 0.1 Simple Fade Width Width of the fading effect. ``` ### 2. Creating the Effect Class You will have to create a new file called `src/SimpleFadeEffect.js` and paste the following code to it. Please study this code carefully, all of it is explained with inline comments.
Expand this to show the code. ```javascript ////////////////////////////////////////////////////////////////////////////////////////// // ) ( // // ( /( ( ( ) ( ( ( ( )\ ) ( ( // // )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( // // ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ // // | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) // // | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< // // |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ // // |__/ // // Copyright (c) 2021 Simon Schneegans // // Released under the GPLv3 or later. See LICENSE file for details. // ////////////////////////////////////////////////////////////////////////////////////////// 'use strict'; const GObject = imports.gi.GObject; const ExtensionUtils = imports.misc.extensionUtils; const Me = imports.misc.extensionUtils.getCurrentExtension(); const utils = Me.imports.src.utils; ////////////////////////////////////////////////////////////////////////////////////////// // This effect ... // // <- Please add a description of your effect here -> // ////////////////////////////////////////////////////////////////////////////////////////// // The shader class for this effect is registered further down in this file. let Shader = null; // The effect class is completely static. It can be used to get some metadata (like the // effect's name or supported GNOME Shell versions), to initialize the respective page of // the settings dialog, as well as to create the actual shader for the effect. var SimpleFadeEffect = class SimpleFadeEffect { // ---------------------------------------------------------------------------- metadata // The effect is available on all GNOME Shell versions supported by this extension. static getMinShellVersion() { return [3, 36]; } // This will be called in various places where a unique identifier for this effect is // required. It should match the prefix of the settings keys which store whether the // effect is enabled currently (e.g. the '*-close-effect'). static getNick() { return 'simple-fade'; } // This will be shown in the sidebar of the preferences dialog as well as in the // drop-down menus where the user can choose the effect. static getLabel() { return 'Simple Fade Effect'; } // -------------------------------------------------------------------- API for prefs.js // This is called by the preferences dialog. It loads the settings page for this effect, // binds all properties to the settings and appends the page to the main stack of the // preferences dialog. static initPreferences(dialog) { // Empty for now... Code is added here later in the tutorial! } // ---------------------------------------------------------------- API for extension.js // This is called from extension.js whenever a window is closed with this effect. static createShader(settings) { return new Shader(settings); } // This is also called from extension.js. It is used to tweak the ongoing transitions of // the actor - usually windows are faded to transparency and scaled down slightly by // GNOME Shell. Here, we modify this behavior as well as the transition duration. static tweakTransitions(actor, settings) { const animationTime = settings.get_int('simple-fade-animation-time'); const tweakTransition = (property, value) => { const transition = actor.get_transition(property); if (transition) { transition.set_to(value); transition.set_duration(animationTime); } }; // We re-target these transitions so that the window is neither scaled nor faded. tweakTransition('opacity', 255); tweakTransition('scale-x', 1); tweakTransition('scale-y', 1); } } ////////////////////////////////////////////////////////////////////////////////////////// // The shader class for this effect will only be registered in GNOME Shell's process // // (not in the preferences process). It's done this way as Clutter may not be installed // // on the system and therefore the preferences would crash. // ////////////////////////////////////////////////////////////////////////////////////////// if (utils.isInShellProcess()) { const Clutter = imports.gi.Clutter; const shaderSnippets = Me.imports.src.shaderSnippets; Shader = GObject.registerClass({}, class Shader extends Clutter.ShaderEffect { _init(settings) { super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER}); this.set_shader_source(` // The code below injects some standard uniforms which will be updated during the // animation. This includes: // uTexture: Contains the texture of the window. // uProgress: A value which transitions from 0 to 1 during the entire animation. // uTime: A steadily increasing value in seconds. // uSizeX: The horizontal size of uTexture in pixels. // uSizeY: The vertical size of uTexture in pixels. ${shaderSnippets.standardUniforms()} // The width of the fading effect is directly loaded from the settings. const float FADE_WIDTH = ${settings.get_double('simple-fade-width')}; void main() { // Get the color from the window texture. vec4 windowColor = texture2D(uTexture, cogl_tex_coord_in[0].st); // Radial distance from window edge to the window's center. float dist = length(cogl_tex_coord_in[0].st - 0.5) * 2.0 / sqrt(2.0); // This gradually dissolves from [1..0] from the outside to the center. float mask = (1.0 - uProgress * (1.0 + FADE_WIDTH) - dist + FADE_WIDTH) / FADE_WIDTH; // Make the mask smoother. mask = smoothstep(0, 1, mask); // Set the final output color. This uses premultiplied alpha. cogl_color_out = windowColor * mask; } `); }; }); } ```
### 3. Registering the new Effect You will have to register the new effect in two files. Both, [`extension.js`](extension.js) and [`prefs.js`](prefs.js) define an array in the beginning where you have to add an import to your new effect. Like this: ```javascript const ALL_EFFECTS = [ ... Me.imports.src.SimpleFadeEffect.SimpleFadeEffect, ... ]; ``` ### Testing your Effect Now you can call `make install` and restart GNOME Shell with Alt + F2, r + Enter. Or logout / login if you are on Wayland. You can open the extension's preferences and choose the new effect! However, there are no configuration options yet. It is also not possible to adjust the animation time setting. ```bash gnome-extensions prefs burn-my-windows@schneegans.github.com ``` ## Adding a Preferences Page For this, we will create a `simpleFadePage.ui` file and load this in the `initPreferences()` method of your new effect class. We will be able to adjust two properties: The animation duration and the width of the fading gradient. Just save the code below to a file called `resources/ui/common/simpleFadePage.ui` and replace any occurrence of `simple-fade` with your effect's nick name!
Expand this to show the code. ```xml 5000 100 10 100 1 0 0.01 0.1 vertical 60 60 60 60 none 1 0 Animation Time [ms] 0 start center 1 end center 1 0 left 300 simple-fade-animation-time edit-clear-symbolic 1 Reset to Default Value 0 Fade Width 0 start center 1 end center 1 2 left 300 simple-fade-width edit-clear-symbolic 1 Reset to Default Value ```
### Loading the Preferences Page In order to load the above `*.ui` file, add the following code to your effect's `initPreferences()` method. ```javascript static initPreferences(dialog) { // Add the settings page to the builder. dialog.getBuilder().add_from_resource(`/ui/common/simpleFadePage.ui`); // These connect the settings to the UI elements. Have a look at prefs.js // on how to bind other types of UI elements. dialog.bindAdjustment('simple-fade-animation-time'); dialog.bindAdjustment('simple-fade-width'); // Finally, append the settings page to the main stack. const stack = dialog.getBuilder().get_object('main-stack'); stack.add_titled( dialog.getBuilder().get_object(SimpleFadeEffect.getNick() + '-prefs'), SimpleFadeEffect.getNick(), SimpleFadeEffect.getLabel()); } ``` Once this is in place, you can kill the extension-preferences process and re-open the settings. It is not required to reload GNOME Shell for developing the preferences dialog! Here's a handy one-liner to do this: ```bash make install && pkill -f '.Extensions' && sleep 2 ; gnome-extensions prefs burn-my-windows@schneegans.github.com ``` ### Supporting GTK3 and GTK4 The above UI file has been designed to support both, GTK3 and GTK4. Usually, that is not possible as properties have been changed between the versions. Most of the time, if your effect should support GNOME Shell 3.3x _and_ GNOME Shell 40+, you will have to provide two `*.ui` files. You can then load the respective files in your `initPreferences()` with such a line: ```javascript dialog.getBuilder().add_from_resource(`/ui/${utils.getGTKString()}/simpleFadePage.ui`); ``` ## Summing Up That's it! If you have any questions, feel free to [ask them on the discussions board](https://github.com/Schneegans/Burn-My-Windows/discussions). If you are happy with your effect, submit a pull request, and it may get included in the next version!