1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-09 23:15:38 -05:00

display: use ddcutil for brightness changing

This commit is contained in:
bbedward
2025-07-15 16:22:06 -04:00
parent 3df92697e0
commit 13ef607758
4 changed files with 158 additions and 89 deletions

View File

@@ -12,12 +12,13 @@ Specifically created for [Niri](https://github.com/YaLTeR/niri).
```bash ```bash
# Arch # Arch
paru -S quickshell-git nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard paru -S quickshell-git nerd-fonts ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil
# Some dependencies are optional # Some dependencies are optional
# - cava for audio visualizer, without it music will just randomly visualize # - cava for audio visualizer, without it music will just randomly visualize
# - cliphist for clipboard history # - cliphist for clipboard history
# - matugen for dynamic themes based on wallpaper # - matugen for dynamic themes based on wallpaper
# - ddcutil for brightness changing
``` ```
2. Configure SwayBG (Optional) 2. Configure SwayBG (Optional)

View File

@@ -7,101 +7,160 @@ pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root
property int brightnessLevel: 75 property list<var> ddcMonitors: []
readonly property list<Monitor> monitors: variants.instances
property bool brightnessAvailable: false property bool brightnessAvailable: false
property int brightnessLevel: 75
// Check if brightness control is available function getMonitorForScreen(screen: ShellScreen): var {
Process { return monitors.find(function(m) { return m.modelData === screen; });
id: brightnessAvailabilityChecker
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then echo 'brightnessctl'; elif command -v xbacklight > /dev/null; then echo 'xbacklight'; else echo 'none'; fi"]
running: true
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
let method = data.trim()
if (method === "brightnessctl" || method === "xbacklight") {
root.brightnessAvailable = true
brightnessChecker.running = true
} else {
root.brightnessAvailable = false
console.log("Brightness control not available - no brightnessctl or xbacklight found")
}
}
}
}
} }
// Brightness Control property var debounceTimer: Timer {
Process { id: debounceTimer
id: brightnessChecker interval: 50
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then brightnessctl get; elif command -v xbacklight > /dev/null; then xbacklight -get | cut -d. -f1; else echo 75; fi"] repeat: false
running: false property int pendingValue: 0
onTriggered: {
stdout: SplitParser { const focusedMonitor = monitors.find(function(m) { return m.modelData === Quickshell.screens[0]; });
splitMarker: "\n" if (focusedMonitor) {
onRead: (data) => { focusedMonitor.setBrightness(pendingValue / 100);
if (data.trim()) {
let brightness = parseInt(data.trim()) || 75
// brightnessctl returns absolute value, need to convert to percentage
if (brightness > 100) {
brightnessMaxChecker.running = true
} else {
root.brightnessLevel = brightness
}
}
}
}
}
Process {
id: brightnessMaxChecker
command: ["brightnessctl", "max"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
let maxBrightness = parseInt(data.trim()) || 100
brightnessCurrentChecker.property("maxBrightness", maxBrightness)
brightnessCurrentChecker.running = true
}
}
}
}
Process {
id: brightnessCurrentChecker
property int maxBrightness: 100
command: ["brightnessctl", "get"]
running: false
stdout: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
let currentBrightness = parseInt(data.trim()) || 75
root.brightnessLevel = Math.round((currentBrightness / maxBrightness) * 100)
}
} }
} }
} }
function setBrightness(percentage) { function setBrightness(percentage) {
if (!root.brightnessAvailable) { root.brightnessLevel = percentage;
console.warn("Brightness control not available") debounceTimer.pendingValue = percentage;
return debounceTimer.restart();
}
function increaseBrightness(): void {
const focusedMonitor = monitors.find(function(m) { return m.modelData === Quickshell.screens[0]; });
if (focusedMonitor)
focusedMonitor.setBrightness(focusedMonitor.brightness + 0.1);
}
function decreaseBrightness(): void {
const focusedMonitor = monitors.find(function(m) { return m.modelData === Quickshell.screens[0]; });
if (focusedMonitor)
focusedMonitor.setBrightness(focusedMonitor.brightness - 0.1);
}
onMonitorsChanged: {
ddcMonitors = [];
if (ddcAvailable) {
ddcProc.running = true;
} }
let brightnessSetProcess = Qt.createQmlObject(' // Update brightness level from first monitor
import Quickshell.Io if (monitors.length > 0) {
Process { root.brightnessLevel = Math.round(monitors[0].brightness * 100);
command: ["bash", "-c", "if command -v brightnessctl > /dev/null; then brightnessctl set ' + percentage + '%; elif command -v xbacklight > /dev/null; then xbacklight -set ' + percentage + '; fi"] }
running: true }
onExited: brightnessChecker.running = true
Component.onCompleted: {
ddcAvailabilityChecker.running = true;
}
Variants {
id: variants
model: Quickshell.screens
Monitor {}
}
Process {
id: ddcAvailabilityChecker
command: ["which", "ddcutil"]
onExited: function(exitCode) {
root.brightnessAvailable = (exitCode === 0);
if (root.brightnessAvailable) {
ddcProc.running = true;
} }
', root) }
}
Process {
id: ddcProc
command: ["ddcutil", "detect", "--brief"]
running: false
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
root.ddcMonitors = text.trim().split("\n\n").filter(function(d) { return d.startsWith("Display "); }).map(function(d) { return ({
model: d.match(/Monitor:.*:(.*):.*/)?.[1] || "Unknown",
busNum: d.match(/I2C bus:[ ]*\/dev\/i2c-([0-9]+)/)?.[1] || "0"
}); });
} else {
root.ddcMonitors = [];
}
}
}
onExited: function(exitCode) {
if (exitCode !== 0) {
root.ddcMonitors = [];
}
}
}
component Monitor: QtObject {
id: monitor
required property ShellScreen modelData
readonly property bool isDdc: root.ddcMonitors.some(function(m) { return m.model === modelData.model; })
readonly property string busNum: root.ddcMonitors.find(function(m) { return m.model === modelData.model; })?.busNum ?? ""
property real brightness: 0.75
readonly property Process initProc: Process {
stdout: StdioCollector {
onStreamFinished: {
if (text.trim()) {
const parts = text.trim().split(" ");
if (parts.length >= 5) {
const current = parseInt(parts[3]) || 75;
const max = parseInt(parts[4]) || 100;
monitor.brightness = current / max;
root.brightnessLevel = Math.round(monitor.brightness * 100);
}
}
}
}
onExited: function(exitCode) {
if (exitCode !== 0) {
monitor.brightness = 0.75;
root.brightnessLevel = 75;
}
}
}
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;
brightness = value;
root.brightnessLevel = rounded;
if (isDdc && busNum) {
Quickshell.execDetached(["ddcutil", "-b", busNum, "setvcp", "10", rounded.toString()]);
}
}
onBusNumChanged: {
if (isDdc && busNum) {
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
initProc.running = true;
}
}
Component.onCompleted: {
Qt.callLater(function() {
if (isDdc && busNum) {
initProc.command = ["ddcutil", "-b", busNum, "getvcp", "10", "--brief"];
initProc.running = true;
}
});
}
} }
} }

