Files
onyx/Spikes/brightness-probe.swift
ScarriffleandClaude Opus 5 9b600de9a9 Live Activities unter der Notch, nicht daneben
Neben ihr wären die Arme zwangsläufig gleich breit — die Silhouette zeichnet den
Steg mittig um die Hardware. Ein Symbol links und ein Balken rechts hieße dann
132 Punkte Schwarz für ein Symbol. Darunter hängt die Fläche frei und wird so
breit, wie ihr Inhalt es braucht.

Lautstärke, Strom, Titelwechsel und Helligkeit. Gemeldet wird nur, was sich
**geändert** hat: `PowerWatcher` schweigt beim ersten gelesenen Zustand, sonst
begrüßte Onyx jeden Start mit „am Netz".

Die Helligkeit kostete einen Spike. Von drei Wegen trägt nur `DisplayServices`,
und nur für den eingebauten Bildschirm. `CoreDisplay` — der Weg, den jede
Anleitung empfiehlt — gibt für alle Bildschirme 1.0 zurück, auch für den, der
auf 62 % stand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 21:05:13 +02:00

82 lines
3.1 KiB
Swift

import AppKit
import IOKit
// Gibt es einen benutzbaren Weg, die Bildschirmhelligkeit zu *lesen* — und
// besser noch: auf ihre Änderung zu *hören*?
//
// Öffentlich gibt es keinen. Geprüft werden deshalb drei private bzw.
// halböffentliche Wege, jeder einzeln, damit hinterher klar ist, welcher trägt
// und welcher nur auf manchen Geräten funktioniert.
let displays: [CGDirectDisplayID] = NSScreen.screens.compactMap {
$0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? CGDirectDisplayID
}
print("Bildschirme: \(displays)\n")
// MARK: - Weg 1: DisplayServices
typealias GetBrightness = @convention(c) (CGDirectDisplayID, UnsafeMutablePointer<Float>) -> Int32
typealias RegisterNotify = @convention(c) (CGDirectDisplayID, UnsafeRawPointer?, UnsafeRawPointer?) -> Int32
let dsPath = "/System/Library/PrivateFrameworks/DisplayServices.framework/DisplayServices"
if let handle = dlopen(dsPath, RTLD_LAZY) {
print("DisplayServices: geladen")
if let sym = dlsym(handle, "DisplayServicesGetBrightness") {
let fn = unsafeBitCast(sym, to: GetBrightness.self)
for id in displays {
var value: Float = -1
let status = fn(id, &value)
print(" GetBrightness(\(id)) → status \(status), Wert \(value)")
}
} else {
print(" DisplayServicesGetBrightness: nicht vorhanden")
}
print(" RegisterForBrightnessChangeNotifications: " +
(dlsym(handle, "DisplayServicesRegisterForBrightnessChangeNotifications") != nil
? "vorhanden" : "FEHLT"))
} else {
print("DisplayServices: nicht ladbar — \(String(cString: dlerror()))")
}
// MARK: - Weg 2: CoreDisplay
typealias UserBrightness = @convention(c) (CGDirectDisplayID) -> Double
let cdPath = "/System/Library/Frameworks/CoreDisplay.framework/CoreDisplay"
if let handle = dlopen(cdPath, RTLD_LAZY) {
print("\nCoreDisplay: geladen")
if let sym = dlsym(handle, "CoreDisplay_Display_GetUserBrightness") {
let fn = unsafeBitCast(sym, to: UserBrightness.self)
for id in displays {
print(" GetUserBrightness(\(id)) → \(fn(id))")
}
} else {
print(" CoreDisplay_Display_GetUserBrightness: nicht vorhanden")
}
} else {
print("\nCoreDisplay: nicht ladbar")
}
// MARK: - Weg 3: IOKit
print("\nIOKit IODisplayGetFloatParameter:")
var iterator = io_iterator_t()
let matching = IOServiceMatching("IODisplayConnect")
if IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iterator) == KERN_SUCCESS {
var found = false
var service = IOIteratorNext(iterator)
while service != 0 {
found = true
var brightness: Float = -1
let status = IODisplayGetFloatParameter(service, 0, kIODisplayBrightnessKey as CFString,
&brightness)
print(" Dienst \(service) → status \(status), Wert \(brightness)")
IOObjectRelease(service)
service = IOIteratorNext(iterator)
}
if !found { print(" kein IODisplayConnect gefunden (auf Apple Silicon üblich)") }
IOObjectRelease(iterator)
} else {
print(" IOServiceGetMatchingServices fehlgeschlagen")
}