From a79323bfd244d3cda6da6cc3bf3a47df2be3366f Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 00:36:01 +0530 Subject: [PATCH 1/7] feat: add per-application opacity controls --- modules/common/Config.qml | 4 + modules/ii/settings/pages/HyprlandConfig.qml | 316 +++++++++++++++++++ scripts/appOpacitySync.py | 254 +++++++++++++++ services/HyprlandConfig.qml | 38 +++ 4 files changed, 612 insertions(+) create mode 100755 scripts/appOpacitySync.py diff --git a/modules/common/Config.qml b/modules/common/Config.qml index 97f2c02f8..9788c78b0 100644 --- a/modules/common/Config.qml +++ b/modules/common/Config.qml @@ -171,6 +171,10 @@ Singleton { property int rounding: 22 property real activeOpacity: 1.0 property real inactiveOpacity: 0.9 + + // Per-application opacity settings. + // Each entry: { id, name, match, active, enabled } + property list applicationOpacityRules: [] property JsonObject blur: JsonObject { property bool enabled: true property int size: 1 diff --git a/modules/ii/settings/pages/HyprlandConfig.qml b/modules/ii/settings/pages/HyprlandConfig.qml index 79b686558..a8c2f683e 100644 --- a/modules/ii/settings/pages/HyprlandConfig.qml +++ b/modules/ii/settings/pages/HyprlandConfig.qml @@ -61,6 +61,89 @@ ContentPage { "input:touchpad:scroll_factor": h.input.touchpad.scrollFactor }) } + // ------------------------------------------------------------------------- + // Per-application active opacity + // ------------------------------------------------------------------------- + + property int applicationOpacityRevision: 0 + property string applicationOpacitySearchText: "" + + function applicationOpacityRule(appId) { + const revision = applicationOpacityRevision + const rules = Config.options.hyprland.decoration.applicationOpacityRules || [] + + for (const rule of rules) { + if (rule && rule.id === appId) + return rule + } + + return null + } + + function saveApplicationOpacity(appEntry, enabled, activePercent) { + const rules = [...(Config.options.hyprland.decoration.applicationOpacityRules || [])] + const id = String(appEntry.id || "") + if (!id) + return + + const index = rules.findIndex(rule => rule && rule.id === id) + const existing = index >= 0 ? rules[index] : null + const match = String((existing && existing.match) || appEntry.startupClass || appEntry.id || "").trim() + if (!match) + return + + const rule = { + id: id, + name: String(appEntry.name || id), + match: match, + active: Math.max(10, Math.min(100, Number(activePercent))) / 100.0, + enabled: Boolean(enabled) + } + + if (index >= 0) + rules[index] = rule + else + rules.push(rule) + + Config.options.hyprland.decoration.applicationOpacityRules = rules + applicationOpacityRevision++ + HyprlandConfig.syncApplicationOpacity() + } + + ScriptModel { + id: applicationOpacityApplications + + values: { + const revision = page.applicationOpacityRevision + const query = page.applicationOpacitySearchText.trim().toLowerCase() + + return [...DesktopEntries.applications.values] + .filter(entry => { + if (!entry || entry.noDisplay) + return false + if (!query) + return true + + const name = String(entry.name || "").toLowerCase() + const id = String(entry.id || "").toLowerCase() + const cls = String(entry.startupClass || "").toLowerCase() + + return name.includes(query) || id.includes(query) || cls.includes(query) + }) + .sort((a, b) => + String(a.name || a.id).localeCompare(String(b.name || b.id)) + ) + } + } + + Connections { + target: DesktopEntries + + function onApplicationsChanged() { + page.applicationOpacityRevision++ + } + } + MonitorConfigOption { id: monitorConfig } ColumnLayout { @@ -452,6 +535,239 @@ ContentPage { } } + // Application Opacity + ContentSection { + icon: "opacity" + shape: MaterialShape.Shape.ClamShell + title: Translation.tr("Application Opacity") + Layout.fillWidth: true + + ContentSubsection { + title: Translation.tr("Installed Applications") + + ColumnLayout { + Layout.fillWidth: true + spacing: 7 + + // ------------------------------------------------ + // Compact search bar + // ------------------------------------------------ + Rectangle { + Layout.fillWidth: true + implicitHeight: 38 + + radius: Appearance.rounding.small + + color: ColorUtils.transparentize( + Appearance.colors.colPrimaryContainer, + 0.74 + ) + + TextInput { + id: applicationOpacitySearch + + anchors.fill: parent + anchors.leftMargin: 11 + anchors.rightMargin: 11 + + color: Appearance.colors.colOnSurface + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + + onTextChanged: + page.applicationOpacitySearchText = text + + Text { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + + text: + Translation.tr("Search applications...") + + color: + Appearance.colors.colOnSurface + + opacity: 0.48 + + visible: + applicationOpacitySearch.text.length === 0 + } + } + } + + // ------------------------------------------------ + // Installed applications + // ------------------------------------------------ + Repeater { + model: applicationOpacityApplications + + delegate: Rectangle { + Layout.fillWidth: true + implicitHeight: 58 + + radius: Appearance.rounding.small + + color: + Appearance.colors.colLayer1 + + border.width: 1 + border.color: + Appearance.colors.colLayer0Border + + property int revision: + page.applicationOpacityRevision + + property var savedRule: { + const r = revision + return page.applicationOpacityRule( + modelData.id + ) + } + + property bool overrideEnabled: + savedRule !== null && + savedRule.enabled === true + + property int activeOpacityValue: + savedRule !== null + ? Math.round( + Number(savedRule.active) * 100 + ) + : Math.round( + Config.options.hyprland.decoration + .activeOpacity * 100 + ) + + RowLayout { + anchors.fill: parent + + anchors.leftMargin: 10 + anchors.rightMargin: 8 + + spacing: 6 + + // ------------------------------------------------ + // App name + class + // ------------------------------------------------ + ColumnLayout { + Layout.fillWidth: true + Layout.minimumWidth: 0 + + spacing: 0 + + Text { + Layout.fillWidth: true + + text: + modelData.name || + modelData.id + + color: + Appearance.colors.colOnSurface + + font.pixelSize: 13 + + elide: + Text.ElideRight + } + + Text { + Layout.fillWidth: true + + text: + modelData.startupClass || + modelData.id + + color: + Appearance.colors.colOnSurface + + opacity: 0.48 + + font.pixelSize: 10 + + elide: + Text.ElideRight + } + } + + // ------------------------------------------------ + // Per-app Override + // ------------------------------------------------ + ConfigSwitch { + Layout.preferredWidth: 78 + + text: + Translation.tr("Override") + + buttonIcon: "" + + checked: + overrideEnabled + + onCheckedChanged: { + if (checked === overrideEnabled) + return + + const active = + savedRule !== null + ? Math.round( + Number(savedRule.active) * + 100 + ) + : Math.round( + Config.options.hyprland + .decoration + .activeOpacity * 100 + ) + + page.saveApplicationOpacity( + modelData, + checked, + active + ) + } + } + + // ------------------------------------------------ + // Per-app active opacity + // ------------------------------------------------ + ConfigSpinBox { + Layout.preferredWidth: 92 + + icon: "" + text: "" + + enabled: overrideEnabled + + value: + activeOpacityValue + + from: 10 + to: 100 + stepSize: 5 + + onValueChanged: { + if (!overrideEnabled) + return + + if (value === activeOpacityValue) + return + + page.saveApplicationOpacity( + modelData, + true, + value + ) + } + } + } + } + } + } + } + } + // Autostart Apps ContentSection { icon: "app_registration" diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py new file mode 100755 index 000000000..96b4faf76 --- /dev/null +++ b/scripts/appOpacitySync.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 + +import argparse +import json +import re +import subprocess +from pathlib import Path + + +REQUIRE_LINE = 'require("hyprland/shellOverrides/appOpacity")' + + +def lua_quote(value: str) -> str: + return '"' + ( + str(value) + .replace("\\", "\\\\") + .replace('"', '\\"') + .replace("\r", "\\r") + .replace("\n", "\\n") + ) + '"' + + +def get_hyprland_clients(): + try: + result = subprocess.run( + ["hyprctl", "clients", "-j"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=True, + ) + clients = json.loads(result.stdout) + if isinstance(clients, list): + return clients + except Exception as exc: + print(f"Warning: could not read Hyprland clients: {exc}") + + return [] + + +def normalize(value: str) -> str: + return str(value or "").strip().lower() + + +def resolve_real_class(match: str, rule_id: str, clients): + target = normalize(match) + rule_id_normalized = normalize(rule_id) + + classes = [] + + for client in clients: + cls = str(client.get("initialClass") or "").strip() + if cls and cls not in classes: + classes.append(cls) + + for cls in classes: + if normalize(cls) == target: + return cls + + for cls in classes: + if normalize(cls) == rule_id_normalized: + return cls + + for cls in classes: + n = normalize(cls) + + if n.endswith("." + target): + return cls + + if target.endswith("." + n): + return cls + + def compact(value): + return re.sub(r"[^a-z0-9]", "", normalize(value)) + + compact_target = compact(target) + + if compact_target: + for cls in classes: + if compact(cls) == compact_target: + return cls + + return match + + +def write_text(path: Path, content: str): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content) + + +def ensure_require(main_path: Path): + try: + content = main_path.read_text() + except FileNotFoundError: + content = "" + + lines = content.splitlines() + + if REQUIRE_LINE in lines: + return + + if content and not content.endswith("\n"): + content += "\n" + + content += REQUIRE_LINE + "\n" + + write_text(main_path, content) + + +def build_application_opacity(config_path: Path, output_path: Path): + try: + data = json.loads(config_path.read_text()) + except Exception as exc: + print(f"Could not read {config_path}: {exc}") + return False + + decoration = ( + data.get("hyprland", {}) + .get("decoration", {}) + ) + + rules = decoration.get("applicationOpacityRules", []) + + try: + inactive = max( + 0.10, + min( + 1.00, + float(decoration.get("inactiveOpacity", 0.75)), + ), + ) + except (TypeError, ValueError): + inactive = 0.75 + + if not isinstance(rules, list): + rules = [] + + clients = get_hyprland_clients() + + lines = [ + "-- AUTO-GENERATED by end4-pC Application Opacity.", + "-- Do not edit this file manually.", + "", + ] + + for rule in rules: + if not isinstance(rule, dict): + continue + + if not bool(rule.get("enabled", False)): + continue + + rule_id = str(rule.get("id", "")).strip() + saved_match = str(rule.get("match", "")).strip() + + if not rule_id and not saved_match: + continue + + real_class = resolve_real_class( + saved_match, + rule_id, + clients, + ) + + if not real_class: + continue + + try: + active = max( + 0.10, + min( + 1.00, + float(rule.get("active", 1.0)), + ), + ) + except (TypeError, ValueError): + active = 1.0 + + safe_id = re.sub( + r"[^A-Za-z0-9_-]", + "_", + rule_id or real_class, + ) + + matcher = "^" + re.escape(real_class) + "$" + + lines.append( + "hl.window_rule({" + f" name = {lua_quote('end4-app-opacity-' + safe_id)}," + f" match = {{ initial_class = {lua_quote(matcher)} }}," + f" opacity = " + f"{lua_quote(f'{active:.2f} override {inactive:.2f} override')}" + " })" + ) + + write_text( + output_path, + "\n".join(lines) + "\n", + ) + + return True + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--config", + required=True, + type=Path, + ) + + parser.add_argument( + "--output", + required=True, + type=Path, + ) + + parser.add_argument( + "--main", + required=True, + type=Path, + ) + + args = parser.parse_args() + + if not build_application_opacity( + args.config, + args.output, + ): + return 1 + + ensure_require(args.main) + + result = subprocess.run( + ["hyprctl", "reload"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + + if result.returncode != 0: + print( + result.stderr.strip() + or result.stdout.strip() + ) + return result.returncode + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/services/HyprlandConfig.qml b/services/HyprlandConfig.qml index 7f6c96181..3b5046c89 100644 --- a/services/HyprlandConfig.qml +++ b/services/HyprlandConfig.qml @@ -13,6 +13,8 @@ Singleton { readonly property string configuratorScriptPath: Quickshell.shellPath("scripts/hyprland/hyprconfigurator.py") readonly property string shellOverridesPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/main.lua`) readonly property string animOverridesPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/animations.lua`) + readonly property string applicationOpacityScriptPath: Quickshell.shellPath("scripts/appOpacitySync.py") + readonly property string applicationOpacityOutputPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/appOpacity.lua`) function set(key: string, value: var) { Quickshell.execDetached([ @@ -54,6 +56,42 @@ Singleton { ]) } + function syncApplicationOpacity() { + if (!Config.ready) + return + + applicationOpacitySyncTimer.restart() + } + + function runApplicationOpacitySync() { + Quickshell.execDetached([ + "python3", + root.applicationOpacityScriptPath, + "--config", Config.filePath, + "--output", root.applicationOpacityOutputPath, + "--main", root.shellOverridesPath + ]) + } + + Timer { + id: applicationOpacitySyncTimer + interval: 300 + repeat: false + + onTriggered: root.runApplicationOpacitySync() + } + + Connections { + target: Config + + function onReadyChanged() { + if (Config.ready) + root.syncApplicationOpacity() + } + } + + Component.onCompleted: root.syncApplicationOpacity() + Connections { target: Hyprland function onRawEvent(event) { From 6f3fa3fead4fbb9a002152c0cfa61a9726d1a10a Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 21:10:00 +0530 Subject: [PATCH 2/7] fix: harden application opacity sync --- modules/ii/settings/pages/HyprlandConfig.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ii/settings/pages/HyprlandConfig.qml b/modules/ii/settings/pages/HyprlandConfig.qml index a8c2f683e..347609a23 100644 --- a/modules/ii/settings/pages/HyprlandConfig.qml +++ b/modules/ii/settings/pages/HyprlandConfig.qml @@ -92,11 +92,14 @@ ContentPage { if (!match) return + const numericActive = Number(activePercent) + const safeActive = Number.isFinite(numericActive) ? numericActive : 100 + const rule = { id: id, name: String(appEntry.name || id), match: match, - active: Math.max(10, Math.min(100, Number(activePercent))) / 100.0, + active: Math.max(10, Math.min(100, safeActive)) / 100.0, enabled: Boolean(enabled) } From 679996bb9595c3d4858a2b2f1409c1b51d34cd42 Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 21:14:07 +0530 Subject: [PATCH 3/7] fix: harden application opacity sync --- scripts/appOpacitySync.py | 62 +++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py index 96b4faf76..4e02c0fbc 100755 --- a/scripts/appOpacitySync.py +++ b/scripts/appOpacitySync.py @@ -2,6 +2,7 @@ import argparse import json +import math import re import subprocess from pathlib import Path @@ -42,10 +43,21 @@ def normalize(value: str) -> str: return str(value or "").strip().lower() +def clamp_opacity(value, default): + try: + value = float(value) + except (TypeError, ValueError): + return default + + if not math.isfinite(value): + return default + + return max(0.10, min(1.00, value)) + + def resolve_real_class(match: str, rule_id: str, clients): target = normalize(match) rule_id_normalized = normalize(rule_id) - classes = [] for client in clients: @@ -96,7 +108,7 @@ def ensure_require(main_path: Path): lines = content.splitlines() - if REQUIRE_LINE in lines: + if any(line.strip() == REQUIRE_LINE for line in lines): return if content and not content.endswith("\n"): @@ -121,16 +133,10 @@ def build_application_opacity(config_path: Path, output_path: Path): rules = decoration.get("applicationOpacityRules", []) - try: - inactive = max( - 0.10, - min( - 1.00, - float(decoration.get("inactiveOpacity", 0.75)), - ), - ) - except (TypeError, ValueError): - inactive = 0.75 + inactive = clamp_opacity( + decoration.get("inactiveOpacity", 0.75), + 0.75, + ) if not isinstance(rules, list): rules = [] @@ -165,16 +171,10 @@ def build_application_opacity(config_path: Path, output_path: Path): if not real_class: continue - try: - active = max( - 0.10, - min( - 1.00, - float(rule.get("active", 1.0)), - ), - ) - except (TypeError, ValueError): - active = 1.0 + active = clamp_opacity( + rule.get("active", 1.0), + 1.0, + ) safe_id = re.sub( r"[^A-Za-z0-9_-]", @@ -232,13 +232,17 @@ def main(): ensure_require(args.main) - result = subprocess.run( - ["hyprctl", "reload"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=False, - ) + try: + result = subprocess.run( + ["hyprctl", "reload"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + except FileNotFoundError: + print("Could not reload Hyprland: hyprctl was not found in PATH") + return 1 if result.returncode != 0: print( From 84a962d1376dcb9b770b7805a0d148ffcd55c87f Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 21:40:53 +0530 Subject: [PATCH 4/7] fix: sync application opacity with inactive opacity --- modules/ii/settings/pages/HyprlandConfig.qml | 1 + scripts/appOpacitySync.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ii/settings/pages/HyprlandConfig.qml b/modules/ii/settings/pages/HyprlandConfig.qml index 347609a23..ea46609d7 100644 --- a/modules/ii/settings/pages/HyprlandConfig.qml +++ b/modules/ii/settings/pages/HyprlandConfig.qml @@ -533,6 +533,7 @@ ContentPage { if (newVal === Config.options.hyprland.decoration.inactiveOpacity) return Config.options.hyprland.decoration.inactiveOpacity = newVal HyprlandConfig.set("decoration:inactive_opacity", newVal) + HyprlandConfig.syncApplicationOpacity() } } } diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py index 4e02c0fbc..478a7e389 100755 --- a/scripts/appOpacitySync.py +++ b/scripts/appOpacitySync.py @@ -134,8 +134,8 @@ def build_application_opacity(config_path: Path, output_path: Path): rules = decoration.get("applicationOpacityRules", []) inactive = clamp_opacity( - decoration.get("inactiveOpacity", 0.75), - 0.75, + decoration.get("inactiveOpacity", 0.9), + 0.9, ) if not isinstance(rules, list): From 966f0d5e5388d59aa2c3f53b42981468a132beb6 Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 21:54:14 +0530 Subject: [PATCH 5/7] fix: prevent invalid opacity values and atomicize writes --- modules/ii/settings/pages/HyprlandConfig.qml | 21 ++++++++------ scripts/appOpacitySync.py | 30 +++++++++++++++++++- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/modules/ii/settings/pages/HyprlandConfig.qml b/modules/ii/settings/pages/HyprlandConfig.qml index ea46609d7..0a649ac97 100644 --- a/modules/ii/settings/pages/HyprlandConfig.qml +++ b/modules/ii/settings/pages/HyprlandConfig.qml @@ -633,15 +633,18 @@ ContentPage { savedRule !== null && savedRule.enabled === true - property int activeOpacityValue: - savedRule !== null - ? Math.round( - Number(savedRule.active) * 100 - ) - : Math.round( - Config.options.hyprland.decoration - .activeOpacity * 100 - ) + property int activeOpacityValue: { + const rawValue = savedRule !== null + ? Number(savedRule.active) * 100 + : Number( + Config.options.hyprland.decoration.activeOpacity + ) * 100 + + if (!Number.isFinite(rawValue)) + return 100 + + return Math.max(10, Math.min(100, Math.round(rawValue))) + } RowLayout { anchors.fill: parent diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py index 478a7e389..d831a7cfb 100755 --- a/scripts/appOpacitySync.py +++ b/scripts/appOpacitySync.py @@ -5,6 +5,9 @@ import math import re import subprocess +import os +import stat +import tempfile from pathlib import Path @@ -97,7 +100,32 @@ def compact(value): def write_text(path: Path, content: str): path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(content) + + existing_mode = ( + stat.S_IMODE(path.stat().st_mode) + if path.exists() + else 0o644 + ) + + fd, temp_name = tempfile.mkstemp( + dir=path.parent, + prefix=f".{path.name}.", + text=True, + ) + + try: + with os.fdopen(fd, "w") as temp_file: + temp_file.write(content) + temp_file.flush() + os.fsync(temp_file.fileno()) + + os.chmod(temp_name, existing_mode) + os.replace(temp_name, path) + finally: + try: + os.unlink(temp_name) + except FileNotFoundError: + pass def ensure_require(main_path: Path): From 0d90cd111e6a3c37e4998a46710de00ed39a3c7c Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 22:11:13 +0530 Subject: [PATCH 6/7] fix: avoid unnecessary Hyprland reloads --- scripts/appOpacitySync.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py index d831a7cfb..9141c69cf 100755 --- a/scripts/appOpacitySync.py +++ b/scripts/appOpacitySync.py @@ -101,6 +101,9 @@ def compact(value): def write_text(path: Path, content: str): path.parent.mkdir(parents=True, exist_ok=True) + if path.exists() and path.read_text() == content: + return False + existing_mode = ( stat.S_IMODE(path.stat().st_mode) if path.exists() @@ -121,6 +124,7 @@ def write_text(path: Path, content: str): os.chmod(temp_name, existing_mode) os.replace(temp_name, path) + return True finally: try: os.unlink(temp_name) @@ -144,7 +148,7 @@ def ensure_require(main_path: Path): content += REQUIRE_LINE + "\n" - write_text(main_path, content) + return write_text(main_path, content) def build_application_opacity(config_path: Path, output_path: Path): @@ -221,7 +225,7 @@ def build_application_opacity(config_path: Path, output_path: Path): " })" ) - write_text( + return write_text( output_path, "\n".join(lines) + "\n", ) @@ -252,13 +256,15 @@ def main(): args = parser.parse_args() - if not build_application_opacity( + output_changed = build_application_opacity( args.config, args.output, - ): - return 1 + ) + + require_changed = ensure_require(args.main) - ensure_require(args.main) + if not output_changed and not require_changed: + return 0 try: result = subprocess.run( From 5065d6ebce20a1200a6e96f8920096646777ee8e Mon Sep 17 00:00:00 2001 From: amanCodex148 Date: Mon, 7 Sep 2026 22:28:20 +0530 Subject: [PATCH 7/7] fix: handle sync errors correctly --- scripts/appOpacitySync.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/appOpacitySync.py b/scripts/appOpacitySync.py index 9141c69cf..1b25846f2 100755 --- a/scripts/appOpacitySync.py +++ b/scripts/appOpacitySync.py @@ -156,7 +156,7 @@ def build_application_opacity(config_path: Path, output_path: Path): data = json.loads(config_path.read_text()) except Exception as exc: print(f"Could not read {config_path}: {exc}") - return False + return None decoration = ( data.get("hyprland", {}) @@ -230,9 +230,6 @@ def build_application_opacity(config_path: Path, output_path: Path): "\n".join(lines) + "\n", ) - return True - - def main(): parser = argparse.ArgumentParser() @@ -261,6 +258,9 @@ def main(): args.output, ) + if output_changed is None: + return 1 + require_changed = ensure_require(args.main) if not output_changed and not require_changed: