handle laptop lid closing in KWin

Instead of KScreen turning the display off, do that in KWin directly
This commit is contained in:
Xaver Hugl 2023-05-08 10:48:30 +02:00
parent 6e9d5c2cc3
commit f60bcfb646
17 changed files with 167 additions and 20 deletions

View file

@ -230,6 +230,7 @@ void Test::setOutputConfig(const QVector<OutputInfo> &infos)
return VirtualBackend::OutputInfo{
.geometry = info.geometry,
.scale = info.scale,
.internal = info.internal,
};
});
static_cast<VirtualBackend *>(kwinApp()->outputBackend())->setVirtualOutputs(converted);

View file

@ -518,6 +518,7 @@ public:
void setPointer(bool set);
void setKeyboard(bool set);
void setTouch(bool set);
void setLidSwitch(bool set);
void setName(const QString &name);
QString sysName() const override;
@ -544,6 +545,7 @@ private:
bool m_pointer = false;
bool m_keyboard = false;
bool m_touch = false;
bool m_lidSwitch = false;
};
void keyboardKeyPressed(quint32 key, quint32 time);
@ -791,6 +793,7 @@ struct OutputInfo
{
QRect geometry;
double scale = 1;
bool internal = false;
};
void setOutputConfig(const QVector<QRect> &geometries);
void setOutputConfig(const QVector<OutputInfo> &infos);

View file

@ -16,6 +16,8 @@
#include <KWayland/Client/surface.h>
using namespace std::chrono_literals;
namespace KWin
{
@ -43,6 +45,7 @@ private Q_SLOTS:
void testMaximizeStateRestoredAfterEnablingOutput();
void testWindowNotRestoredAfterMovingWindowAndEnablingOutput();
void testLaptopLidClosed();
};
void OutputChangesTest::initTestCase()
@ -592,6 +595,45 @@ void OutputChangesTest::testMaximizeStateRestoredAfterEnablingOutput()
QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50));
}
void OutputChangesTest::testLaptopLidClosed()
{
Test::setOutputConfig({
Test::OutputInfo{
.geometry = QRect(0, 0, 1280, 1024),
.internal = true,
},
Test::OutputInfo{
.geometry = QRect(1280, 0, 1280, 1024),
.internal = false,
},
});
const auto outputs = kwinApp()->outputBackend()->outputs();
const auto internal = outputs.front();
QVERIFY(internal->isInternal());
const auto external = outputs.back();
QVERIFY(!external->isInternal());
auto lidSwitch = std::make_unique<Test::VirtualInputDevice>();
lidSwitch->setLidSwitch(true);
lidSwitch->setName("virtual lid switch");
input()->addInputDevice(lidSwitch.get());
auto timestamp = 1ms;
Q_EMIT lidSwitch->switchToggledOff(timestamp++, lidSwitch.get());
QVERIFY(internal->isEnabled());
QVERIFY(external->isEnabled());
Q_EMIT lidSwitch->switchToggledOn(timestamp++, lidSwitch.get());
QVERIFY(!internal->isEnabled());
QVERIFY(external->isEnabled());
Q_EMIT lidSwitch->switchToggledOff(timestamp++, lidSwitch.get());
QVERIFY(internal->isEnabled());
QVERIFY(external->isEnabled());
input()->removeInputDevice(lidSwitch.get());
}
} // namespace KWin
WAYLANDTEST_MAIN(KWin::OutputChangesTest)

View file

