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); +}