View File

@@ -318,6 +318,9 @@ Singleton {
const lines = text.split('\n') const lines = text.split('\n')
let section = 'memory' let section = 'memory'
const coreUsages = [] const coreUsages = []
let memFree = 0
let memBuffers = 0
let memCached = 0
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim() const line = lines[i].trim()
@@ -333,9 +336,12 @@ Singleton {
if (section === 'memory') { if (section === 'memory') {
if (line.startsWith('MemTotal:')) { if (line.startsWith('MemTotal:')) {
root.totalMemoryKB = parseInt(line.split(/\s+/)[1]) root.totalMemoryKB = parseInt(line.split(/\s+/)[1])
} else if (line.startsWith('MemAvailable:')) { } else if (line.startsWith('MemFree:')) {
const availableKB = parseInt(line.split(/\s+/)[1]) memFree = parseInt(line.split(/\s+/)[1])
root.usedMemoryKB = root.totalMemoryKB - availableKB } else if (line.startsWith('Buffers:')) {
memBuffers = parseInt(line.split(/\s+/)[1])
} else if (line.startsWith('Cached:')) {
memCached = parseInt(line.split(/\s+/)[1])
} else if (line.startsWith('SwapTotal:')) { } else if (line.startsWith('SwapTotal:')) {
root.totalSwapKB = parseInt(line.split(/\s+/)[1]) root.totalSwapKB = parseInt(line.split(/\s+/)[1])
} else if (line.startsWith('SwapFree:')) { } else if (line.startsWith('SwapFree:')) {
@@ -383,6 +389,9 @@ Singleton {
} }
} }
// Calculate used memory as total minus free minus buffers minus cached
root.usedMemoryKB = root.totalMemoryKB - memFree - memBuffers - memCached
// Update per-core usage // Update per-core usage
root.perCoreCpuUsage = coreUsages root.perCoreCpuUsage = coreUsages

View File

@@ -36,7 +36,7 @@ ScrollView {
rightIcon: "brightness_high" rightIcon: "brightness_high"
enabled: BrightnessService.brightnessAvailable enabled: BrightnessService.brightnessAvailable
onSliderValueChanged: (newValue) => { onSliderValueChanged: function(newValue) {
BrightnessService.setBrightness(newValue) BrightnessService.setBrightness(newValue)
} }
} }