diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ include(FeatureSummary) include(KDEClangFormat) -find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Test) +find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Test Sensors) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config DBusAddons diff --git a/common/globals.h b/common/globals.h --- a/common/globals.h +++ b/common/globals.h @@ -21,6 +21,16 @@ namespace Globals { + enum class DeviceOrientation { + Undefined, + TopUp, + TopDown, + LeftUp, + RightUp, + FaceUp, + FaceDown + }; + void setDirPath(const QString &path); QString dirPath(); } diff --git a/common/orientation_sensor.h b/common/orientation_sensor.h new file mode 100644 --- /dev/null +++ b/common/orientation_sensor.h @@ -0,0 +1,53 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#pragma once + +#include "globals.h" + +#include + +class QOrientationSensor; + +using namespace Globals; + +class OrientationSensor : public QObject +{ + Q_OBJECT +public: + explicit OrientationSensor(QObject *parent = nullptr); + ~OrientationSensor() override final; + + DeviceOrientation value() const; + bool available() const; + bool enabled() const; + + void setEnabled(bool enable); + +Q_SIGNALS: + void valueChanged(DeviceOrientation orientation); + void availableChanged(bool available); + void enabledChanged(bool enabled); + +private: + void refresh(); + void updateState(); + + QOrientationSensor *m_sensor; + DeviceOrientation m_value = DeviceOrientation::Undefined; + bool m_available = false; + bool m_enabled = false; +}; diff --git a/common/orientation_sensor.cpp b/common/orientation_sensor.cpp new file mode 100644 --- /dev/null +++ b/common/orientation_sensor.cpp @@ -0,0 +1,108 @@ +/******************************************************************** +Copyright © 2019 Roman Gilg + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "orientation_sensor.h" + +#include +#include + +OrientationSensor::OrientationSensor(QObject *parent) + : QObject(parent) + , m_sensor(new QOrientationSensor(this)) +{ + connect(m_sensor, &QOrientationSensor::activeChanged, this, &OrientationSensor::refresh); + + // Start the sensor once in the beginning to check if it is available. + m_sensor->start(); +} + +OrientationSensor::~OrientationSensor() = default; + +void OrientationSensor::updateState() +{ + auto toOrientation = [] (QOrientationReading *reading) { + switch (reading->orientation()) { + case QOrientationReading::Undefined: + return DeviceOrientation::Undefined; + case QOrientationReading::TopUp: + return DeviceOrientation::TopUp; + case QOrientationReading::TopDown: + return DeviceOrientation::TopDown; + case QOrientationReading::LeftUp: + return DeviceOrientation::LeftUp; + case QOrientationReading::RightUp: + return DeviceOrientation::RightUp; + case QOrientationReading::FaceUp: + return DeviceOrientation::FaceUp; + case QOrientationReading::FaceDown: + return DeviceOrientation::FaceDown; + default: + Q_UNREACHABLE(); + } + }; + const auto orientation = toOrientation(m_sensor->reading()); + if (m_value != orientation) { + m_value = orientation; + Q_EMIT valueChanged(orientation); + } +} + +void OrientationSensor::refresh() +{ + if (m_sensor->isActive()) { + m_available = true; + if (m_enabled) { + updateState(); + } + Q_EMIT availableChanged(true); + } else { + Q_EMIT availableChanged(false); + } +} + +DeviceOrientation OrientationSensor::value() const +{ + return m_value; +} + +bool OrientationSensor::available() const +{ + return m_available; +} + +bool OrientationSensor::enabled() const +{ + return m_sensor->isActive(); +} + +void OrientationSensor::setEnabled(bool enable) +{ + if (m_enabled == enable) { + return; + } + m_enabled = enable; + + if (enable) { + connect(m_sensor, &QOrientationSensor::readingChanged, + this, &OrientationSensor::updateState); + m_sensor->start(); + } else { + disconnect(m_sensor, &QOrientationSensor::readingChanged, + this, &OrientationSensor::updateState); + m_value = DeviceOrientation::Undefined; + } + Q_EMIT enabledChanged(enable); +} diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -13,6 +13,7 @@ osdaction.cpp ${CMAKE_SOURCE_DIR}/common/globals.cpp ${CMAKE_SOURCE_DIR}/common/control.cpp + ${CMAKE_SOURCE_DIR}/common/orientation_sensor.cpp ${CMAKE_SOURCE_DIR}/common/utils.cpp ) @@ -32,6 +33,7 @@ target_link_libraries(kscreen Qt5::Widgets Qt5::DBus Qt5::Quick + Qt5::Sensors KF5::Declarative KF5::Screen KF5::DBusAddons diff --git a/kded/config.h b/kded/config.h --- a/kded/config.h +++ b/kded/config.h @@ -17,6 +17,7 @@ #ifndef KDED_CONFIG_H #define KDED_CONFIG_H +#include "../common/globals.h" #include @@ -44,6 +45,8 @@ } void activateControlWatching(); + bool autoRotationRequested() const; + void setDeviceOrientation(Globals::DeviceOrientation orientation); void log(); void setValidityFlags(KScreen::Config::ValidityFlags flags) { diff --git a/kded/config.cpp b/kded/config.cpp --- a/kded/config.cpp +++ b/kded/config.cpp @@ -17,7 +17,6 @@ *********************************************************************/ #include "config.h" #include "output.h" -#include "../common/globals.h" #include "../common/control.h" #include "kscreen_daemon_debug.h" #include "device.h" @@ -68,6 +67,29 @@ m_control->activateWatcher(); } +bool Config::autoRotationRequested() const +{ + for (KScreen::OutputPtr &output : m_data->outputs()) { + if (m_control->getAutoRotate(output)) { + return true; + } + } + return false; +} + +void Config::setDeviceOrientation(Globals::DeviceOrientation orientation) +{ + for (KScreen::OutputPtr &output : m_data->outputs()) { + if (!m_control->getAutoRotate(output)) { + continue; + } + if (Output::updateOrientation(output, orientation)) { + // TODO: call Layouter to find fitting positions for other outputs again + return; + } + } +} + bool Config::fileExists() const { return (QFile::exists(configsDirPath() % id()) || QFile::exists(configsDirPath() % s_fixedConfigFileName)); diff --git a/kded/daemon.h b/kded/daemon.h --- a/kded/daemon.h +++ b/kded/daemon.h @@ -19,7 +19,9 @@ #ifndef KSCREEN_DAEMON_H #define KSCREEN_DAEMON_H +#include "../common/globals.h" #include "osdaction.h" + #include #include @@ -29,6 +31,7 @@ #include class Config; +class OrientationSensor; namespace KScreen { @@ -81,12 +84,16 @@ void disableOutput(KScreen::OutputPtr &output); void showOsd(const QString &icon, const QString &text); + void updateOrientation(); + std::unique_ptr m_monitoredConfig; bool m_monitoring; + bool m_configDirty = true; QTimer* m_changeCompressor; QTimer* m_saveTimer; QTimer* m_lidClosedTimer; KScreen::OsdManager *m_osdManager; + OrientationSensor *m_orientationSensor; bool m_startingUp = true; }; diff --git a/kded/daemon.cpp b/kded/daemon.cpp --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -20,6 +20,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "daemon.h" + +#include "../common/orientation_sensor.h" #include "config.h" #include "generator.h" #include "device.h" @@ -50,7 +52,7 @@ , m_changeCompressor(new QTimer(this)) , m_saveTimer(nullptr) , m_lidClosedTimer(new QTimer(this)) - + , m_orientationSensor(nullptr) { KScreen::Log::instance(); QMetaObject::invokeMethod(this, "getInitialConfig", Qt::QueuedConnection); @@ -100,6 +102,17 @@ m_lidClosedTimer->setSingleShot(true); connect(m_lidClosedTimer, &QTimer::timeout, this, &KScreenDaemon::lidClosedTimeout); + if (m_monitoredConfig->data()->supportedFeatures() + & (KScreen::Config::Feature::AutoRotation + | KScreen::Config::Feature::TabletMode)) { + m_orientationSensor = new OrientationSensor(this); + + connect(m_orientationSensor, &OrientationSensor::availableChanged, + this, &KScreenDaemon::updateOrientation); + connect(m_orientationSensor, &OrientationSensor::valueChanged, + this, &KScreenDaemon::updateOrientation); + } + connect(Device::self(), &Device::lidClosedChanged, this, &KScreenDaemon::lidClosedChanged); connect(Device::self(), &Device::resumingFromSuspend, this, [&]() { @@ -125,35 +138,80 @@ monitorConnectedChange(); } +void KScreenDaemon::updateOrientation() +{ + using Orientation = Globals::DeviceOrientation; + + if (!m_monitoredConfig) { + return; + } + Q_ASSERT(m_monitoredConfig->data()->supportedFeatures() + & (KScreen::Config::Feature::AutoRotation + | KScreen::Config::Feature::TabletMode)); + + if (!m_orientationSensor->available() || !m_orientationSensor->enabled()) { + return; + } + + const auto orientation = m_orientationSensor->value(); + if (orientation == Orientation::Undefined) { + // Orientation sensor went off. Do not change current orientation. + return; + } + if (orientation == Orientation::FaceUp || + orientation == Orientation::FaceDown) { + // We currently don't do anything with FaceUp/FaceDown, but in the future we could use them + // to shut off and switch on again a display when display is facing downwards/upwards. + return; + } + + m_monitoredConfig->setDeviceOrientation(orientation); + if (m_monitoring) { + doApplyConfig(m_monitoredConfig->data()); + } else { + m_configDirty = true; + } +} + void KScreenDaemon::doApplyConfig(const KScreen::ConfigPtr& config) { qCDebug(KSCREEN_KDED) << "Do set and apply specific config"; auto configWrapper = std::unique_ptr(new Config(config)); configWrapper->setValidityFlags(KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); configWrapper->activateControlWatching(); - connect(configWrapper.get(), &Config::controlChanged, this, [this]() { - // TODO - }); doApplyConfig(std::move(configWrapper)); } void KScreenDaemon::doApplyConfig(std::unique_ptr config) { - setMonitorForChanges(false); // TODO: remove? m_monitoredConfig = std::move(config); + + m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested()); + connect(m_monitoredConfig.get(), &Config::controlChanged, this, [this]() { + m_orientationSensor->setEnabled(m_monitoredConfig->autoRotationRequested()); + updateOrientation(); + }); + refreshConfig(); } void KScreenDaemon::refreshConfig() { setMonitorForChanges(false); + m_configDirty = false; KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig->data()); - connect(new KScreen::SetConfigOperation(m_monitoredConfig->data()), &KScreen::SetConfigOperation::finished, this, - [&]() { - qCDebug(KSCREEN_KDED) << "Config applied"; - setMonitorForChanges(true); - }); + connect(new KScreen::SetConfigOperation(m_monitoredConfig->data()), + &KScreen::SetConfigOperation::finished, + this, [this]() { + qCDebug(KSCREEN_KDED) << "Config applied"; + if (m_configDirty) { + // Config changed in the meantime again, apply. + doApplyConfig(m_monitoredConfig->data()); + } else { + setMonitorForChanges(true); + } + }); } void KScreenDaemon::applyConfig() diff --git a/kded/output.h b/kded/output.h --- a/kded/output.h +++ b/kded/output.h @@ -18,6 +18,7 @@ #define KDED_OUTPUT_H #include "../common/control.h" +#include "../common/globals.h" #include @@ -34,14 +35,16 @@ static QString dirPath(); + static bool updateOrientation(KScreen::OutputPtr &output, + Globals::DeviceOrientation orientation); + private: static QString globalFileName(const QString &hash); static QVariantMap getGlobalData(KScreen::OutputPtr output); static void readIn(KScreen::OutputPtr output, const QVariantMap &info, Control::OutputRetention retention); static bool readInGlobal(KScreen::OutputPtr output); static void readInGlobalPartFromInfo(KScreen::OutputPtr output, const QVariantMap &info); - /* * When a global output value (scale, rotation) is changed we might * need to reposition the outputs when another config is read. diff --git a/kded/output.cpp b/kded/output.cpp --- a/kded/output.cpp +++ b/kded/output.cpp @@ -16,7 +16,6 @@ *********************************************************************/ #include "output.h" #include "config.h" -#include "../common/globals.h" #include "kscreen_daemon_debug.h" #include "generator.h" @@ -119,6 +118,44 @@ return true; } +KScreen::Output::Rotation orientationToRotation(Globals::DeviceOrientation orientation, + KScreen::Output::Rotation fallback) +{ + using namespace Globals; + + switch (orientation) { + case DeviceOrientation::TopUp: + return KScreen::Output::Rotation::None; + case DeviceOrientation::TopDown: + return KScreen::Output::Rotation::Inverted; + case DeviceOrientation::LeftUp: + return KScreen::Output::Rotation::Right; + case DeviceOrientation::RightUp: + return KScreen::Output::Rotation::Left; + case DeviceOrientation::Undefined: + case DeviceOrientation::FaceUp: + case DeviceOrientation::FaceDown: + return fallback; + default: + Q_UNREACHABLE(); + } +} + +bool Output::updateOrientation(KScreen::OutputPtr &output, Globals::DeviceOrientation orientation) +{ + if (output->type() != KScreen::Output::Type::Panel) { + return false; + } + const auto currentRotation = output->rotation(); + const auto rotation = orientationToRotation(orientation, currentRotation); + if (rotation == currentRotation) { + return true; + } + output->setRotation(rotation); + return true; +} + +// TODO: move this into the Layouter class. void Output::adjustPositions(KScreen::ConfigPtr config, const QVariantList &outputsInfo) { typedef QPair Out;