feat!: support exponential brightness curve

I don't need most of the fluff for handling other types of displays, and getting rid of it lets me do something a lot nicer: add an exponential-gamma brightness display.
This commit is contained in:
Kiana Sheibani 2026-01-25 04:45:00 -05:00
parent bc5d256073
commit e45f412930
Signed by: toki
GPG key ID: 6CB106C25E86A9F7
9 changed files with 28 additions and 133 deletions

View file

@ -171,6 +171,8 @@ Singleton {
readonly property string sunsetFrom: "21:00" readonly property string sunsetFrom: "21:00"
readonly property string sunsetTo: "9:00" readonly property string sunsetTo: "9:00"
readonly property int sunsetTemperature: 4500 readonly property int sunsetTemperature: 4500
readonly property real brightnessExp: 4.0
} }
readonly property QtObject session: QtObject { readonly property QtObject session: QtObject {

View file

@ -13,7 +13,6 @@ Item {
id: root id: root
required property PersistentProperties uiState required property PersistentProperties uiState
required property ShellScreen screen
readonly property real nonAnimWidth: content.implicitWidth readonly property real nonAnimWidth: content.implicitWidth
readonly property real nonAnimHeight: y > 0 || hasCurrent ? content.implicitHeight : 0 readonly property real nonAnimHeight: y > 0 || hasCurrent ? content.implicitHeight : 0

View file

@ -11,7 +11,6 @@ Item {
id: root id: root
required property var uiState required property var uiState
required property Brightness.Monitor monitor
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
@ -95,13 +94,10 @@ Item {
implicitHeight: Config.osd.sliderLength implicitHeight: Config.osd.sliderLength
function onWheel(event: WheelEvent) { function onWheel(event: WheelEvent) {
const monitor = root.monitor;
if (!monitor)
return;
if (event.angleDelta.y > 0) if (event.angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + 0.1); Brightness.setBrightness(Brightness.brightness + 0.1);
else if (event.angleDelta.y < 0) else if (event.angleDelta.y < 0)
monitor.setBrightness(monitor.brightness - 0.1); Brightness.setBrightness(Brightness.brightness - 0.1);
} }
CustomFilledSlider { CustomFilledSlider {
@ -109,8 +105,8 @@ Item {
color: Config.colors.brightness color: Config.colors.brightness
icon: Icons.getBrightnessIcon(value) icon: Icons.getBrightnessIcon(value)
value: root.monitor?.brightness ?? 0 value: Brightness.brightness
onMoved: root.monitor?.setBrightness(value) onMoved: Brightness.setBrightness(value)
} }
} }
} }

View file

@ -7,10 +7,8 @@ Scope {
id: root id: root
required property PersistentProperties uiState required property PersistentProperties uiState
required property ShellScreen screen
required property bool hovered required property bool hovered
required property bool suppressed required property bool suppressed
readonly property Brightness.Monitor monitor: Brightness.getMonitorForScreen(screen)
function show(): void { function show(): void {
if (!root.suppressed) { if (!root.suppressed) {
@ -34,7 +32,7 @@ Scope {
} }
Connections { Connections {
target: root.monitor target: Brightness
function onBrightnessChanged(): void { function onBrightnessChanged(): void {
if (root.uiState.osdBrightnessReact) if (root.uiState.osdBrightnessReact)

View file

@ -8,7 +8,6 @@ Item {
id: root id: root
required property var uiState required property var uiState
required property ShellScreen screen
visible: width > 0 visible: width > 0
implicitWidth: 0 implicitWidth: 0
@ -61,6 +60,5 @@ Item {
id: content id: content
uiState: root.uiState uiState: root.uiState
monitor: Brightness.getMonitorForScreen(root.screen)
} }
} }

View file

@ -120,7 +120,6 @@ CustomMouseArea {
Osd.Interactions { Osd.Interactions {
uiState: root.uiState uiState: root.uiState
screen: root.screen
hovered: root.osdHovered hovered: root.osdHovered
suppressed: root.osdSuppressed suppressed: root.osdSuppressed
} }

View file

@ -13,7 +13,6 @@ Item {
id: root id: root
required property PersistentProperties uiState required property PersistentProperties uiState
required property ShellScreen screen
required property Item bar required property Item bar
readonly property alias popouts: popouts readonly property alias popouts: popouts
@ -31,14 +30,12 @@ Item {
id: popouts id: popouts
uiState: root.uiState uiState: root.uiState
screen: root.screen
} }
Osd.Wrapper { Osd.Wrapper {
id: osd id: osd
uiState: root.uiState uiState: root.uiState
screen: root.screen
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right

View file

@ -110,7 +110,6 @@ Variants {
visible: !uiState.uiState.hidden visible: !uiState.uiState.hidden
uiState: uiState.uiState uiState: uiState.uiState
screen: scope.modelData
bar: bar bar: bar
} }
} }

View file

@ -12,59 +12,39 @@ Singleton {
reloadableId: "brightness" reloadableId: "brightness"
property list<var> ddcMonitors: [] property real brightness: 1.0
readonly property list<Monitor> monitors: variants.instances property real maxBrightness: 0.0
property bool appleDisplayPresent: false
function getMonitorForScreen(screen: ShellScreen): var {
return monitors.find(m => m.modelData === screen);
}
function increaseBrightness(): void { function increaseBrightness(): void {
const focusedName = Hypr.focusedMonitor.name; setBrightness(brightness + Config.osd.brightnessIncrement);
const monitor = monitors.find(m => focusedName === m.modelData.name);
if (monitor)
monitor.setBrightness(monitor.brightness + Config.osd.brightnessIncrement);
} }
function decreaseBrightness(): void { function decreaseBrightness(): void {
const focusedName = Hypr.focusedMonitor.name; setBrightness(brightness - Config.osd.brightnessIncrement);
const monitor = monitors.find(m => focusedName === m.modelData.name);
if (monitor)
monitor.setBrightness(monitor.brightness - Config.osd.brightnessIncrement);
} }
onMonitorsChanged: { function setBrightness(value: real): void {
ddcMonitors = []; value = Math.max(0, Math.min(1, value));
ddcProc.running = true; if (Math.abs(brightness - value) < 0.01) return;
} brightness = value;
Variants { const exp = Config.services.brightnessExp;
id: variants const raw = Math.round((value ** exp) * maxBrightness);
Quickshell.execDetached(["brightnessctl", "s", `${raw}`]);
model: Quickshell.screens
Monitor {}
} }
Component.onCompleted: initProc.running = true
Process { Process {
running: true id: initProc
command: ["sh", "-c", "asdbctl get"] // To avoid warnings if asdbctl is not installed command: ["sh", "-c", "echo $(brightnessctl g) $(brightnessctl m)"]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: root.appleDisplayPresent = text.trim().length > 0 onStreamFinished: {
const exp = Config.services.brightnessExp;
const [cur, max] = text.split(" ");
root.maxBrightness = parseInt(max);
root.brightness = (parseInt(cur) / root.maxBrightness) ** (1 / exp);
} }
} }
Process {
id: ddcProc
command: ["ddcutil", "detect", "--brief"]
stdout: StdioCollector {
onStreamFinished: root.ddcMonitors = text.trim().split("\n\n").filter(d => d.startsWith("Display ")).map(d => ({
model: d.match(/Monitor:.*:(.*):.*/)[1],
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)[1]
}))
}
} }
CustomShortcut { CustomShortcut {
@ -110,77 +90,4 @@ Singleton {
root.decreaseBrightness(); root.decreaseBrightness();
} }
} }
component Monitor: QtObject {
id: monitor
required property ShellScreen modelData
readonly property bool isDdc: root.ddcMonitors.some(m => m.model === modelData.model)
readonly property string busNum: root.ddcMonitors.find(m => m.model === modelData.model)?.busNum ?? ""
readonly property bool isAppleDisplay: root.appleDisplayPresent && modelData.model.startsWith("StudioDisplay")
property real brightness
property real queuedBrightness: NaN
readonly property Process initProc: Process {
stdout: StdioCollector {
onStreamFinished: {
if (monitor.isAppleDisplay) {
const val = parseInt(text.trim());
monitor.brightness = val / 101;
} else {
const [, , , cur, max] = text.split(" ");
monitor.brightness = parseInt(cur) / parseInt(max);
}
}
}
}
readonly property Timer timer: Timer {
interval: 500
onTriggered: {
if (!isNaN(monitor.queuedBrightness)) {
monitor.setBrightness(monitor.queuedBrightness);
monitor.queuedBrightness = NaN;
}
}
}
function setBrightness(value: real): void {
value = Math.max(0, Math.min(1, value));
const rounded = Math.round(value * 100);
if (Math.round(brightness * 100) === rounded)
return;
if (isDdc && timer.running) {
queuedBrightness = value;
return;
}
brightness = value;
if (isAppleDisplay)
Quickshell.execDetached(["asdbctl", "set", rounded]);
else if (isDdc)
Quickshell.execDetached(["ddcutil", "-b", busNum, "setvcp", "10", rounded]);
else
Quickshell.execDetached(["brightnessctl", "s", `${rounded}%`]);
if (isDdc)
timer.restart();
}
function initBrightness(): void {
if (isAppleDisplay)
initProc.command = ["asdbctl", "get"];
else if (isDdc)
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
else
initProc.command = ["sh", "-c", "echo a b c $(brightnessctl g) $(brightnessctl m)"];
initProc.running = true;
}
onBusNumChanged: initBrightness()
Component.onCompleted: initBrightness()
}
} }