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.
This commit is contained in:
Xaver Hugl 2024-06-06 00:45:54 +02:00
parent 7ad1303795
commit faba2b6286
15 changed files with 365 additions and 1 deletions

View file

@ -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

View file

@ -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<OutputChangeSet> &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<OutputChangeSet> &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();

View file

@ -73,6 +73,7 @@ private:
ColorDescription createColorDescription(const std::shared_ptr<OutputChangeSet> &props) const;
Capabilities computeCapabilities() const;
void updateInformation();
void setBrightnessDevice(BrightnessDevice *device) override;
QList<std::shared_ptr<OutputMode>> getModes() const;

View file

@ -0,0 +1,36 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
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;
}
}

View file

@ -0,0 +1,35 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QByteArray>
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;
};
}

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,111 @@
/*
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
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<BrightnessDevice *> 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<int64_t>(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);
}
}
}
}

View file

@ -0,0 +1,70 @@
/*
SPDX-FileCopyrightText: 2024 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "core/brightnessdevice.h"
#include <QByteArray>
#include <QObject>
#include <QPointer>
#include <qwayland-server-kde-external-brightness-v1.h>
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<BrightnessDevice *> 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<BrightnessDevice *> 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<ExternalBrightnessV1> m_global;
uint32_t m_maxBrightness = 1;
bool m_internal = false;
QByteArray m_edidBeginning;
bool m_done = false;
};
}

View file

@ -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;
}

View file

@ -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()) {

View file

@ -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)
};

View file

@ -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<QList<Output *>> &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<Output *> 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);

View file

@ -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<QList<Output *>> &outputOrder = std::nullopt);
void createDpmsFilter();
void maybeDestroyDpmsFilter();
void assignBrightnessDevices();
bool breaksShowingDesktop(Window *window) const;