diff --git a/example.toml b/example.toml index 743c651f61..09c0dacb24 100644 --- a/example.toml +++ b/example.toml @@ -211,6 +211,7 @@ enable_ddcutil = false # Per-monitor backend override: # [brightness.monitor.eDP-1] # backend = "backlight" # auto | none | backlight | ddcutil +# backlight_device = "intel_backlight" # explicit sysfs device name or path; run: noctalia msg brightness-list-backlight-devices # [brightness.monitor.DP-1] # backend = "ddcutil" diff --git a/src/config/config_types.h b/src/config/config_types.h index 5255f0364a..2535e2c78a 100644 --- a/src/config/config_types.h +++ b/src/config/config_types.h @@ -1081,6 +1081,7 @@ constexpr EnumOption kBrightnessBackendPreferences[ struct BrightnessMonitorOverride { std::string match; std::optional backend; + std::optional backlightDevice; // sysfs device name or path, e.g. "intel_backlight" bool operator==(const BrightnessMonitorOverride&) const = default; }; diff --git a/src/config/schema/config_schema.cpp b/src/config/schema/config_schema.cpp index c8f88246b9..703d83a9c2 100644 --- a/src/config/schema/config_schema.cpp +++ b/src/config/schema/config_schema.cpp @@ -432,6 +432,19 @@ namespace noctalia::config::schema { static const Schema s = { field(&BrightnessMonitorOverride::match, "match"), optionalEnumField(&BrightnessMonitorOverride::backend, "backend", kBrightnessBackendPreferences), + custom( + "backlight_device", + [](const toml::table& tbl, BrightnessMonitorOverride& out, std::string_view, Diagnostics&) { + if (auto v = tbl["backlight_device"].value()) { + out.backlightDevice = *v; + } + }, + [](toml::table& tbl, const BrightnessMonitorOverride& in) { + if (in.backlightDevice.has_value()) { + tbl.insert_or_assign("backlight_device", *in.backlightDevice); + } + } + ), }; return s; } diff --git a/src/system/brightness_service.cpp b/src/system/brightness_service.cpp index c6288a17f7..262cfda13e 100644 --- a/src/system/brightness_service.cpp +++ b/src/system/brightness_service.cpp @@ -189,6 +189,34 @@ namespace { return BrightnessBackendPreference::Auto; } + // Returns the explicit backlight device name/path configured for this output, if any. + std::optional backlightDeviceForOutput(const BrightnessConfig& config, const WaylandOutput* output) { + if (output == nullptr) { + return std::nullopt; + } + + for (const auto& override : config.monitorOverrides) { + if (override.match.empty() || !outputMatchesSelector(override.match, *output)) { + continue; + } + if (override.backlightDevice.has_value()) { + return *override.backlightDevice; + } + break; + } + + return std::nullopt; + } + + // Returns the sysfs device name from either a bare name ("intel_backlight") or a path. + std::string_view extractBacklightDeviceName(std::string_view deviceSpec) { + const auto lastSlash = deviceSpec.rfind('/'); + if (lastSlash != std::string_view::npos) { + return deviceSpec.substr(lastSlash + 1); + } + return deviceSpec; + } + void applyOutputMetadata(BrightnessDisplay& display, const WaylandOutput& output) { display.label = output.description.empty() ? output.connectorName : output.description; display.physicalWidth = output.width; @@ -841,6 +869,16 @@ struct BrightnessService::Impl { continue; } + if (const auto explicitDevice = backlightDeviceForOutput(activeConfig, output); explicitDevice.has_value()) { + if (extractBacklightDeviceName(*explicitDevice) != name) { + kLog.debug( + "skipping backlight '{}' for connector {} (explicit device '{}' configured)", name, connectorName, + *explicitDevice + ); + continue; + } + } + if (sessionProxy == nullptr) { kLog.debug("skipping backlight '{}' because logind brightness control is unavailable", name); continue; @@ -1607,6 +1645,28 @@ void BrightnessService::registerIpc(IpcService& ipc, std::function onBat "brightness-down", -1.0f, "brightness-down [current|*|all|monitor-selector] [step]", "Decrease brightness (defaults to current monitor)" ); + + ipc.registerHandler( + "brightness-list-backlight-devices", + [](const std::string& /*args*/) -> std::string { + const std::string backlightDir = "/sys/class/backlight"; + DIR* dir = ::opendir(backlightDir.c_str()); + if (dir == nullptr) { + return "error: no backlight devices available\n"; + } + std::string result; + while (auto* entry = ::readdir(dir)) { + const std::string name = entry->d_name; + if (name == "." || name == "..") { + continue; + } + result += name + "\n"; + } + ::closedir(dir); + return result.empty() ? "error: no backlight devices available\n" : result; + }, + "brightness-list-backlight-devices", "List available sysfs backlight device names" + ); } void BrightnessService::setChangeCallback(ChangeCallback callback) { m_impl->changeCallback = std::move(callback); }