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/orientation_sensor.h b/common/orientation_sensor.h new file mode 100644 --- /dev/null +++ b/common/orientation_sensor.h @@ -0,0 +1,47 @@ +/******************************************************************** +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 +#include + +class OrientationSensor : public QObject +{ + Q_OBJECT +public: + explicit OrientationSensor(QObject *parent = nullptr); + ~OrientationSensor() override final; + + QOrientationReading::Orientation value() const; + bool available() const; + bool enabled() const; + + void setEnabled(bool enable); + +Q_SIGNALS: + void valueChanged(QOrientationReading::Orientation orientation); + void availableChanged(bool available); + void enabledChanged(bool enabled); + +private: + void refresh(); + void updateState(); + + QOrientationSensor *m_sensor; + QOrientationReading::Orientation m_value = QOrientationReading::Undefined; + 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,83 @@ +/******************************************************************** +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 + +OrientationSensor::OrientationSensor(QObject *parent) + : QObject(parent) + , m_sensor(new QOrientationSensor(this)) +{ + connect(m_sensor, &QOrientationSensor::activeChanged, this, &OrientationSensor::refresh); +} + +OrientationSensor::~OrientationSensor() = default; + +void OrientationSensor::updateState() +{ + const auto orientation = m_sensor->reading()->orientation(); + if (m_value != orientation) { + m_value = orientation; + Q_EMIT valueChanged(orientation); + } +} + +void OrientationSensor::refresh() +{ + if (m_sensor->isActive()) { + if (m_enabled) { + updateState(); + } + Q_EMIT availableChanged(true); + } else { + Q_EMIT availableChanged(false); + } +} + +QOrientationReading::Orientation OrientationSensor::value() const +{ + return m_value; +} + +bool OrientationSensor::available() const +{ + return m_sensor->connectToBackend(); +} + +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 = QOrientationReading::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,9 +17,10 @@ #ifndef KDED_CONFIG_H #define KDED_CONFIG_H - #include +#include + #include class ControlConfig; @@ -44,6 +45,8 @@ } void activateControlWatching(); + bool autoRotationRequested() const; + void setDeviceOrientation(QOrientationReading::Orientation 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(QOrientationReading::Orientation 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" @@ -40,6 +42,7 @@ #include #include +#include #include K_PLUGIN_CLASS_WITH_JSON(KScreenDaemon, "kscreen.json") @@ -50,8 +53,13 @@ , m_changeCompressor(new QTimer(this)) , m_saveTimer(nullptr) , m_lidClosedTimer(new QTimer(this)) - + , m_orientationSensor(new OrientationSensor(this)) { + connect(m_orientationSensor, &OrientationSensor::availableChanged, + this, &KScreenDaemon::updateOrientation); + connect(m_orientationSensor, &OrientationSensor::valueChanged, + this, &KScreenDaemon::updateOrientation); + KScreen::Log::instance(); QMetaObject::invokeMethod(this, "getInitialConfig", Qt::QueuedConnection); } @@ -125,35 +133,80 @@ monitorConnectedChange(); } +void KScreenDaemon::updateOrientation() +{ + if (!m_monitoredConfig) { + return; + } + const auto features = m_monitoredConfig->data()->supportedFeatures(); + if (!features.testFlag(KScreen::Config::Feature::AutoRotation) + || !features.testFlag(KScreen::Config::Feature::TabletMode) ) { + return; + } + + if (!m_orientationSensor->available() || !m_orientationSensor->enabled()) { + return; + } + + const auto orientation = m_orientationSensor->value(); + if (orientation == QOrientationReading::Undefined) { + // Orientation sensor went off. Do not change current orientation. + return; + } + if (orientation == QOrientationReading::FaceUp || + orientation == QOrientationReading::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,9 +18,11 @@ #define KDED_OUTPUT_H #include "../common/control.h" +#include "../common/globals.h" #include +#include #include class Output @@ -34,14 +36,16 @@ static QString dirPath(); + static bool updateOrientation(KScreen::OutputPtr &output, + QOrientationReading::Orientation 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,45 @@ return true; } +KScreen::Output::Rotation orientationToRotation(QOrientationReading::Orientation orientation, + KScreen::Output::Rotation fallback) +{ + using Orientation = QOrientationReading::Orientation; + + switch (orientation) { + case Orientation::TopUp: + return KScreen::Output::Rotation::None; + case Orientation::TopDown: + return KScreen::Output::Rotation::Inverted; + case Orientation::LeftUp: + return KScreen::Output::Rotation::Right; + case Orientation::RightUp: + return KScreen::Output::Rotation::Left; + case Orientation::Undefined: + case Orientation::FaceUp: + case Orientation::FaceDown: + return fallback; + default: + Q_UNREACHABLE(); + } +} + +bool Output::updateOrientation(KScreen::OutputPtr &output, + QOrientationReading::Orientation 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; diff --git a/tests/kded/CMakeLists.txt b/tests/kded/CMakeLists.txt --- a/tests/kded/CMakeLists.txt +++ b/tests/kded/CMakeLists.txt @@ -23,7 +23,7 @@ add_executable(${testname} ${test_SRCS}) add_dependencies(${testname} kscreen) # make sure the dbus interfaces are generated target_compile_definitions(${testname} PRIVATE "-DTEST_DATA=\"${CMAKE_CURRENT_SOURCE_DIR}/\"") - target_link_libraries(${testname} Qt5::Test Qt5::DBus Qt5::Gui KF5::Screen) + target_link_libraries(${testname} Qt5::Test Qt5::DBus Qt5::Gui Qt5::Sensors KF5::Screen) add_test(NAME kscreen-kded-${testname} COMMAND ${testname}) ecm_mark_as_test(${testname}) endmacro()