From 1225c014e84fc60f6230c2b12b0c8410465b757b Mon Sep 17 00:00:00 2001
From: end-4 <97237370+end-4@users.noreply.github.com>
Date: Thu, 19 Mar 2026 12:34:32 +0100
Subject: [PATCH] add anti-flashbang using shader
---
.../quickToggles/AntiFlashbangToggle.qml | 4 +-
.../nightLight/NightLightDialog.qml | 26 ++++++++--
.../services/HyprlandAntiFlashbangShader.qml | 38 +++++++++++++++
.../anti-flashbang.glsl | 47 +++++++++++++++++++
4 files changed, 109 insertions(+), 6 deletions(-)
create mode 100644 dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml
create mode 100644 dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl
diff --git a/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml b/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml
index 088434ad7..64f8c4e72 100644
--- a/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml
+++ b/dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml
@@ -8,10 +8,10 @@ QuickToggleModel {
name: Translation.tr("Anti-flashbang")
tooltipText: Translation.tr("Anti-flashbang")
icon: "flash_off"
- toggled: Config.options.light.antiFlashbang.enable
+ toggled: HyprlandAntiFlashbangShader.enabled
mainAction: () => {
- Config.options.light.antiFlashbang.enable = !Config.options.light.antiFlashbang.enable;
+ HyprlandAntiFlashbangShader.toggle()
}
hasMenu: true
}
diff --git a/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml b/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml
index f0286fc38..e6f7509f2 100644
--- a/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml
+++ b/dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml
@@ -42,7 +42,7 @@ WindowDialog {
right: parent.right
}
iconSize: Appearance.font.pixelSize.larger
- buttonIcon: "lightbulb"
+ buttonIcon: "check"
text: Translation.tr("Enable now")
checked: Hyprsunset.active
onCheckedChanged: {
@@ -102,14 +102,32 @@ WindowDialog {
right: parent.right
}
iconSize: Appearance.font.pixelSize.larger
- buttonIcon: "flash_off"
- text: Translation.tr("Enable")
+ buttonIcon: "filter"
+ text: Translation.tr("Content adjustment")
+ checked: HyprlandAntiFlashbangShader.enabled
+ onCheckedChanged: {
+ if (checked) HyprlandAntiFlashbangShader.enable()
+ else HyprlandAntiFlashbangShader.disable()
+ }
+ StyledToolTip {
+ text: Translation.tr("Dims screen content as needed.
Pros: Immediately responsive
Cons: Expensive and can hurt color accuracy
Uses a Hyprland screen shader")
+ }
+ }
+
+ ConfigSwitch {
+ anchors {
+ left: parent.left
+ right: parent.right
+ }
+ iconSize: Appearance.font.pixelSize.larger
+ buttonIcon: "light_mode"
+ text: Translation.tr("Brightness adjustment")
checked: Config.options.light.antiFlashbang.enable
onCheckedChanged: {
Config.options.light.antiFlashbang.enable = checked;
}
StyledToolTip {
- text: Translation.tr("Example use case: eroge on one workspace, dark Discord window on another")
+ text: Translation.tr("Adapts the display (physical screen) brightness
Pros: Less expensive, retains colors
Cons: Not immediately responsive
Adjusts display brightness after each Hyprland IPC event")
}
}
}
diff --git a/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml b/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml
new file mode 100644
index 000000000..eea2123b9
--- /dev/null
+++ b/dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml
@@ -0,0 +1,38 @@
+pragma Singleton
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import Quickshell
+
+import qs.modules.common.models.hyprland
+
+Singleton {
+ id: root
+
+ readonly property string shaderPath: Quickshell.shellPath("services/hyprlandAntiFlashbangShader/anti-flashbang.glsl")
+ property bool enabled: confOpt.value == shaderPath
+
+ function enable() {
+ HyprlandConfig.setMany({
+ "decoration:screen_shader": root.shaderPath,
+ "debug:damage_tracking": 1, // Turn off dmg tracking to prevent weird flashes. 1 = monitor only
+ });
+ }
+
+ function disable() {
+ HyprlandConfig.resetMany([
+ "decoration:screen_shader",
+ "debug:damage_tracking"
+ ]);
+ }
+
+ function toggle() {
+ if (root.enabled) disable()
+ else enable()
+ }
+
+ HyprlandConfigOption {
+ id: confOpt
+ key: "decoration:screen_shader"
+ }
+}
diff --git a/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl b/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl
new file mode 100644
index 000000000..32e6ad2ed
--- /dev/null
+++ b/dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl
@@ -0,0 +1,47 @@
+#version 300 es
+precision highp float;
+
+in vec2 v_texcoord;
+uniform sampler2D tex;
+out vec4 fragColor;
+
+float overlayOpacityForBrightness(float x) {
+ // Note: range 0 to 1
+
+ // Will a fancy curve help?... I'll have to experiment more at night
+ // float y = pow(x, 2.0) * 0.75;
+ // float y = (1.0 - exp(-x))*1.15;
+ // float y = (1.0 - exp(-pow((x-0.15), 0.6)))*1.18;
+ float y = x*0.75;
+
+ return min(max(y, 0.001), 1.0);
+}
+
+void main() {
+ // 1. Get the current pixel color
+ vec4 pixColor = texture(tex, v_texcoord);
+
+ // 2. Calculate average screen brightness
+ vec3 totalRGB = vec3(0.0);
+ float samples = 0.0;
+
+ // We use a nested loop to create a 10x10 grid (100 samples)
+ // This is dense enough to catch small icons/text but light enough to run fast.
+ for(float x = 0.05; x < 1.0; x += 0.1) {
+ for(float y = 0.05; y < 1.0; y += 0.1) {
+ totalRGB += texture(tex, vec2(x, y)).rgb;
+ samples++;
+ }
+ }
+
+ vec3 avgColor = totalRGB / samples;
+ float globalBrightness = dot(avgColor, vec3(0.2126, 0.7152, 0.0722));
+
+ // 3. Get the specific opacity for this brightness level
+ float opacity = overlayOpacityForBrightness(globalBrightness);
+
+ // 4. Apply the "black overlay" effect
+ vec3 outColor = mix(pixColor.rgb, vec3(0.0), opacity);
+
+ fragColor = vec4(outColor, pixColor.a);
+}