From edd656f5e70c6535d78ec989a93a06e265840a6b Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Wed, 15 Jul 2026 13:36:06 +0800 Subject: [PATCH] fix(shortcut): support brightness keys on Wayland MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the Treeland output-manager protocol to read and adjust output brightness when Display1 is unavailable on Wayland. Serialize cross-process read-modify-write operations with a runtime lock so repeated shortcut processes do not lose updates, wait for compositor commit results before showing the OSD, and keep ambient auto-brightness enabled when an adjustment fails. Preserve the existing Display1 path on X11. 在 Wayland 下 Display1 不可用时,通过 Treeland output-manager 协议读取并调整输出亮度。使用运行时进程锁串行化跨进程的读取、修改和写入操作,避免快捷键重复触发时丢失亮度更新;等待合成器提交结果后再显示 OSD,并在调节失败时保持环境光自动亮度设置不变。X11 下继续使用原有 Display1 路径。 Log: support brightness keys on Wayland Change-Id: Ib70f62de7fbcb7f93f23bf3c550fbacf7eb2e4ea --- .../tools/dde-shortcut-tool/CMakeLists.txt | 3 + .../dde-shortcut-tool/displaycontroller.cpp | 51 +-- .../treelandbrightnesscontroller.cpp | 313 ++++++++++++++++++ .../treelandbrightnesscontroller.h | 86 +++++ 4 files changed, 431 insertions(+), 22 deletions(-) create mode 100644 src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.cpp create mode 100644 src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.h diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt index ef13a17..5461240 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(dde-shortcut-tool touchpadcontroller.h powercontroller.cpp powercontroller.h + treelandbrightnesscontroller.cpp + treelandbrightnesscontroller.h treelandlockscreenwrapper.cpp treelandlockscreenwrapper.h kbdlightcontroller.cpp @@ -51,6 +53,7 @@ qt6_generate_wayland_protocol_client_sources(dde-shortcut-tool NO_INCLUDE_CORE_ONLY FILES ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-dde-shell-v1.xml + ${TREELAND_PROTOCOLS_DATA_DIR}/treeland-output-manager-v1.xml ) target_link_libraries(dde-shortcut-tool PRIVATE diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp index 9f8b0fd..31ebce8 100644 --- a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/displaycontroller.cpp @@ -3,6 +3,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #include "displaycontroller.h" +#include "treelandbrightnesscontroller.h" #include #include @@ -23,8 +24,8 @@ DisplayController::DisplayController(QObject *parent) , m_isWayland(qEnvironmentVariable("XDG_SESSION_TYPE").toLower() == "wayland") { if (m_isWayland) { - // TODO: Display1 is not available on Wayland. Adapt display actions to - // Treeland/compositor APIs instead of using org.deepin.dde.Display1. + // Brightness uses Treeland directly on Wayland. Other display actions + // still avoid the unavailable org.deepin.dde.Display1 service. qInfo() << "DisplayController: skip Display1 initialization on Wayland"; return; } @@ -101,13 +102,7 @@ QString DisplayController::actionHelp(const QString &action) const bool DisplayController::changeBrightness(bool raised) { - if (m_isWayland) { - // TODO: Implement Wayland brightness control without Display1. - qWarning() << "DisplayController: brightness control is not supported on Wayland"; - return false; - } - - if (!m_displayInterface || !m_displayInterface->isValid()) { + if (!m_isWayland && (!m_displayInterface || !m_displayInterface->isValid())) { qWarning() << "Display interface not available"; return false; } @@ -115,28 +110,40 @@ bool DisplayController::changeBrightness(bool raised) auto *powerConfig = DConfig::create("org.deepin.dde.daemon", "org.deepin.dde.daemon.power", "", this); if (!powerConfig->isValid()) { qWarning() << "daemon power config is not valid"; + powerConfig->deleteLater(); return false; } - // Check if ambient light auto-adjustment is enabled, disable it first if so - QVariant autoAdjustValue = powerConfig->value("ambientLightAdjustBrightness"); - if (autoAdjustValue.toBool()) { - powerConfig->setValue("ambientLightAdjustBrightness", false); - qDebug() << "Disabled ambient light auto brightness adjustment"; + const bool autoAdjustEnabled = + powerConfig->value("ambientLightAdjustBrightness").toBool(); + + bool success = false; + if (m_isWayland) { + TreelandBrightnessController controller; + success = controller.changeBrightness(raised); + } else { + // Call Display1's ChangeBrightness method directly + QDBusReply reply = m_displayInterface->call("ChangeBrightness", raised); + if (!reply.isValid()) { + qWarning() << "Failed to change brightness:" << reply.error().message(); + } else { + success = true; + } } - // Call Display1's ChangeBrightness method directly - QDBusReply reply = m_displayInterface->call("ChangeBrightness", raised); - if (!reply.isValid()) { - qWarning() << "Failed to change brightness:" << reply.error().message(); + if (!success) { + powerConfig->deleteLater(); return false; } - - qDebug() << "Changed brightness:" << (raised ? "up" : "down"); - showOSD(raised ? "BrightnessUp" : "BrightnessDown"); + if (autoAdjustEnabled) { + powerConfig->setValue("ambientLightAdjustBrightness", false); + qDebug() << "Disabled ambient light auto brightness adjustment"; + } powerConfig->deleteLater(); - + + qDebug() << "Changed brightness:" << (raised ? "up" : "down"); + showOSD(raised ? "BrightnessUp" : "BrightnessDown"); return true; } diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.cpp b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.cpp new file mode 100644 index 0000000..e869518 --- /dev/null +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.cpp @@ -0,0 +1,313 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "treelandbrightnesscontroller.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +constexpr double BrightnessStep = 5.0; +constexpr int BrightnessCommitTimeoutMs = 1000; +constexpr int BrightnessLockTimeoutMs = 1500; + +} // namespace + +namespace TreelandBrightnessPrivate { + +TreelandColorControl::TreelandColorControl(struct ::treeland_output_color_control_v1 *object) + : QtWayland::treeland_output_color_control_v1(object) +{ +} + +TreelandColorControl::~TreelandColorControl() +{ + if (isInitialized()) { + destroy(); + } +} + +bool TreelandColorControl::hasBrightness() const +{ + return m_brightness >= 0.0; +} + +double TreelandColorControl::brightness() const +{ + return m_brightness; +} + +double TreelandColorControl::changeBrightness(bool raised) +{ + const double delta = raised ? BrightnessStep : -BrightnessStep; + const double target = std::clamp(m_brightness + delta, 0.0, 100.0); + + set_brightness(wl_fixed_from_double(target)); + commit(); + return target; +} + +void TreelandColorControl::setResultHandler(std::function handler) +{ + m_resultHandler = std::move(handler); +} + +void TreelandColorControl::treeland_output_color_control_v1_result(uint32_t success) +{ + if (m_resultHandler) { + m_resultHandler(success == 1); + } +} + +void TreelandColorControl::treeland_output_color_control_v1_color_temperature(uint32_t) +{ +} + +void TreelandColorControl::treeland_output_color_control_v1_brightness(wl_fixed_t brightness) +{ + m_brightness = wl_fixed_to_double(brightness); +} + +TreelandOutputManager::TreelandOutputManager(struct wl_registry *registry, uint32_t id, int version) + : QtWayland::treeland_output_manager_v1(registry, id, version) +{ +} + +TreelandOutputManager::~TreelandOutputManager() +{ + if (isInitialized()) { + destroy(); + } +} + +std::unique_ptr TreelandOutputManager::createColorControl(struct wl_output *output) +{ + if (!isInitialized() || !output) { + return nullptr; + } + + auto *control = get_color_control(output); + if (!control) { + return nullptr; + } + + return std::make_unique(control); +} + +WaylandBrightnessSession::~WaylandBrightnessSession() +{ + for (auto &output : m_outputs) { + output.colorControl.reset(); + } + m_manager.reset(); + + for (auto &output : m_outputs) { + if (output.wlOutput) { + wl_output_destroy(output.wlOutput); + } + } + m_outputs.clear(); + + if (m_registry) { + wl_registry_destroy(m_registry); + } +} + +bool WaylandBrightnessSession::changeBrightness(bool raised) +{ + auto *waylandApp = qGuiApp ? qGuiApp->nativeInterface() : nullptr; + if (!waylandApp) { + qWarning() << "TreelandBrightnessController: no QWaylandApplication"; + return false; + } + + m_display = waylandApp->display(); + if (!m_display) { + qWarning() << "TreelandBrightnessController: no wl_display"; + return false; + } + + m_registry = wl_display_get_registry(m_display); + if (!m_registry) { + qWarning() << "TreelandBrightnessController: failed to get wl_registry"; + return false; + } + + static const struct wl_registry_listener registryListener = { + handleGlobal, + handleGlobalRemove, + }; + wl_registry_add_listener(m_registry, ®istryListener, this); + + if (wl_display_roundtrip(m_display) < 0) { + qWarning() << "TreelandBrightnessController: failed to discover Wayland globals"; + return false; + } + + if (!m_manager) { + qWarning() << "TreelandBrightnessController: output manager v2 is unavailable"; + return false; + } + + for (auto &output : m_outputs) { + output.colorControl = m_manager->createColorControl(output.wlOutput); + } + + // The protocol sends the current brightness once after each color + // control is created. A roundtrip makes those values available before + // calculating the shortcut's relative adjustment. + if (wl_display_roundtrip(m_display) < 0) { + qWarning() << "TreelandBrightnessController: failed to query current brightness"; + return false; + } + + int requestedCount = 0; + for (auto &output : m_outputs) { + if (!output.colorControl || !output.colorControl->hasBrightness()) { + qWarning() << "TreelandBrightnessController: brightness unavailable for output" + << output.registryName; + continue; + } + + const double current = output.colorControl->brightness(); + const double target = output.colorControl->changeBrightness(raised); + qDebug() << "TreelandBrightnessController: output" << output.registryName + << "brightness" << current << "->" << target; + ++requestedCount; + } + + if (requestedCount == 0) { + qWarning() << "TreelandBrightnessController: no output supports brightness control"; + return false; + } + + QEventLoop eventLoop; + QTimer timeout; + timeout.setSingleShot(true); + + int resultCount = 0; + int successCount = 0; + for (auto &output : m_outputs) { + if (!output.colorControl || !output.colorControl->hasBrightness()) { + continue; + } + + output.colorControl->setResultHandler([&eventLoop, &resultCount, &successCount, requestedCount](bool success) { + ++resultCount; + if (success) { + ++successCount; + } + if (resultCount >= requestedCount) { + eventLoop.quit(); + } + }); + } + + const auto clearResultHandlers = [this] { + for (auto &output : m_outputs) { + if (output.colorControl) { + output.colorControl->setResultHandler({}); + } + } + }; + + QObject::connect(&timeout, &QTimer::timeout, &eventLoop, &QEventLoop::quit); + + // The compositor may finish applying output color asynchronously after a + // wl_display sync callback. Flush explicitly, then keep processing events + // until all result callbacks arrive or the timeout expires. + // EAGAIN leaves requests buffered; Qt Wayland retries the flush while the + // event loop below processes display events. + if (wl_display_flush(m_display) < 0 && errno != EAGAIN) { + qWarning() << "TreelandBrightnessController: failed to flush brightness requests"; + clearResultHandlers(); + return false; + } + timeout.start(BrightnessCommitTimeoutMs); + eventLoop.exec(); + + clearResultHandlers(); + + if (resultCount < requestedCount) { + qWarning() << "TreelandBrightnessController: timed out waiting for brightness results:" + << resultCount << "/" << requestedCount; + } + + if (successCount == 0) { + qWarning() << "TreelandBrightnessController: compositor rejected brightness changes"; + return false; + } + + return true; +} + +void WaylandBrightnessSession::handleGlobal(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version) +{ + auto *session = static_cast(data); + + if (std::strcmp(interface, wl_output_interface.name) == 0) { + auto *output = static_cast( + wl_registry_bind(registry, name, &wl_output_interface, 1)); + if (output) { + session->m_outputs.push_back({name, output, nullptr}); + } + return; + } + + if (std::strcmp(interface, treeland_output_manager_v1_interface.name) == 0 + && version >= 2 && !session->m_manager) { + session->m_manager = std::make_unique( + registry, name, static_cast(std::min(version, 2U))); + } +} + +void WaylandBrightnessSession::handleGlobalRemove(void *data, struct wl_registry *, uint32_t name) +{ + auto *session = static_cast(data); + auto it = std::find_if(session->m_outputs.begin(), session->m_outputs.end(), + [name](const Output &output) { + return output.registryName == name; + }); + if (it == session->m_outputs.end()) { + return; + } + + it->colorControl.reset(); + if (it->wlOutput) { + wl_output_destroy(it->wlOutput); + } + session->m_outputs.erase(it); +} + +} // namespace TreelandBrightnessPrivate + +bool TreelandBrightnessController::changeBrightness(bool raised) +{ + const QString runtimeDir = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + if (runtimeDir.isEmpty()) { + qWarning() << "TreelandBrightnessController: user runtime directory is unavailable"; + return false; + } + + // Serialize cross-process brightness read-modify-write operations. + QLockFile lock(QDir(runtimeDir).filePath(QStringLiteral("dde-shortcut-tool-display-brightness.lock"))); + if (!lock.tryLock(BrightnessLockTimeoutMs)) { + qWarning() << "TreelandBrightnessController: timed out waiting for another brightness operation"; + return false; + } + + TreelandBrightnessPrivate::WaylandBrightnessSession session; + return session.changeBrightness(raised); +} diff --git a/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.h b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.h new file mode 100644 index 0000000..a20fc99 --- /dev/null +++ b/src/plugin-qt/shortcut/tools/dde-shortcut-tool/treelandbrightnesscontroller.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef TREELANDBRIGHTNESSCONTROLLER_H +#define TREELANDBRIGHTNESSCONTROLLER_H + +#include "qwayland-treeland-output-manager-v1.h" + +#include +#include +#include + +struct wl_display; +struct wl_output; +struct wl_registry; + +class TreelandBrightnessController; + +namespace TreelandBrightnessPrivate { + +class TreelandColorControl : public QtWayland::treeland_output_color_control_v1 +{ +public: + explicit TreelandColorControl(struct ::treeland_output_color_control_v1 *object); + ~TreelandColorControl() override; + + bool hasBrightness() const; + double brightness() const; + double changeBrightness(bool raised); + void setResultHandler(std::function handler); + +protected: + void treeland_output_color_control_v1_result(uint32_t success) override; + void treeland_output_color_control_v1_color_temperature(uint32_t temperature) override; + void treeland_output_color_control_v1_brightness(wl_fixed_t brightness) override; + +private: + double m_brightness = -1.0; + std::function m_resultHandler; +}; + +class TreelandOutputManager : public QtWayland::treeland_output_manager_v1 +{ +public: + TreelandOutputManager(struct wl_registry *registry, uint32_t id, int version); + ~TreelandOutputManager() override; + + std::unique_ptr createColorControl(struct wl_output *output); +}; + +class WaylandBrightnessSession +{ + friend class ::TreelandBrightnessController; + +private: + WaylandBrightnessSession() = default; + ~WaylandBrightnessSession(); + + bool changeBrightness(bool raised); + + struct Output { + uint32_t registryName = 0; + struct wl_output *wlOutput = nullptr; + std::unique_ptr colorControl; + }; + + static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, + const char *interface, uint32_t version); + static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name); + + struct wl_display *m_display = nullptr; + struct wl_registry *m_registry = nullptr; + std::unique_ptr m_manager; + std::vector m_outputs; +}; + +} // namespace TreelandBrightnessPrivate + +class TreelandBrightnessController +{ +public: + bool changeBrightness(bool raised); +}; + +#endif // TREELANDBRIGHTNESSCONTROLLER_H