借助systemd实现非特权背光调节.md


/sys/class/backlight/下面的背光设备默认好像得 root 权限才能写入,一个常见的解决方案是使用 udev 规则修改文件属组,再把自己加入进去。某天抬头一看,waybar 好像没问我要 root 也能改,気になります!

翻了下 waybar 的 backlight 实现,大概从这里开始,使用udev枚举了一波 backlight 设备,然后通过一个到org.freedesktop.login1.Session的 Proxy 设置亮度,有几个相关的链接:

看了看libudev的man page,建议我们使用sd-device替代

libudev.h provides an API to introspect and enumerate devices on the local system. This library is supported, but should not be used in new projects. Please see sd-device(3) for an equivalent replacement with a more modern API.

于是屁颠屁颠地跑去 systemd 那,好心人连 D-Bus client 都给封装好了做事是真的多


nix 人先 copy 一个 devShell 过来

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs = { nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = import nixpkgs { inherit system; };
      in {
        devShells.default = pkgs.mkShell {
          packages = with pkgs; [ systemd.dev ];
        };
      });
}
#include <stdio.h>
#include <systemd/sd-device.h>

#define _cleanup_(x) __attribute__((cleanup(x)))

#define SUBSYSTEM "backlight"

int main() {
  _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator =
      NULL;

  int r = sd_device_enumerator_new(&enumerator);
  if (r < 0) {
    fprintf(stderr, "Failed to create device enumerator: %s\n", strerror(-r));
    return 1;
  }

  r = sd_device_enumerator_add_match_subsystem(enumerator, SUBSYSTEM, 1);
  if (r < 0) {
    fprintf(stderr, "Failed to add match subsystem: %s\n", strerror(-r));
    return 1;
  }

  sd_device *backlight = sd_device_enumerator_get_device_first(enumerator);

  if (!backlight) {
    fprintf(stderr, "No backlight device found\n");
    return 1;
  }

  const char *name = NULL;
  r = sd_device_get_sysname(backlight, &name);

  if (r < 0) {
    fprintf(stderr, "Failed to get backlight sysname: %s\n", strerror(-r));
    return 1;
  }

  printf("Backlight device: %s\n", name);

  return 0;
}
cc backlight.c -lsystemd -o backlight && ./backlight

sd_device比较坏的地方是文档不完整,比如这个sd_device_enumerator_add_match_subsystem的第三个参数是啥意思,单看签名真的不懂,翻源码才发现置0排除匹配子系统。还有枚举到的sd_device,看stackoverflow上的老哥给的是每次都sd_device_unref,如果枚举出来设备不止一个,最后sd_device_enumerator_unref就会炸

unref enumerator 的时候应该也会 unref 里面的 device,这里再吐槽下这个 macro,萌新看不懂啦


回到正题,拿到name了之后就可以丢给 D-Bus 了

#include <stdio.h>
#include <systemd/sd-bus.h>

#define _cleanup_(x) __attribute__((cleanup(x)))

#define SUBSYSTEM "backlight"
#define DESTINATION "org.freedesktop.login1"
#define PATH "/org/freedesktop/login1/session/self"
#define INTERFACE "org.freedesktop.login1.Session"
#define METHOD "SetBrightness"

void set_brightness(const char *name, uint brightness) {
  _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
  _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;

  int r;

  r = sd_bus_open_system(&bus);
  if (r < 0) {
    fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
    return;
  }

  r = sd_bus_call_method(bus, DESTINATION, PATH, INTERFACE, METHOD, &error,
                         NULL, "ssu", SUBSYSTEM, name, brightness);
  if (r < 0) {
    fprintf(stderr, "Failed to set brightness: %s\n", error.message);
    return;
  }

  printf("Brightness set to %u\n", brightness);
}

DESTINATION 相当于一个 D-Bus 端点,通过 PATH 获取到 object,比如这里的/org/freedesktop/login1/session/self获取到的是对应当前 systemd session 的 object,实现了org.freedesktop.login1.Session接口,可以调用SetBrightness方法,参数签名为ssu,也就是string string unsignedsd_bus_call_method传进去那个NULL代表不接收返回值。按理说INTERFACE为了兼容 DCOP,是可以不显式指定的,sd-bus这里试了下会报错

实际写的时候用busctl进行了一个list tree introspect 防呆三板斧

搞定~


顺带一提获取背光亮度这种属性可以用sd_device_get_sysattr_value,如果要 watch 的话可以用sd_device_monitor (防止自己忘了.jpg)