// brightness.js — pure brightness logic: clamp, percent, role thresholds. // NO I/O. Dual-mode: node `require` (tests) + QML import. Same pattern as clip.js/audio.js. // --- clamp a level to 0..1, null/NaN-safe --- function clampLevel(value) { var n = Number(value); if (!isFinite(n)) return 0; return n < 0 ? 0 : (n > 1 ? 1 : n); } // --- integer percent 0..100 for a level --- function levelPercent(value) { return Math.round(clampLevel(value) * 100); } // --- compact/bar role color token by level band: // dim (<=15%) -> ui.minor, bright (>=85%) -> warning, else ui.accent --- function levelRole(value) { var v = clampLevel(value); if (v <= 0.15) return "ui.minor"; if (v >= 0.85) return "warning"; return "ui.accent"; } function outputArray(outputs) { if (Array.isArray(outputs)) return outputs; var obj = outputs || {}; return Object.keys(obj).map(function (name) { var row = obj[name] || {}; var copy = {}; Object.keys(row).forEach(function (key) { copy[key] = row[key]; }); copy.name = copy.name || name; return copy; }); } function displayName(output) { var name = String(output.name || "display"); var model = String(output.model || ""); return model.length > 0 && model !== "Unknown" ? name + " · " + model : name; } function displayRows(backlight, outputs, ddc) { var rows = []; var b = backlight || {}; if (b.present) { rows.push({ id: "backlight", name: "built-in", kind: "backlight", level: clampLevel(b.level), controllable: true, issue: "" }); } var ddcMap = ddc || {}; outputArray(outputs).forEach(function (output) { var key = String(output.name || ""); // Internal connectors use the backlight row, never DDC setup. if (key.length === 0 || /^(eDP|LVDS|DSI)-[0-9]+$/.test(key)) return; var state = ddcMap[key] || {}; var ok = !!state.controllable; rows.push({ id: "ddc:" + key, name: displayName(output), kind: "ddc", level: ok ? clampLevel(state.level) : 0, controllable: ok, issue: ok ? "" : "DDC?" }); }); return rows; } function globalState(rows) { var levels = (rows || []).filter(function (row) { return !!row.controllable; }).map(function (row) { return Math.round(clampLevel(row.level) * 100) / 100; }); if (levels.length === 0) return { mode: "none", level: 0, levels: [] }; var first = levels[0]; var mixed = levels.some(function (level) { return level !== first; }); if (!mixed) return { mode: "synced", level: first, levels: levels }; var sum = levels.reduce(function (a, b) { return a + b; }, 0); return { mode: "mixed", level: Math.round(sum / levels.length * 100) / 100, levels: levels }; } if (typeof module !== 'undefined' && module.exports) { module.exports = { clampLevel: clampLevel, levelPercent: levelPercent, levelRole: levelRole, displayRows: displayRows, globalState: globalState }; }