diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp index 3a09f96ba7..51e7ae34d0 100644 --- a/autotests/integration/kwin_wayland_test.cpp +++ b/autotests/integration/kwin_wayland_test.cpp @@ -230,6 +230,7 @@ void Test::setOutputConfig(const QVector &infos) return VirtualBackend::OutputInfo{ .geometry = info.geometry, .scale = info.scale, + .internal = info.internal, }; }); static_cast(kwinApp()->outputBackend())->setVirtualOutputs(converted); diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h index a089c69a8c..432c0921dd 100644 --- a/autotests/integration/kwin_wayland_test.h +++ b/autotests/integration/kwin_wayland_test.h @@ -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 &geometries); void setOutputConfig(const QVector &infos); diff --git a/autotests/integration/outputchanges_test.cpp b/autotests/integration/outputchanges_test.cpp index 6639d1665c..9437182d7a 100644 --- a/autotests/integration/outputchanges_test.cpp +++ b/autotests/integration/outputchanges_test.cpp @@ -16,6 +16,8 @@ #include +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(); + 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) diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp index d95616b901..0678980f4f 100644 --- a/autotests/integration/test_helpers.cpp +++ b/autotests/integration/test_helpers.cpp @@ -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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0e0044799a..69e133a2a1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/backends/virtual/virtual_backend.cpp b/src/backends/virtual/virtual_backend.cpp index 554b4a364e..a12b5770da 100644 --- a/src/backends/virtual/virtual_backend.cpp +++ b/src/backends/virtual/virtual_backend.cpp @@ -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); diff --git a/src/backends/virtual/virtual_backend.h b/src/backends/virtual/virtual_backend.h index 96a636794e..f750bd8971 100644 --- a/src/backends/virtual/virtual_backend.h +++ b/src/backends/virtual/virtual_backend.h @@ -37,6 +37,7 @@ public: { QRect geometry; double scale = 1; + bool internal = false; }; Output *addOutput(const OutputInfo &info); void setVirtualOutputs(const QVector &infos); diff --git a/src/backends/virtual/virtual_output.cpp b/src/backends/virtual/virtual_output.cpp index 26d85937e6..d96c3d6951 100644 --- a/src/backends/virtual/virtual_output.cpp +++ b/src/backends/virtual/virtual_output.cpp @@ -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()) @@ -27,6 +27,7 @@ VirtualOutput::VirtualOutput(VirtualBackend *parent) m_identifier = ++identifier; setInformation(Information{ .name = QStringLiteral("Virtual-%1").arg(identifier), + .internal = internal, }); } diff --git a/src/backends/virtual/virtual_output.h b/src/backends/virtual/virtual_output.h index 4531f86a91..e45025d117 100644 --- a/src/backends/virtual/virtual_output.h +++ b/src/backends/virtual/virtual_output.h @@ -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; diff --git a/src/kscreenintegration.cpp b/src/kscreenintegration.cpp index baa110dd30..1bfcb7f9e3 100644 --- a/src/kscreenintegration.cpp +++ b/src/kscreenintegration.cpp @@ -34,14 +34,18 @@ static QString outputHash(Output *output) } /// See KScreen::Config::connectedOutputsHash in libkscreen -QString connectedOutputsHash(const QVector &outputs) +QString connectedOutputsHash(const QVector &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 parseMode(Output *output, const QJsonObject &modeInf std::optional>> readOutputConfig(const QVector &outputs, const QString &hash) { const auto outputsInfo = outputsConfig(outputs, hash); + if (outputsInfo.isEmpty()) { + return std::nullopt; + } std::vector> outputOrder; OutputConfiguration cfg; // default position goes from left to right diff --git a/src/kscreenintegration.h b/src/kscreenintegration.h index 701a8f8258..96c0569ce9 100644 --- a/src/kscreenintegration.h +++ b/src/kscreenintegration.h @@ -18,7 +18,7 @@ namespace KWin namespace KScreenIntegration { -QString connectedOutputsHash(const QVector &outputs); +QString connectedOutputsHash(const QVector &outputs, bool isLidClosed); std::optional>> readOutputConfig(const QVector &outputs, const QString &hash); } } diff --git a/src/lidswitchtracker.cpp b/src/lidswitchtracker.cpp new file mode 100644 index 0000000000..ba0dcb0765 --- /dev/null +++ b/src/lidswitchtracker.cpp @@ -0,0 +1,37 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2023 Xaver Hugl + + 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(); + } + } +} + +} diff --git a/src/lidswitchtracker.h b/src/lidswitchtracker.h new file mode 100644 index 0000000000..3eee6c4f85 --- /dev/null +++ b/src/lidswitchtracker.h @@ -0,0 +1,34 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once +#include "input_event_spy.h" + +#include + +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; +}; + +} diff --git a/src/outputconfigurationstore.cpp b/src/outputconfigurationstore.cpp index df7fe11a38..ecb4bdf380 100644 --- a/src/outputconfigurationstore.cpp +++ b/src/outputconfigurationstore.cpp @@ -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> OutputConfigurationStore::queryConfig(const QVector &outputs) +std::pair> OutputConfigurationStore::queryConfig(const QVector &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> OutputConfigurationStore::generateConfig(const QVector &outputs) const +std::pair> OutputConfigurationStore::generateConfig(const QVector &outputs, bool isLidClosed) const { OutputConfiguration ret; - + QVector 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> 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 diff --git a/src/outputconfigurationstore.h b/src/outputconfigurationstore.h index 6014f7d513..12ab8ccbd4 100644 --- a/src/outputconfigurationstore.h +++ b/src/outputconfigurationstore.h @@ -21,13 +21,12 @@ class OutputMode; class OutputConfigurationStore { public: - std::pair> queryConfig(const QVector &outputs); + std::pair> queryConfig(const QVector &outputs, bool isLidClosed); private: - std::pair> generateConfig(const QVector &outputs) const; + std::pair> generateConfig(const QVector &outputs, bool isLidClosed) const; std::shared_ptr chooseMode(Output *output) const; double chooseScale(Output *output, OutputMode *mode) const; double targetDpi(Output *output) const; }; - } diff --git a/src/workspace.cpp b/src/workspace.cpp index 14f0f88ebf..022112fb6d 100644 --- a/src/workspace.cpp +++ b/src/workspace.cpp @@ -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()) , m_placementTracker(std::make_unique(this)) , m_outputConfigStore(std::make_unique()) + , m_lidSwitchTracker(std::make_unique()) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture 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(); diff --git a/src/workspace.h b/src/workspace.h index 96edd7f526..bd1850c033 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -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 m_placeholderFilter; std::map> m_tileManagers; std::unique_ptr m_outputConfigStore; + std::unique_ptr m_lidSwitchTracker; private: friend bool performTransiencyCheck();