Something went wrong. Try again.
A Material 3 inspired desktop shell for Niri and Hyprland created with Ignis.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134from __future__ import annotations
import loggingfrom pathlib import Path
from gi.repository import Gio, GLib, GObject
log = logging.getLogger("exo.backlight")
SYSFS_BACKLIGHT = Path("/sys/class/backlight")
def _read_int(path: Path) -> int | None: try: return int(path.read_text().strip()) except (OSError, ValueError): return None
def _pick_device(root: Path = SYSFS_BACKLIGHT) -> Path | None: try: candidates = sorted(p for p in root.iterdir() if p.is_dir()) except OSError: return None
if not candidates: return None
def score(path: Path) -> tuple[int, str]: name = path.name max_brightness = _read_int(path / "max_brightness") or 0 if max_brightness <= 0: return (99, name) if name == "intel_backlight": rank = 0 elif name.startswith("amdgpu_bl"): rank = 1 elif "backlight" in name: rank = 2 else: rank = 3 return (rank, name)
for path in sorted(candidates, key=score): if (_read_int(path / "max_brightness") or 0) > 0: return path return None
class SysfsBacklight(GObject.Object): _instance: SysfsBacklight | None = None
def __init__(self, device: Path | None = None): super().__init__() self._device = device if device is not None else _pick_device() self._monitor: Gio.FileMonitor | None = None self._available = self._device is not None self._max_brightness_value = 0 self._brightness_value = 0
if self._device is not None: self._max_brightness_value = _read_int(self._device / "max_brightness") or 0 self._brightness_value = _read_int(self._device / "brightness") or 0 self._watch() log.info( "sysfs backlight: %s (max=%s)", self._device.name, self._max_brightness_value, )
@classmethod def get_default(cls) -> SysfsBacklight: if cls._instance is None: cls._instance = cls() return cls._instance
@GObject.Property(type=bool, default=False) def available(self) -> bool: return self._available
@GObject.Property(type=int, default=0) def max_brightness(self) -> int: return self._max_brightness_value
@GObject.Property(type=int, default=0) def brightness(self) -> int: return self._brightness_value
@brightness.setter def brightness(self, value: int) -> None: if self._device is None or self._max_brightness_value <= 0: return value = max(0, min(int(value), self._max_brightness_value)) try: (self._device / "brightness").write_text(str(value)) except OSError as error: log.warning("failed to set backlight brightness: %s", error) return self._set_brightness(value)
def _set_brightness(self, value: int) -> None: value = max(0, int(value)) if value == self._brightness_value: return self._brightness_value = value self.notify("brightness")
def _refresh(self) -> None: if self._device is None: return current = _read_int(self._device / "brightness") if current is not None: self._set_brightness(current)
def _watch(self) -> None: if self._device is None: return try: gfile = Gio.File.new_for_path(str(self._device / "brightness")) self._monitor = gfile.monitor_file(Gio.FileMonitorFlags.NONE, None) self._monitor.connect("changed", self._on_changed) except GLib.Error as error: log.warning("failed to watch backlight brightness: %s", error) self._monitor = None
def _on_changed(self, _monitor, _file, _other_file, event_type) -> None: if event_type in ( Gio.FileMonitorEvent.CHANGED, Gio.FileMonitorEvent.CHANGES_DONE_HINT, Gio.FileMonitorEvent.ATTRIBUTE_CHANGED, Gio.FileMonitorEvent.CREATED, ): self._refresh()