From faba2b62860b80cc5f2489d84cf4e0b52c3f01a4 Mon Sep 17 00:00:00 2001 From: Xaver Hugl Date: Thu, 6 Jun 2024 00:45:54 +0200 Subject: [PATCH] wayland: add support for controlling brightness devices exposed by powerdevil This way, KWin can set the brightness on internal panels or external monitors with DDC/CI support, without being exposed to the mess that is actually directly setting the brightness. This also adds a capability flag for brightness control to the output management protocol. Powerdevil will expose a brightness slider for each output and change the brightness setting of the output accordingly. KWin in turn changes the brightness levels of the actual brightness device, or of a multiplier in compositing accordingly. --- src/CMakeLists.txt | 1 + src/backends/drm/drm_output.cpp | 26 ++++++ src/backends/drm/drm_output.h | 1 + src/core/brightnessdevice.cpp | 36 +++++++++ src/core/brightnessdevice.h | 35 ++++++++ src/core/output.cpp | 23 ++++++ src/core/output.h | 6 ++ src/wayland/CMakeLists.txt | 8 ++ src/wayland/externalbrightness_v1.cpp | 111 ++++++++++++++++++++++++++ src/wayland/externalbrightness_v1.h | 70 ++++++++++++++++ src/wayland/outputdevice_v2.cpp | 5 +- src/wayland_server.cpp | 8 ++ src/wayland_server.h | 3 + src/workspace.cpp | 31 +++++++ src/workspace.h | 2 + 15 files changed, 365 insertions(+), 1 deletion(-) create mode 100644 src/core/brightnessdevice.cpp create mode 100644 src/core/brightnessdevice.h create mode 100644 src/wayland/externalbrightness_v1.cpp create mode 100644 src/wayland/externalbrightness_v1.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 66325254d7..676595b22c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,6 +42,7 @@ target_sources(kwin PRIVATE colors/colormanager.cpp compositor.cpp compositor_wayland.cpp + core/brightnessdevice.cpp core/colorlut.cpp core/colorlut3d.cpp core/colorpipeline.cpp diff --git a/src/backends/drm/drm_output.cpp b/src/backends/drm/drm_output.cpp index 38e70968b2..13de3a70c5 100644 --- a/src/backends/drm/drm_output.cpp +++ b/src/backends/drm/drm_output.cpp @@ -13,6 +13,7 @@ #include "drm_gpu.h" #include "drm_pipeline.h" +#include "core/brightnessdevice.h" #include "core/colortransformation.h" #include "core/iccprofile.h" #include "core/outputconfiguration.h" @@ -260,6 +261,9 @@ Output::Capabilities DrmOutput::computeCapabilities() const // TODO only set this if an orientation sensor is available? capabilities |= Capability::AutoRotation; } + if (m_brightnessDevice || m_state.highDynamicRange) { + capabilities |= Capability::BrightnessControl; + } return capabilities; } @@ -413,6 +417,15 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props next.desiredModeRefreshRate = props->desiredModeRefreshRate.value_or(m_state.desiredModeRefreshRate); setState(next); + updateInformation(); + if (m_brightnessDevice) { + if (m_state.highDynamicRange) { + m_brightnessDevice->setBrightness(1); + } else { + m_brightnessDevice->setBrightness(m_state.brightness); + } + } + if (!isEnabled() && m_pipeline->needsModeset()) { m_gpu->maybeModeset(nullptr); } @@ -426,6 +439,19 @@ void DrmOutput::applyQueuedChanges(const std::shared_ptr &props Q_EMIT changed(); } +void DrmOutput::setBrightnessDevice(BrightnessDevice *device) +{ + Output::setBrightnessDevice(device); + if (device) { + if (m_state.highDynamicRange) { + device->setBrightness(1); + } else { + device->setBrightness(m_state.brightness); + } + } + updateInformation(); +} + void DrmOutput::revertQueuedChanges() { m_pipeline->revertPendingChanges(); diff --git a/src/backends/drm/drm_output.h b/src/backends/drm/drm_output.h index daf9f6b3c1..78f7b5afc0 100644 --- a/src/backends/drm/drm_output.h +++ b/src/backends/drm/drm_output.h @@ -73,6 +73,7 @@ private: ColorDescription createColorDescription(const std::shared_ptr &props) const; Capabilities computeCapabilities() const; void updateInformation(); + void setBrightnessDevice(BrightnessDevice *device) override; QList> getModes() const; diff --git a/src/core/brightnessdevice.cpp b/src/core/brightnessdevice.cpp new file mode 100644 index 0000000000..8e641d10a3 --- /dev/null +++ b/src/core/brightnessdevice.cpp @@ -0,0 +1,36 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "brightnessdevice.h" +#include "output.h" + +namespace KWin +{ + +BrightnessDevice::BrightnessDevice() +{ +} + +BrightnessDevice::~BrightnessDevice() +{ + if (m_output) { + m_output->setBrightnessDevice(nullptr); + } +} + +void BrightnessDevice::setOutput(Output *output) +{ + m_output = output; +} + +Output *BrightnessDevice::output() const +{ + return m_output; +} + +} diff --git a/src/core/brightnessdevice.h b/src/core/brightnessdevice.h new file mode 100644 index 0000000000..ab317a9491 --- /dev/null +++ b/src/core/brightnessdevice.h @@ -0,0 +1,35 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include + +namespace KWin +{ + +class Output; + +class BrightnessDevice +{ +public: + explicit BrightnessDevice(); + virtual ~BrightnessDevice(); + + void setOutput(Output *output); + virtual void setBrightness(double brightness) = 0; + + Output *output() const; + virtual bool isInternal() const = 0; + virtual QByteArray edidBeginning() const = 0; + +private: + Output *m_output = nullptr; +}; + +} diff --git a/src/core/output.cpp b/src/core/output.cpp index f944480704..58b7c03ac9 100644 --- a/src/core/output.cpp +++ b/src/core/output.cpp @@ -8,6 +8,7 @@ */ #include "output.h" +#include "brightnessdevice.h" #include "iccprofile.h" #include "outputconfiguration.h" @@ -340,6 +341,9 @@ Output::Output(QObject *parent) Output::~Output() { + if (m_brightnessDevice) { + m_brightnessDevice->setOutput(nullptr); + } } void Output::ref() @@ -785,6 +789,25 @@ double Output::brightness() const { return m_state.brightness; } + +BrightnessDevice *Output::brightnessDevice() const +{ + return m_brightnessDevice; +} + +void Output::setBrightnessDevice(BrightnessDevice *device) +{ + if (m_brightnessDevice == device) { + return; + } + if (m_brightnessDevice) { + m_brightnessDevice->setOutput(nullptr); + } + m_brightnessDevice = device; + if (device) { + device->setOutput(this); + } +} } // namespace KWin #include "moc_output.cpp" diff --git a/src/core/output.h b/src/core/output.h index eb53ba836e..ab6bebdcc0 100644 --- a/src/core/output.h +++ b/src/core/output.h @@ -29,6 +29,7 @@ class OutputConfiguration; class ColorTransformation; class IccProfile; class OutputChangeSet; +class BrightnessDevice; /** * The OutputTransform type is used to describe the transform applied to the output content. @@ -157,6 +158,7 @@ public: AutoRotation = 1 << 6, IccProfile = 1 << 7, Tearing = 1 << 8, + BrightnessControl = 1 << 9, }; Q_DECLARE_FLAGS(Capabilities, Capability) @@ -364,6 +366,9 @@ public: const ColorDescription &colorDescription() const; + BrightnessDevice *brightnessDevice() const; + virtual void setBrightnessDevice(BrightnessDevice *device); + Q_SIGNALS: /** * This signal is emitted when the geometry of this output has changed. @@ -489,6 +494,7 @@ protected: Information m_information; QUuid m_uuid; int m_refCount = 1; + BrightnessDevice *m_brightnessDevice = nullptr; }; inline QRect Output::rect() const diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 0bd0ef1564..5052183165 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -252,6 +252,10 @@ ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml PROTOCOL ${WaylandProtocols_DATADIR}/staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml BASENAME linux-drm-syncobj-v1 ) +ecm_add_qtwayland_server_protocol_kde(WaylandProtocols_xml + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-external-brightness-v1.xml + BASENAME kde-external-brightness-v1 +) target_sources(kwin PRIVATE abstract_data_source.cpp @@ -275,6 +279,7 @@ target_sources(kwin PRIVATE dpms.cpp drmclientbuffer.cpp drmlease_v1.cpp + externalbrightness_v1.cpp filtered_display.cpp fractionalscale_v1.cpp frog_colormanagement_v1.cpp @@ -357,6 +362,7 @@ install(FILES display.h dpms.h drmlease_v1.h + externalbrightness_v1.h fractionalscale_v1.h frog_colormanagement_v1.h idle.h @@ -415,11 +421,13 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-content-type-v1.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-frog-color-management-v1.h + ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-kde-external-brightness-v1.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-linux-drm-syncobj-v1.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-presentation-time.h ${CMAKE_CURRENT_BINARY_DIR}/qwayland-server-xx-color-management-v2.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-content-type-v1-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-frog-color-management-v1-server-protocol.h + ${CMAKE_CURRENT_BINARY_DIR}/wayland-kde-external-brightness-v1-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-linux-drm-syncobj-v1-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-presentation-time-server-protocol.h ${CMAKE_CURRENT_BINARY_DIR}/wayland-xx-color-management-v2-server-protocol.h diff --git a/src/wayland/externalbrightness_v1.cpp b/src/wayland/externalbrightness_v1.cpp new file mode 100644 index 0000000000..0cf78f38bf --- /dev/null +++ b/src/wayland/externalbrightness_v1.cpp @@ -0,0 +1,111 @@ +/* + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ +#include "externalbrightness_v1.h" +#include "core/output.h" +#include "display.h" + +namespace KWin +{ + +static constexpr uint32_t s_version = 1; + +ExternalBrightnessV1::ExternalBrightnessV1(Display *display, QObject *parent) + : QObject(parent) + , QtWaylandServer::kde_external_brightness_v1(*display, s_version) +{ +} + +void ExternalBrightnessV1::kde_external_brightness_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void ExternalBrightnessV1::kde_external_brightness_v1_create_brightness_control(Resource *resource, uint32_t id) +{ + new ExternalBrightnessDeviceV1(this, resource->client(), id, resource->version()); +} + +void ExternalBrightnessV1::addDevice(ExternalBrightnessDeviceV1 *device) +{ + m_devices.push_back(device); + Q_EMIT devicesChanged(); +} + +void ExternalBrightnessV1::removeDevice(ExternalBrightnessDeviceV1 *device) +{ + m_devices.removeOne(device); + Q_EMIT devicesChanged(); +} + +QList ExternalBrightnessV1::devices() const +{ + return m_devices; +} + +ExternalBrightnessDeviceV1::ExternalBrightnessDeviceV1(ExternalBrightnessV1 *global, wl_client *client, uint32_t version, uint32_t id) + : QtWaylandServer::kde_external_brightness_device_v1(client, version, id) + , m_global(global) +{ +} + +ExternalBrightnessDeviceV1::~ExternalBrightnessDeviceV1() +{ + if (m_global) { + m_global->removeDevice(this); + } +} + +void ExternalBrightnessDeviceV1::setBrightness(double brightness) +{ + const uint32_t val = std::clamp(std::round(brightness * m_maxBrightness), 0, m_maxBrightness); + send_requested_brightness(val); +} + +bool ExternalBrightnessDeviceV1::isInternal() const +{ + return m_internal; +} + +QByteArray ExternalBrightnessDeviceV1::edidBeginning() const +{ + return m_edidBeginning; +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_destroy_resource(Resource *resource) +{ + delete this; +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_destroy(Resource *resource) +{ + wl_resource_destroy(resource->handle); +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_set_internal(Resource *resource, uint32_t internal) +{ + m_internal = internal == 1; +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_set_edid(Resource *resource, const QString &edid) +{ + m_edidBeginning = QByteArray::fromBase64(edid.toUtf8()); +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_set_max_brightness(Resource *resource, uint32_t value) +{ + m_maxBrightness = value; +} + +void ExternalBrightnessDeviceV1::kde_external_brightness_device_v1_commit(Resource *resource) +{ + if (!m_done) { + m_done = true; + if (m_global) { + m_global->addDevice(this); + } + } +} +} diff --git a/src/wayland/externalbrightness_v1.h b/src/wayland/externalbrightness_v1.h new file mode 100644 index 0000000000..df5a307777 --- /dev/null +++ b/src/wayland/externalbrightness_v1.h @@ -0,0 +1,70 @@ +/* + SPDX-FileCopyrightText: 2024 Xaver Hugl + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL + */ +#pragma once + +#include "core/brightnessdevice.h" + +#include +#include +#include +#include + +namespace KWin +{ + +class Display; +class ExternalBrightnessDeviceV1; +class Output; + +class ExternalBrightnessV1 : public QObject, private QtWaylandServer::kde_external_brightness_v1 +{ + Q_OBJECT +public: + explicit ExternalBrightnessV1(Display *display, QObject *parent); + + QList devices() const; + +Q_SIGNALS: + void devicesChanged(); + +private: + void kde_external_brightness_v1_destroy(Resource *resource) override; + void kde_external_brightness_v1_create_brightness_control(Resource *resource, uint32_t id) override; + + void addDevice(ExternalBrightnessDeviceV1 *device); + void removeDevice(ExternalBrightnessDeviceV1 *device); + + friend class ExternalBrightnessDeviceV1; + QList m_devices; +}; + +class ExternalBrightnessDeviceV1 : private QtWaylandServer::kde_external_brightness_device_v1, public BrightnessDevice +{ +public: + explicit ExternalBrightnessDeviceV1(ExternalBrightnessV1 *global, wl_client *client, uint32_t version, uint32_t id); + ~ExternalBrightnessDeviceV1() override; + + void setBrightness(double brightness) override; + + bool isInternal() const override; + QByteArray edidBeginning() const override; + +private: + void kde_external_brightness_device_v1_destroy_resource(Resource *resource) override; + void kde_external_brightness_device_v1_destroy(Resource *resource) override; + void kde_external_brightness_device_v1_set_internal(Resource *resource, uint32_t internal) override; + void kde_external_brightness_device_v1_set_edid(Resource *resource, const QString &string) override; + void kde_external_brightness_device_v1_set_max_brightness(Resource *resource, uint32_t value) override; + void kde_external_brightness_device_v1_commit(Resource *resource) override; + + QPointer m_global; + uint32_t m_maxBrightness = 1; + bool m_internal = false; + QByteArray m_edidBeginning; + bool m_done = false; +}; + +} diff --git a/src/wayland/outputdevice_v2.cpp b/src/wayland/outputdevice_v2.cpp index f38d7a8335..8375a67847 100644 --- a/src/wayland/outputdevice_v2.cpp +++ b/src/wayland/outputdevice_v2.cpp @@ -22,7 +22,7 @@ namespace KWin { -static const quint32 s_version = 8; +static const quint32 s_version = 9; static QtWaylandServer::kde_output_device_v2::transform kwinTransformToOutputDeviceTransform(OutputTransform transform) { @@ -58,6 +58,9 @@ static uint32_t kwinCapabilitiesToOutputDeviceCapabilities(Output::Capabilities if (caps & Output::Capability::IccProfile) { ret |= QtWaylandServer::kde_output_device_v2::capability_icc_profile; } + if (caps & Output::Capability::BrightnessControl) { + ret |= QtWaylandServer::kde_output_device_v2::capability_brightness; + } return ret; } diff --git a/src/wayland_server.cpp b/src/wayland_server.cpp index db6bd50fd7..c6479f4abb 100644 --- a/src/wayland_server.cpp +++ b/src/wayland_server.cpp @@ -34,6 +34,7 @@ #include "wayland/dpms.h" #include "wayland/drmclientbuffer.h" #include "wayland/drmlease_v1.h" +#include "wayland/externalbrightness_v1.h" #include "wayland/filtered_display.h" #include "wayland/fractionalscale_v1.h" #include "wayland/frog_colormanagement_v1.h" @@ -523,6 +524,8 @@ bool WaylandServer::init() } }); + m_externalBrightness = new ExternalBrightnessV1(m_display, m_display); + return true; } @@ -840,6 +843,11 @@ LinuxDrmSyncObjV1Interface *WaylandServer::linuxSyncObj() const return m_linuxDrmSyncObj; } +ExternalBrightnessV1 *WaylandServer::externalBrightness() const +{ + return m_externalBrightness; +} + void WaylandServer::setRenderBackend(RenderBackend *backend) { if (backend->drmDevice()->supportsSyncObjTimelines()) { diff --git a/src/wayland_server.h b/src/wayland_server.h index 0cf8978939..8512a9f34d 100644 --- a/src/wayland_server.h +++ b/src/wayland_server.h @@ -51,6 +51,7 @@ class TearingControlManagerV1Interface; class XwaylandShellV1Interface; class OutputOrderV1Interface; class XdgDialogWmV1Interface; +class ExternalBrightnessV1; class Window; class Output; @@ -217,6 +218,7 @@ public: } LinuxDrmSyncObjV1Interface *linuxSyncObj() const; + ExternalBrightnessV1 *externalBrightness() const; void setRenderBackend(RenderBackend *backend); @@ -288,6 +290,7 @@ private: OutputOrderV1Interface *m_outputOrder = nullptr; XXColorManagerV2 *m_xxColorManager = nullptr; XdgDialogWmV1Interface *m_xdgDialogWm = nullptr; + ExternalBrightnessV1 *m_externalBrightness = nullptr; KWIN_SINGLETON(WaylandServer) }; diff --git a/src/workspace.cpp b/src/workspace.cpp index c33bff92ed..9f003ffbcf 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -58,9 +58,11 @@ #include "utils/orientationsensor.h" #include "virtualdesktops.h" #include "was_user_interaction_x11_filter.h" +#include "wayland/externalbrightness_v1.h" #include "wayland_server.h" #if KWIN_BUILD_X11 #include "atoms.h" +#include "core/brightnessdevice.h" #include "group.h" #include "netinfo.h" #include "utils/xcbutils.h" @@ -260,6 +262,10 @@ void Workspace::init() connect(this, &Workspace::windowAdded, m_placementTracker.get(), &PlacementTracker::add); connect(this, &Workspace::windowRemoved, m_placementTracker.get(), &PlacementTracker::remove); m_placementTracker->init(getPlacementTrackerHash()); + + if (waylandServer()) { + connect(waylandServer()->externalBrightness(), &ExternalBrightnessV1::devicesChanged, this, &Workspace::assignBrightnessDevices); + } } QString Workspace::getPlacementTrackerHash() @@ -1327,6 +1333,10 @@ void Workspace::updateOutputs(const std::optional> &outputOrder) m_placementTracker->uninhibit(); m_placementTracker->restore(getPlacementTrackerHash()); + if (!added.isEmpty() || !removed.isEmpty()) { + assignBrightnessDevices(); + } + for (Output *output : removed) { output->unref(); } @@ -1352,6 +1362,27 @@ void Workspace::maybeDestroyDpmsFilter() } } +void Workspace::assignBrightnessDevices() +{ + QList candidates = m_outputs; + const auto devices = waylandServer()->externalBrightness()->devices(); + for (BrightnessDevice *device : devices) { + // assign the device to the most fitting output + for (auto it = candidates.begin(); it != candidates.end(); it++) { + Output *output = *it; + if (output->isInternal() != device->isInternal()) { + continue; + } + if (!output->isInternal() && (!output->edid().isValid() || device->edidBeginning().isEmpty() || !output->edid().raw().startsWith(device->edidBeginning()))) { + continue; + } + output->setBrightnessDevice(device); + candidates.erase(it); + break; + } + } +} + void Workspace::slotDesktopAdded(VirtualDesktop *desktop) { m_focusChain->addDesktop(desktop); diff --git a/src/workspace.h b/src/workspace.h index e29d55b932..e7ae60e5c1 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -82,6 +82,7 @@ class OutputConfigurationStore; class LidSwitchTracker; class DpmsInputEventFilter; class OrientationSensor; +class BrightnessDevice; class KWIN_EXPORT Workspace : public QObject { @@ -643,6 +644,7 @@ private: void updateOutputs(const std::optional> &outputOrder = std::nullopt); void createDpmsFilter(); void maybeDestroyDpmsFilter(); + void assignBrightnessDevices(); bool breaksShowingDesktop(Window *window) const;