diff --git a/assets/translations/en.json b/assets/translations/en.json index 16e1bd71f..ecfb8638f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1464,7 +1464,8 @@ }, "ddcutil": { "label": "DDC/CI (ddcutil)", - "description": "Control external monitor brightness via DDC/CI" + "description": "Control external monitor brightness via DDC/CI", + "requires-ddcutil": "Install ddcutil to enable DDC/CI brightness control" }, "night-light": { "label": "Night Light", diff --git a/src/app/application.cpp b/src/app/application.cpp index a6afdeba8..50a3c04a4 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -578,8 +578,8 @@ void Application::initServices() { } try { - m_brightnessService = - std::make_unique(m_systemBus.get(), m_wayland, m_configService.config().brightness); + m_brightnessService = std::make_unique( + m_systemBus.get(), m_wayland, m_configService.config().brightness, &m_dependencyService); m_brightnessService->setChangeCallback([this, shouldRefreshControlCenter]() { m_brightnessOsd.onBrightnessChanged(*m_brightnessService); m_bar.refresh(); diff --git a/src/shell/control_center/control_center_panel.cpp b/src/shell/control_center/control_center_panel.cpp index 8baedf7c2..393f06c45 100644 --- a/src/shell/control_center/control_center_panel.cpp +++ b/src/shell/control_center/control_center_panel.cpp @@ -1,10 +1,12 @@ #include "shell/control_center/control_center_panel.h" +#include "config/config_service.h" #include "i18n/i18n.h" #include "notification/notification_manager.h" #include "render/core/renderer.h" #include "render/scene/input_area.h" #include "shell/panel/panel_manager.h" +#include "system/brightness_service.h" #include "system/dependency_service.h" #include "ui/controls/button.h" #include "ui/controls/flex.h" @@ -22,6 +24,8 @@ ControlCenterPanel::ControlCenterPanel( SystemMonitorService* sysmon, NightLightManager* nightLight, noctalia::theme::ThemeService* theme, IdleInhibitor* idleInhibitor, DependencyService* dependencies, WaylandConnection* wayland, Wallpaper* wallpaper) { (void)upower; + m_config = config; + m_brightness = brightness; m_notificationManager = notifications; m_dependencies = dependencies; m_tabs[tabIndex(TabId::Overview)] = @@ -243,6 +247,9 @@ void ControlCenterPanel::onOpen(std::string_view context) { if (m_dependencies != nullptr) { m_dependencies->rescan(); } + if (m_brightness != nullptr && m_config != nullptr) { + m_brightness->reload(m_config->config().brightness); + } selectTab(tabFromContext(context)); } diff --git a/src/shell/control_center/control_center_panel.h b/src/shell/control_center/control_center_panel.h index f39d95108..27c06a03a 100644 --- a/src/shell/control_center/control_center_panel.h +++ b/src/shell/control_center/control_center_panel.h @@ -135,6 +135,8 @@ private: std::array m_tabContainers{}; std::array m_tabHeaderActions{}; TabId m_activeTab = TabId::Overview; + ConfigService* m_config = nullptr; + BrightnessService* m_brightness = nullptr; NotificationManager* m_notificationManager = nullptr; DependencyService* m_dependencies = nullptr; }; diff --git a/src/shell/settings/settings_registry.cpp b/src/shell/settings/settings_registry.cpp index cf77fb4aa..f854df2a9 100644 --- a/src/shell/settings/settings_registry.cpp +++ b/src/shell/settings/settings_registry.cpp @@ -620,8 +620,11 @@ namespace settings { {"shell", "mpris", "blacklist"}, ListSetting{.items = cfg.shell.mpris.blacklist}, "mpris media player dbus session blacklist")); entries.push_back(makeEntry("services", "brightness", tr("settings.schema.services.ddcutil.label"), - tr("settings.schema.services.ddcutil.description"), {"brightness", "enable_ddcutil"}, - ToggleSetting{cfg.brightness.enableDdcutil}, "monitor ddcutil")); + env.ddcutilAvailable ? tr("settings.schema.services.ddcutil.description") + : tr("settings.schema.services.ddcutil.requires-ddcutil"), + {"brightness", "enable_ddcutil"}, + ToggleSetting{.checked = cfg.brightness.enableDdcutil, .enabled = env.ddcutilAvailable}, + "monitor ddcutil")); if (!env.wlsunsetAvailable) { // Show only the master toggle in a disabled state so users can discover the feature // and learn the dependency requirement. The remaining settings are hidden until wlsunset is installed. diff --git a/src/shell/settings/settings_registry.h b/src/shell/settings/settings_registry.h index 9f3919945..9b1f910f0 100644 --- a/src/shell/settings/settings_registry.h +++ b/src/shell/settings/settings_registry.h @@ -111,6 +111,7 @@ namespace settings { // Runtime conditions that gate optional sections (e.g. compositor-specific features). struct RegistryEnvironment { bool niriBackdropSupported = false; // hide the [backdrop] section when false + bool ddcutilAvailable = false; // disable ddcutil toggle when ddcutil is not on PATH bool wlsunsetAvailable = false; // hide night-light entries when wlsunset is not on PATH std::vector availableOutputs; // monitor selectors available on this machine std::vector communityPalettes; diff --git a/src/shell/settings/settings_window.cpp b/src/shell/settings/settings_window.cpp index c9422d68b..68c8909f0 100644 --- a/src/shell/settings/settings_window.cpp +++ b/src/shell/settings/settings_window.cpp @@ -948,6 +948,7 @@ void SettingsWindow::buildScene(std::uint32_t width, std::uint32_t height) { } settings::RegistryEnvironment env; env.niriBackdropSupported = (m_wayland != nullptr && compositors::isNiri()); + env.ddcutilAvailable = (m_dependencies != nullptr && m_dependencies->hasDdcutil()); env.wlsunsetAvailable = (m_dependencies != nullptr && m_dependencies->hasWlsunset()); for (const auto& paletteInfo : noctalia::theme::availableCommunityPalettes()) { env.communityPalettes.push_back(settings::SelectOption{paletteInfo.name, paletteInfo.name}); diff --git a/src/system/brightness_service.cpp b/src/system/brightness_service.cpp index 6800848b0..2f786e010 100644 --- a/src/system/brightness_service.cpp +++ b/src/system/brightness_service.cpp @@ -2,11 +2,11 @@ #include "config/config_service.h" #include "core/log.h" -#include "core/process.h" #include "core/timer_manager.h" #include "dbus/system_bus.h" #include "ipc/ipc_arg_parse.h" #include "ipc/ipc_service.h" +#include "system/dependency_service.h" #include "wayland/wayland_connection.h" #include @@ -586,6 +586,7 @@ namespace { struct BrightnessService::Impl { SystemBus* bus = nullptr; WaylandConnection& wayland; + DependencyService* dependencies = nullptr; BrightnessConfig activeConfig; ChangeCallback changeCallback; @@ -613,8 +614,8 @@ struct BrightnessService::Impl { std::unordered_map pendingRefreshes; std::queue completions; - Impl(SystemBus* systemBus, WaylandConnection& wl, const BrightnessConfig& config) - : bus(systemBus), wayland(wl), activeConfig(config) { + Impl(SystemBus* systemBus, WaylandConnection& wl, const BrightnessConfig& config, DependencyService* deps) + : bus(systemBus), wayland(wl), dependencies(deps), activeConfig(config) { setupPollFds(); workerThread = std::thread([this]() { workerLoop(); }); } @@ -816,7 +817,7 @@ struct BrightnessService::Impl { if (!activeConfig.enableDdcutil) { return; } - if (!process::commandExists("ddcutil")) { + if (dependencies != nullptr && !dependencies->hasDdcutil()) { if (!warnedMissingDdcutil) { kLog.warn("brightness.enable_ddcutil is set but ddcutil is not installed"); warnedMissingDdcutil = true; @@ -1307,8 +1308,9 @@ struct BrightnessService::Impl { } }; -BrightnessService::BrightnessService(SystemBus* bus, WaylandConnection& wayland, const BrightnessConfig& config) - : m_impl(new Impl(bus, wayland, config)) { +BrightnessService::BrightnessService(SystemBus* bus, WaylandConnection& wayland, const BrightnessConfig& config, + DependencyService* dependencies) + : m_impl(new Impl(bus, wayland, config, dependencies)) { m_impl->rebuildState(false); } diff --git a/src/system/brightness_service.h b/src/system/brightness_service.h index 4cc5e36ea..7cd13f905 100644 --- a/src/system/brightness_service.h +++ b/src/system/brightness_service.h @@ -6,6 +6,7 @@ #include class IpcService; +class DependencyService; class SystemBus; class WaylandConnection; struct BrightnessConfig; @@ -29,7 +30,8 @@ class BrightnessService { public: using ChangeCallback = std::function; - BrightnessService(SystemBus* bus, WaylandConnection& wayland, const BrightnessConfig& config); + BrightnessService(SystemBus* bus, WaylandConnection& wayland, const BrightnessConfig& config, + DependencyService* dependencies = nullptr); ~BrightnessService(); BrightnessService(const BrightnessService&) = delete; diff --git a/src/system/dependency_service.cpp b/src/system/dependency_service.cpp index e01970b47..55ec2d991 100644 --- a/src/system/dependency_service.cpp +++ b/src/system/dependency_service.cpp @@ -11,7 +11,8 @@ namespace { constexpr Logger kLog("dependencies"); // Optional CLI tools the shell knows about. Adding a new tracked tool is one line. - constexpr std::array kTrackedTools = { + constexpr std::array kTrackedTools = { + "ddcutil", "wlsunset", }; diff --git a/src/system/dependency_service.h b/src/system/dependency_service.h index ae75abb13..9d347927c 100644 --- a/src/system/dependency_service.h +++ b/src/system/dependency_service.h @@ -9,6 +9,7 @@ public: DependencyService(); [[nodiscard]] bool has(std::string_view name) const; + [[nodiscard]] bool hasDdcutil() const { return has("ddcutil"); } [[nodiscard]] bool hasWlsunset() const { return has("wlsunset"); } void rescan();