diff --git a/kwin/README.md b/kwin/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/kwin/build.sh b/kwin/build.sh
new file mode 100755
index 0000000..15c0fde
--- /dev/null
+++ b/kwin/build.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# -------------------------------------------------------------------------------------- #
+# ) ( #
+# ( /( ( ( ) ( ( ( ( )\ ) ( ( #
+# )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( #
+# ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ #
+# | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) #
+# | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< #
+# |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ #
+# |__/ #
+# Copyright (c) 2021 Simon Schneegans #
+# Released under the GPLv3 or later. See LICENSE file for details. #
+# -------------------------------------------------------------------------------------- #
+
+# Exit the script when one command fails.
+set -e
+
+# Go to the script's directory.
+cd "$( cd "$( dirname "$0" )" && pwd )" || \
+ { echo "ERROR: Could not find kwin directory."; exit 1; }
+
+BUILD_DIR="_build"
+
+mkdir -p "${BUILD_DIR}"
+
+generate() {
+ cp -r "kwin4_effect_$1" "${BUILD_DIR}"
+
+ mkdir -p "${BUILD_DIR}/kwin4_effect_$1/contents/shaders"
+ mkdir -p "${BUILD_DIR}/kwin4_effect_$1/contents/code"
+
+ sed -e "s;%NICK%;$1;g" -e "s;%NAME%;$2;g" -e "s;%DESCRIPTION%;$3;g" \
+ metadata.desktop.in > "${BUILD_DIR}/kwin4_effect_$1/metadata.desktop"
+
+ sed -e "s;%NICK%;$1;g" -e "s;%NAME%;$2;g" -e "s;%DESCRIPTION%;$3;g" \
+ main.js.in > "${BUILD_DIR}/kwin4_effect_$1/contents/code/main.js"
+
+}
+
+generate "tv" "TV" "Make windows close like turning off a TV"
+# generate "fire" "Fire" "Make windows burn"
\ No newline at end of file
diff --git a/kwin/kwin4_effect_tv/contents/config/main.xml b/kwin/kwin4_effect_tv/contents/config/main.xml
new file mode 100755
index 0000000..c0f6524
--- /dev/null
+++ b/kwin/kwin4_effect_tv/contents/config/main.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ 0
+
+
+ 128,128,128
+
+
+
diff --git a/kwin/kwin4_effect_tv/contents/ui/config.ui b/kwin/kwin4_effect_tv/contents/ui/config.ui
new file mode 100755
index 0000000..8c55fdf
--- /dev/null
+++ b/kwin/kwin4_effect_tv/contents/ui/config.ui
@@ -0,0 +1,80 @@
+
+
+ TvEffectConfigForm
+
+
+
+ 0
+ 0
+ 498
+ 451
+
+
+
+ -
+
+
+ Duration:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ &Color:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ kcfg_Color
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Default
+
+
+ milliseconds
+
+
+ 9999
+
+
+ 5
+
+
+
+
+
+
+
+ KColorCombo
+ QComboBox
+
+
+
+
+
+
diff --git a/kwin/main.js.in b/kwin/main.js.in
new file mode 100755
index 0000000..a0d96c6
--- /dev/null
+++ b/kwin/main.js.in
@@ -0,0 +1,172 @@
+/*
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+ SPDX-FileCopyrightText: 2018, 2021, 2022 Vlad Zahorodnii , Martin Flöser , Simon Schneegans
+
+ SPDX-License-Identifier: GPL-3.0-or-later
+*/
+
+"use strict";
+
+const blacklist = [
+ // The logout screen has to be animated only by the logout effect.
+ "ksmserver ksmserver",
+ "ksmserver-logout-greeter ksmserver-logout-greeter",
+
+ // KDE Plasma splash screen has to be animated only by the login effect.
+ "ksplashqml ksplashqml"
+];
+
+class Effect {
+ constructor() {
+ effect.configChanged.connect(this.loadConfig.bind(this));
+ effect.animationEnded.connect(this.cleanupForcedRoles.bind(this));
+ effects.windowAdded.connect(this.slotWindowAdded.bind(this));
+ effects.windowClosed.connect(this.slotWindowClosed.bind(this));
+ effects.windowDataChanged.connect(this.slotWindowDataChanged.bind(this));
+
+ this.shader = effect.addShader(Effect.MapTexture, "%NICK%.frag");
+
+ this.loadConfig();
+ }
+
+ loadConfig() {
+ const defaultDuration = 1000;
+ const duration = effect.readConfig("Duration", defaultDuration) || defaultDuration;
+ this.duration = animationTime(duration);
+
+ effect.setUniform(this.shader, "uEffectColor", effect.readConfig("Color", "white"));
+ }
+
+ static isScaleWindow(window) {
+ // We don't want to animate most of plasmashell's windows, yet, some
+ // of them we want to, for example, Task Manager Settings window.
+ // The problem is that all those window share single window class.
+ // So, the only way to decide whether a window should be animated is
+ // to use a heuristic: if a window has decoration, then it's most
+ // likely a dialog or a settings window so we have to animate it.
+ if (window.windowClass == "plasmashell plasmashell"
+ || window.windowClass == "plasmashell org.kde.plasmashell") {
+ return window.hasDecoration;
+ }
+
+ if (blacklist.indexOf(window.windowClass) != -1) {
+ return false;
+ }
+
+ if (window.hasDecoration) {
+ return true;
+ }
+
+ // Don't animate combobox popups, tooltips, popup menus, etc.
+ if (window.popupWindow) {
+ return false;
+ }
+
+ // Dont't animate the outline and the screenlocker as it looks bad.
+ if (window.lockScreen || window.outline) {
+ return false;
+ }
+
+ // Override-redirect windows are usually used for user interface
+ // concepts that are not expected to be animated by this effect.
+ if (!window.managed) {
+ return false;
+ }
+
+ return window.normalWindow || window.dialog;
+ }
+
+ setupForcedRoles(window) {
+ window.setData(Effect.WindowForceBackgroundContrastRole, true);
+ window.setData(Effect.WindowForceBlurRole, true);
+ }
+
+ cleanupForcedRoles(window) {
+ window.setData(Effect.WindowForceBackgroundContrastRole, null);
+ window.setData(Effect.WindowForceBlurRole, null);
+ }
+
+ slotWindowAdded(window) {
+ if (effects.hasActiveFullScreenEffect) {
+ return;
+ }
+ if (!Effect.isScaleWindow(window)) {
+ return;
+ }
+ if (!window.visible) {
+ return;
+ }
+ if (effect.isGrabbed(window, Effect.WindowAddedGrabRole)) {
+ return;
+ }
+ this.setupForcedRoles(window);
+ window.scaleInAnimation = animate({
+ window: window,
+ curve: QEasingCurve.Linear,
+ duration: this.duration,
+ animations: [
+ {
+ type: Effect.ShaderUniform,
+ shader: this.shader,
+ uniform: "uForOpening",
+ from: 1.0,
+ to: 1.0
+ }
+ ]
+ });
+ }
+
+ slotWindowClosed(window) {
+ if (effects.hasActiveFullScreenEffect) {
+ return;
+ }
+ if (!Effect.isScaleWindow(window)) {
+ return;
+ }
+ if (!window.visible || window.skipsCloseAnimation) {
+ return;
+ }
+ if (effect.isGrabbed(window, Effect.WindowClosedGrabRole)) {
+ return;
+ }
+ if (window.scaleInAnimation) {
+ cancel(window.scaleInAnimation);
+ delete window.scaleInAnimation;
+ }
+ this.setupForcedRoles(window);
+ window.scaleOutAnimation = animate({
+ window: window,
+ curve: QEasingCurve.Linear,
+ duration: this.duration,
+ animations: [
+ {
+ type: Effect.ShaderUniform,
+ shader: this.shader,
+ uniform: "uForOpening",
+ from: 0.0,
+ to: 0.0
+ }
+ ]
+ });
+ }
+
+ slotWindowDataChanged(window, role) {
+ if (role == Effect.WindowAddedGrabRole) {
+ if (window.scaleInAnimation && effect.isGrabbed(window, role)) {
+ cancel(window.scaleInAnimation);
+ delete window.scaleInAnimation;
+ this.cleanupForcedRoles(window);
+ }
+ } else if (role == Effect.WindowClosedGrabRole) {
+ if (window.scaleOutAnimation && effect.isGrabbed(window, role)) {
+ cancel(window.scaleOutAnimation);
+ delete window.scaleOutAnimation;
+ this.cleanupForcedRoles(window);
+ }
+ }
+ }
+}
+
+new Effect();
diff --git a/kwin/metadata.desktop.in b/kwin/metadata.desktop.in
new file mode 100755
index 0000000..c975bed
--- /dev/null
+++ b/kwin/metadata.desktop.in
@@ -0,0 +1,21 @@
+[Desktop Entry]
+Name=%NAME%
+Icon=preferences-system-windows-effect-%NICK%
+Comment=%DESCRIPTION%
+
+Type=Service
+X-KDE-ServiceTypes=KWin/Effect,KCModule
+X-KDE-PluginInfo-Author=Vlad Zahorodnii, Martin Flöser, Simon Schneegans
+X-KDE-PluginInfo-Email=vlad.zahorodnii@kde.org, mgraesslin@kde.org
+X-KDE-PluginInfo-Name=kwin4_effect_%NICK%
+X-KDE-PluginInfo-Version=1
+X-KDE-PluginInfo-Category=Window Open/Close Animation
+X-KDE-PluginInfo-License=GPLv3
+X-KDE-PluginInfo-EnabledByDefault=false
+X-KDE-Ordering=60
+X-Plasma-API=javascript
+X-Plasma-MainScript=code/main.js
+X-KDE-PluginKeyword=kwin4_effect_%NICK%
+X-KDE-Library=kcm_kwin4_genericscripted
+X-KWin-Config-TranslationDomain=kwin_effects
+X-KWin-Exclusive-Category=toplevel-open-close-animation