import QtQuick import Quickshell import Quickshell.Io import "brightness.js" as Brightness Item { id: root property Osd osdService property Niri niriService property string devicePath: "/sys/class/backlight/intel_backlight" property var ddcDisplays: ({}) property var lastAction: ({}) property var lastOsd: ({}) // Optimistic session level. The sysfs write is async (execDetached) and the // FileView (blockAllReads) loads once and never reloads, so a fresh read after // a write returns the stale startup value. Track the level we just set so // stateJson / the bar reflect it immediately. -1 = not yet set this session. property real liveLevel: -1 function intText(text, fallback) { const n = parseInt(String(text || "").trim(), 10); return isFinite(n) ? n : fallback; } function backlightPresent() { return String(maxFile.text() || "").trim().length > 0 && String(brightnessFile.text() || "").trim().length > 0; } function nativeMax() { return Math.max(1, intText(maxFile.text(), 1)); } function nativeBrightness() { return Math.max(1, intText(brightnessFile.text(), nativeMax())); } function levelFraction() { if (!backlightPresent()) return 0; if (liveLevel >= 0) return Brightness.clampLevel(liveLevel); return Brightness.clampLevel(nativeBrightness() / nativeMax()); } function outputRows() { return niriService ? niriService.outputList() : []; } function osd(value) { const text = String(Brightness.levelPercent(value)); if (!osdService) return { "event": "brightness", "value": text }; try { return JSON.parse(osdService.trigger("brightness", text)); } catch (error) { return { "event": "brightness", "value": text }; } } function stateObject() { const level = Math.round(levelFraction() * 100) / 100; const displays = Brightness.displayRows({ "present": backlightPresent(), "level": level }, outputRows(), ddcDisplays); const global = Brightness.globalState(displays); // Internal panels use backlight; only external displays need DDC setup. const setupNeeded = displays.some(row => row.kind === "ddc" && !row.controllable); return { "level": global.level, "displays": displays, "global": global, "setup": { "needed": setupNeeded, "available": true }, "compact": { "label": "brightness", "role": Brightness.levelRole(global.level) }, "lastAction": lastAction, "lastOsd": lastOsd }; } function stateJson() { return JSON.stringify(stateObject()); } function selfTest() { const cases = []; const check = (name, got, want) => cases.push({ "case": name, "ok": JSON.stringify(got) === JSON.stringify(want), "got": got, "want": want }); check("clampLevel(-1)", Brightness.clampLevel(-1), 0); check("clampLevel(2)", Brightness.clampLevel(2), 1); check("levelPercent(0.5)", Brightness.levelPercent(0.5), 50); check("levelRole(0.15)", Brightness.levelRole(0.15), "ui.minor"); check("levelRole(0.5)", Brightness.levelRole(0.5), "ui.accent"); check("levelRole(0.85)", Brightness.levelRole(0.85), "warning"); const fail = cases.filter(c => !c.ok).length; return JSON.stringify({ "pass": cases.length - fail, "fail": fail, "cases": cases }); } function applyNative(level) { if (!backlightPresent()) return; const value = Brightness.clampLevel(level); Quickshell.execDetached(["sh", "-c", `d='${devicePath}'; m=$(cat "$d/max_brightness"); n=$(awk -v v=${value} -v m=$m 'BEGIN { x=int(v*m); if (x<1) x=1; if (x>m) x=m; print x }'); printf '%s\n' "$n" > "$d/brightness"`]); } function setupDdc() { lastAction = { "kind": "setup-ddc" }; Quickshell.execDetached(["pkexec", "sh", "-c", "pacman -S --needed --noconfirm ddcutil && modprobe i2c-dev && udevadm trigger --subsystem-match=i2c-dev"]); return stateJson(); } function setDisplay(id, level) { const value = Brightness.clampLevel(level); if (String(id) === "backlight") { applyNative(value); liveLevel = value; } lastAction = { "kind": "set-display", "id": String(id), "level": Math.round(value * 100) / 100 }; lastOsd = osd(value); return stateJson(); } function setAll(level) { const value = Brightness.clampLevel(level); const rows = stateObject().displays || []; for (let i = 0; i < rows.length; i++) { if (rows[i].controllable) setDisplay(rows[i].id, value); } lastAction = { "kind": "set-all", "level": Math.round(value * 100) / 100 }; lastOsd = osd(value); return stateJson(); } function setLevel(level) { const value = Brightness.clampLevel(level); return setAll(value); } function increase() { return setLevel(levelFraction() + 0.05); } function decrease() { return setLevel(levelFraction() - 0.05); } FileView { id: brightnessFile path: `${root.devicePath}/brightness` blockLoading: true blockAllReads: true printErrors: false } FileView { id: maxFile path: `${root.devicePath}/max_brightness` blockLoading: true blockAllReads: true printErrors: false } }