@ -1403,6 +1403,11 @@ void VirtualInputDevice::setTouch(bool set)
m_touch = set;
}
void VirtualInputDevice::setLidSwitch(bool set)
{
m_lidSwitch = set;
}
void VirtualInputDevice::setName(const QString &name)
{
m_name = name;
@ -1478,7 +1483,7 @@ bool VirtualInputDevice::isTabletModeSwitch() const
bool VirtualInputDevice::isLidSwitch() const
{
return false;
return m_lidSwitch;
}
void keyboardKeyPressed(quint32 key, quint32 time)

View file

@ -99,6 +99,7 @@ target_sources(kwin PRIVATE
layers.cpp
layershellv1integration.cpp
layershellv1window.cpp
lidswitchtracker.cpp
main.cpp
modifier_only_shortcuts.cpp
mousebuttons.cpp
@ -117,8 +118,8 @@ target_sources(kwin PRIVATE
pluginmanager.cpp
pointer_input.cpp
popup_input_filter.cpp
rootinfo_filter.cpp
resources.qrc
rootinfo_filter.cpp
rulebooksettings.cpp
rules.cpp
scene/cursoritem.cpp

View file

@ -108,7 +108,7 @@ Outputs VirtualBackend::outputs() const
VirtualOutput *VirtualBackend::createOutput(const OutputInfo &info)
{
VirtualOutput *output = new VirtualOutput(this);
VirtualOutput *output = new VirtualOutput(this, info.internal);
output->init(info.geometry.topLeft(), info.geometry.size() * info.scale, info.scale);
m_outputs.append(output);
Q_EMIT outputAdded(output);

View file

@ -37,6 +37,7 @@ public:
{
QRect geometry;
double scale = 1;
bool internal = false;
};
Output *addOutput(const OutputInfo &info);
void setVirtualOutputs(const QVector<OutputInfo> &infos);

View file

@ -15,7 +15,7 @@
namespace KWin
{
VirtualOutput::VirtualOutput(VirtualBackend *parent)
VirtualOutput::VirtualOutput(VirtualBackend *parent, bool internal)
: Output(parent)
, m_backend(parent)
, m_renderLoop(std::make_unique<RenderLoop>())
@ -27,6 +27,7 @@ VirtualOutput::VirtualOutput(VirtualBackend *parent)
m_identifier = ++identifier;
setInformation(Information{
.name = QStringLiteral("Virtual-%1").arg(identifier),
.internal = internal,
});
}

View file

@ -24,7 +24,7 @@ class VirtualOutput : public Output
Q_OBJECT
public:
VirtualOutput(VirtualBackend *parent = nullptr);
VirtualOutput(VirtualBackend *parent, bool internal);
~VirtualOutput() override;
RenderLoop *renderLoop() const override;

View file

@ -34,14 +34,18 @@ static QString outputHash(Output *output)
}
/// See KScreen::Config::connectedOutputsHash in libkscreen
QString connectedOutputsHash(const QVector<Output *> &outputs)
QString connectedOutputsHash(const QVector<Output *> &outputs, bool isLidClosed)
{
QStringList hashedOutputs;
hashedOutputs.reserve(outputs.count());
for (auto output : std::as_const(outputs)) {
if (!output->isPlaceholder() && !output->isNonDesktop()) {
hashedOutputs << outputHash(output);
if (output->isPlaceholder() || !output->isNonDesktop()) {
continue;
}
if (output->isInternal() && isLidClosed) {
continue;
}
hashedOutputs << outputHash(output);
}
std::sort(hashedOutputs.begin(), hashedOutputs.end());
const auto hash = QCryptographicHash::hash(hashedOutputs.join(QString()).toLatin1(), QCryptographicHash::Md5);
@ -175,6 +179,9 @@ std::shared_ptr<OutputMode> parseMode(Output *output, const QJsonObject &modeInf
std::optional<std::pair<OutputConfiguration, QVector<Output *>>> readOutputConfig(const QVector<Output *> &outputs, const QString &hash)
{
const auto outputsInfo = outputsConfig(outputs, hash);
if (outputsInfo.isEmpty()) {
return std::nullopt;
}
std::vector<std::pair<uint32_t, Output *>> outputOrder;
OutputConfiguration cfg;
// default position goes from left to right

View file

@ -18,7 +18,7 @@ namespace KWin
namespace KScreenIntegration
{
QString connectedOutputsHash(const QVector<Output *> &outputs);
QString connectedOutputsHash(const QVector<Output *> &outputs, bool isLidClosed);
std::optional<std::pair<OutputConfiguration, QVector<Output *>>> readOutputConfig(const QVector<Output *> &outputs, const QString &hash);
}
}

37
src/lidswitchtracker.cpp Normal file
View file

@ -0,0 +1,37 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "lidswitchtracker.h"
#include "core/inputdevice.h"
#include "input_event.h"
namespace KWin
{
LidSwitchTracker::LidSwitchTracker()
{
input()->installInputEventSpy(this);
}
bool LidSwitchTracker::isLidClosed() const
{
return m_isLidClosed;
}
void LidSwitchTracker::switchEvent(KWin::SwitchEvent *event)
{
if (event->device()->isLidSwitch()) {
const bool state = event->state() == SwitchEvent::State::On;
if (state != m_isLidClosed) {
m_isLidClosed = state;
Q_EMIT lidStateChanged();
}
}
}
}

34
src/lidswitchtracker.h Normal file
View file

@ -0,0 +1,34 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "input_event_spy.h"
#include <QObject>
namespace KWin
{
class LidSwitchTracker : public QObject, InputEventSpy
{
Q_OBJECT
public:
explicit LidSwitchTracker();
bool isLidClosed() const;
Q_SIGNALS:
void lidStateChanged();
private:
void switchEvent(KWin::SwitchEvent *event) override;
bool m_isLidClosed = false;
};
}

View file

@ -9,35 +9,39 @@
#include "outputconfigurationstore.h"
#include "core/inputdevice.h"
#include "core/output.h"
#include "core/outputbackend.h"
#include "core/outputconfiguration.h"
#include "input.h"
#include "input_event.h"
#include "kscreenintegration.h"
#include "workspace.h"
namespace KWin
{
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::queryConfig(const QVector<Output *> &outputs)
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::queryConfig(const QVector<Output *> &outputs, bool isLidClosed)
{
const auto kscreenConfig = KScreenIntegration::readOutputConfig(outputs, KScreenIntegration::connectedOutputsHash(outputs));
const auto kscreenConfig = KScreenIntegration::readOutputConfig(outputs, KScreenIntegration::connectedOutputsHash(outputs, isLidClosed));
if (kscreenConfig) {
return kscreenConfig.value();
} else {
// no config file atm -> generate a new one
return generateConfig(outputs);
return generateConfig(outputs, isLidClosed);
}
}
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::generateConfig(const QVector<Output *> &outputs) const
std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::generateConfig(const QVector<Output *> &outputs, bool isLidClosed) const
{
OutputConfiguration ret;
QVector<Output *> outputOrder;
QPoint pos(0, 0);
for (const auto output : outputs) {
const auto mode = chooseMode(output);
const double scale = chooseScale(output, mode.get());
const bool enable = !isLidClosed || !output->isInternal();
*ret.changeSet(output) = {
.mode = mode,
.enabled = true,
.enabled = enable,
.pos = pos,
.scale = scale,
.transform = output->panelOrientation(),
@ -46,6 +50,9 @@ std::pair<OutputConfiguration, QVector<Output *>> OutputConfigurationStore::gene
.vrrPolicy = RenderLoop::VrrPolicy::Automatic,
};
pos.setX(pos.x() + mode->size().width() / scale);
if (enable) {
outputOrder.push_back(output);
}
}
return std::make_pair(ret, outputs);
}
@ -106,7 +113,7 @@ double OutputConfigurationStore::chooseScale(Output *output, OutputMode *mode) c
const double outputDpi = mode->size().height() / (output->physicalSize().height() / 25.4);
const double desiredScale = outputDpi / targetDpi(output);
// round to 25% steps
return std::round(100.0 * desiredScale / 25.0) * 100.0 / 25.0;
return std::clamp(std::round(100.0 * desiredScale / 25.0) * 25.0 / 100.0, 1.0, 5.0);
}
double OutputConfigurationStore::targetDpi(Output *output) const

View file

@ -21,13 +21,12 @@ class OutputMode;
class OutputConfigurationStore
{
public:
std::pair<OutputConfiguration, QVector<Output *>> queryConfig(const QVector<Output *> &outputs);
std::pair<OutputConfiguration, QVector<Output *>> queryConfig(const QVector<Output *> &outputs, bool isLidClosed);
private:
std::pair<OutputConfiguration, QVector<Output *>> generateConfig(const QVector<Output *> &outputs) const;
std::pair<OutputConfiguration, QVector<Output *>> generateConfig(const QVector<Output *> &outputs, bool isLidClosed) const;
std::shared_ptr<OutputMode> chooseMode(Output *output) const;
double chooseScale(Output *output, OutputMode *mode) const;
double targetDpi(Output *output) const;
};
}

View file

@ -46,6 +46,7 @@
#include "tabbox/tabbox.h"
#endif
#include "decorations/decorationbridge.h"
#include "lidswitchtracker.h"
#include "main.h"
#include "outputconfigurationstore.h"
#include "placeholderinputeventfilter.h"
@ -129,6 +130,7 @@ Workspace::Workspace()
, m_applicationMenu(std::make_unique<ApplicationMenu>())
, m_placementTracker(std::make_unique<PlacementTracker>(this))
, m_outputConfigStore(std::make_unique<OutputConfigurationStore>())
, m_lidSwitchTracker(std::make_unique<LidSwitchTracker>())
{
// If KWin was already running it saved its configuration after loosing the selection -> Reread
QFuture<void> reparseConfigFuture = QtConcurrent::run(&Options::reparseConfiguration, options);
@ -269,6 +271,11 @@ 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());
connect(m_lidSwitchTracker.get(), &LidSwitchTracker::lidStateChanged, this, [this]() {
const auto [config, order] = m_outputConfigStore->queryConfig(kwinApp()->outputBackend()->outputs(), m_lidSwitchTracker->isLidClosed());
applyOutputConfiguration(config, order);
});
}
QString Workspace::getPlacementTrackerHash()
@ -532,7 +539,7 @@ void Workspace::updateOutputConfiguration()
setOutputOrder(newOrder);
};
const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs);
const auto &[cfg, order] = m_outputConfigStore->queryConfig(outputs, m_lidSwitchTracker->isLidClosed());
if (!kwinApp()->outputBackend()->applyOutputChanges(cfg)) {
qCWarning(KWIN_CORE) << "Applying output config failed!";
setFallbackOutputOrder();

View file

@ -78,6 +78,7 @@ class Placement;
class OutputConfiguration;
class TileManager;
class OutputConfigurationStore;
class LidSwitchTracker;
class KWIN_EXPORT Workspace : public QObject
{
@ -723,6 +724,7 @@ private:
std::unique_ptr<PlaceholderInputEventFilter> m_placeholderFilter;
std::map<Output *, std::unique_ptr<TileManager>> m_tileManagers;
std::unique_ptr<OutputConfigurationStore> m_outputConfigStore;
std::unique_ptr<LidSwitchTracker> m_lidSwitchTracker;
private:
friend bool performTransiencyCheck();