diff --git a/rpi_backlight/__init__.py b/rpi_backlight/__init__.py index 1883241..bc1525a 100644 --- a/rpi_backlight/__init__.py +++ b/rpi_backlight/__init__.py @@ -29,7 +29,9 @@ class BoardType(Enum): _BACKLIGHT_SYSFS_PATHS = { - BoardType.RASPBERRY_PI: next(iglob("/sys/class/backlight/*-0045/"), "/sys/class/backlight/rpi_backlight/"), + BoardType.RASPBERRY_PI: next( + iglob("/sys/class/backlight/*-0045/"), "/sys/class/backlight/rpi_backlight/" + ), BoardType.TINKER_BOARD: "/sys/devices/platform/ff150000.i2c/i2c-3/3-0045/", BoardType.TINKER_BOARD_2: "/sys/devices/platform/ff3e0000.i2c/i2c-8/8-0045/", BoardType.GENERIC: "/sys/class/backlight/backlight/", @@ -38,6 +40,56 @@ class BoardType(Enum): _EMULATOR_MAGIC_STRING = ":emulator:" +def _detect_working_rpi_backlight_sysfs_path() -> Path: + """ + Pick a working backlight device on systems that expose multiple + /sys/class/backlight/* entries (e.g. Pi 5 + KMS/DSI). + We probe by writing the current brightness value back to 'brightness'. + """ + base = Path("/sys/class/backlight") + if not base.exists(): + return Path("/sys/class/backlight/rpi_backlight/") + + try: + candidates = sorted(p for p in base.iterdir() if p.is_dir()) + except OSError: + return Path( + next( + iglob("/sys/class/backlight/*-0045/"), + "/sys/class/backlight/rpi_backlight/", + ) + ) + + def _read_int(path: Path): + try: + return int(path.read_text()) + except Exception: + return None + + for p in candidates: + brightness = p / "brightness" + if not brightness.exists(): + continue + + current = _read_int(p / "actual_brightness") + if current is None: + current = _read_int(brightness) + if current is None: + continue + + try: + brightness.write_text(str(current)) + return p + except OSError as e: + if e.errno in (errno.EINVAL, errno.EIO, getattr(errno, "EREMOTEIO", 121)): + continue + if e.errno == errno.EPERM: + raise + continue + + return Path("/sys/class/backlight/rpi_backlight/") + + def _permission_denied() -> None: raise PermissionError( "You must either run this program as root or change the permissions " @@ -60,7 +112,10 @@ def __init__( ) if not backlight_sysfs_path: - backlight_sysfs_path = _BACKLIGHT_SYSFS_PATHS[board_type] + if board_type == BoardType.RASPBERRY_PI: + backlight_sysfs_path = str(_detect_working_rpi_backlight_sysfs_path()) + else: + backlight_sysfs_path = _BACKLIGHT_SYSFS_PATHS[board_type] elif backlight_sysfs_path == _EMULATOR_MAGIC_STRING: if not _EMULATOR_SYSFS_TMP_FILE_PATH.exists(): raise RuntimeError( diff --git a/rpi_backlight/cli.py b/rpi_backlight/cli.py index c65e1ba..c1f9251 100644 --- a/rpi_backlight/cli.py +++ b/rpi_backlight/cli.py @@ -77,6 +77,11 @@ def main(): parser = _create_argument_parser() args = parser.parse_args() + if args.sysfs_path in ("on", "off"): + parser.error( + "invalid SYSFS_PATH. Use -p on/off (or -p toggle) instead of 'rpi-backlight on|off'." + ) + backlight = Backlight( board_type=STRING_TO_BOARD_TYPE[args.board_type], backlight_sysfs_path=args.sysfs_path, @@ -102,8 +107,12 @@ def main(): "-b/--set-brightness must be used without other options except for -d/--duration" ) # backlight.fade context manager can be used always as args.fade defaults to zero + if (backlight.power is False) and (args.set_brightness > 0): + backlight.power = True with backlight.fade(duration=args.duration): backlight.brightness = args.set_brightness + if (backlight.power is True) and (args.set_brightness == 0): + backlight.power = False return if args.set_power: @@ -122,6 +131,19 @@ def main(): backlight.power = True with backlight.fade(duration=args.duration): backlight.brightness = 100 + elif args.set_power == "on": + # Ensure brightness is 0 when we turn the display on + backlight.brightness = 0 + if args.board_type == "raspberry-pi": + backlight.power = True + with backlight.fade(duration=args.duration): + backlight.brightness = 100 + elif args.set_power == "off": + if backlight.power: + with backlight.fade(duration=args.duration): + backlight.brightness = 0 + if args.board_type == "raspberry-pi": + backlight.power = False else: backlight.power = True if args.set_power == "on" else False return