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:
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user