Skip to content

Instantly share code, notes, and snippets.

@Schneegans
Last active December 2, 2025 04:57
Show Gist options
  • Select an option

  • Save Schneegans/72af6738f8c1cf7f03a7941a7dac15a1 to your computer and use it in GitHub Desktop.

Select an option

Save Schneegans/72af6738f8c1cf7f03a7941a7dac15a1 to your computer and use it in GitHub Desktop.
Test-Effect
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2018, 2021, 2022 Vlad Zahorodnii <[email protected]>, Martin Flöser <[email protected]>, 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 TestEffect {
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.addFragmentShader(Effect.MapTexture, "test.frag");
this.loadConfig();
}
loadConfig() {
const defaultDuration = 500;
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 (!TestEffect.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,
shader: this.shader,
type: Effect.Shader,
animations: [
{
type: Effect.ShaderUniform,
uniform: "uForOpening",
shader: this.shader,
from: 1.0,
to: 1.0
},
{
type: Effect.Scale,
shader: this.shader,
from: {
value1: 1.0,
value2: 0.0
},
to: {
value1: 1.0,
value2: 1.0
}
}
]
});
}
slotWindowClosed(window) {
if (effects.hasActiveFullScreenEffect) {
return;
}
if (!TestEffect.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,
shader: this.shader,
type: Effect.Shader,
animations: [
{
type: Effect.ShaderUniform,
uniform: "uForOpening",
shader: this.shader,
from: 0.0,
to: 0.0
},
{
type: Effect.Scale,
shader: this.shader,
from: {
value1: 1.0,
value2: 1.0
},
to: {
value1: 1.0,
value2: 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 TestEffect();
[Desktop Entry]
Name=Test
Icon=preferences-system-windows-effect-test
Comment=Foo Bar
Type=Service
X-KDE-ServiceTypes=KWin/Effect,KCModule
X-KDE-PluginInfo-Author=Vlad Zahorodnii, Martin Flöser, Simon Schneegans
[email protected], [email protected]
X-KDE-PluginInfo-Name=kwin4_effect_test
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_test
X-KDE-Library=kcm_kwin4_genericscripted
X-KWin-Config-TranslationDomain=kwin_effects
X-KWin-Exclusive-Category=toplevel-open-close-animation
uniform float uForOpening;
uniform float textureWidth;
uniform float textureHeight;
void main() {
gl_FragColor = vec4(1, uForOpening, 0, 1);
}
#version 140
uniform float uForOpening;
uniform float textureWidth;
uniform float textureHeight;
out vec4 fragColor;
void main() {
fragColor = vec4(1, uForOpening, 0, 1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment