diff --git a/common/control.h b/common/control.h --- a/common/control.h +++ b/common/control.h @@ -87,6 +87,12 @@ void setAutoRotate(const KScreen::OutputPtr &output, bool value); void setAutoRotate(const QString &outputId, const QString &outputName, bool value); + bool getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const; + bool getAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName) const; + void setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value); + void setAutoRotateOnlyInTabletMode(const QString &outputId, const QString &outputName, + bool value); + KScreen::OutputPtr getReplicationSource(const KScreen::OutputPtr &output) const; KScreen::OutputPtr getReplicationSource(const QString &outputId, const QString &outputName) const; @@ -128,6 +134,9 @@ bool getAutoRotate() const; void setAutoRotate(bool value); + bool getAutoRotateOnlyInTabletMode() const; + void setAutoRotateOnlyInTabletMode(bool value); + QString dirPath() const override; QString filePath() const override; diff --git a/common/control.cpp b/common/control.cpp --- a/common/control.cpp +++ b/common/control.cpp @@ -368,9 +368,8 @@ } // Info for output not found. - // TODO: make this return value depend on the device having a tablet state? - return true; - } + return true; +} void ControlConfig::setAutoRotate(const KScreen::OutputPtr &output, bool value) { @@ -410,6 +409,74 @@ setOutputAutoRotate(); } +bool ControlConfig::getAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const +{ + return getAutoRotateOnlyInTabletMode(output->hashMd5(), output->name()); +} + +bool ControlConfig::getAutoRotateOnlyInTabletMode(const QString &outputId, + const QString &outputName) const +{ + const auto retention = getOutputRetention(outputId, outputName); + if (retention == OutputRetention::Individual) { + const QVariantList outputsInfo = getOutputs(); + for (const auto variantInfo : outputsInfo) { + const QVariantMap info = variantInfo.toMap(); + if (!infoIsOutput(info, outputId, outputName)) { + continue; + } + const auto val = info[QStringLiteral("autorotate-tablet-only")]; + return !val.canConvert() || val.toBool(); + } + } + // Retention is global or info for output not in config control file. + if (auto *outputControl = getOutputControl(outputId, outputName)) { + return outputControl->getAutoRotateOnlyInTabletMode(); + } + + // Info for output not found. + return true; +} + +void ControlConfig::setAutoRotateOnlyInTabletMode(const KScreen::OutputPtr &output, bool value) +{ + setAutoRotateOnlyInTabletMode(output->hashMd5(), output->name(), value); +} + +// TODO: combine methods (templated functions) +void ControlConfig::setAutoRotateOnlyInTabletMode(const QString &outputId, + const QString &outputName, bool value) +{ + QList::iterator it; + QVariantList outputsInfo = getOutputs(); + + auto setOutputAutoRotateOnlyInTabletMode = [&outputId, &outputName, value, this]() { + if (auto *control = getOutputControl(outputId, outputName)) { + control->setAutoRotateOnlyInTabletMode(value); + } + }; + + for (it = outputsInfo.begin(); it != outputsInfo.end(); ++it) { + QVariantMap outputInfo = (*it).toMap(); + if (!infoIsOutput(outputInfo, outputId, outputName)) { + continue; + } + outputInfo[QStringLiteral("autorotate-tablet-only")] = value; + *it = outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotateOnlyInTabletMode(); + return; + } + // no entry yet, create one + auto outputInfo = createOutputInfo(outputId, outputName); + outputInfo[QStringLiteral("autorotate-tablet-only")] = value; + + outputsInfo << outputInfo; + setOutputs(outputsInfo); + setOutputAutoRotateOnlyInTabletMode(); +} + + KScreen::OutputPtr ControlConfig::getReplicationSource(const KScreen::OutputPtr &output) const { return getReplicationSource(output->hashMd5(), output->name()); @@ -560,3 +627,18 @@ } infoMap[QStringLiteral("autorotate")] = value; } + +bool ControlOutput::getAutoRotateOnlyInTabletMode() const +{ + const auto val = constInfo()[QStringLiteral("autorotate-tablet-only")]; + return !val.canConvert() || val.toBool(); +} + +void ControlOutput::setAutoRotateOnlyInTabletMode(bool value) +{ + auto &infoMap = info(); + if (infoMap.isEmpty()) { + infoMap = createOutputInfo(m_output->hashMd5(), m_output->name()); + } + infoMap[QStringLiteral("autorotate-tablet-only")] = value; +} diff --git a/kcm/CMakeLists.txt b/kcm/CMakeLists.txt --- a/kcm/CMakeLists.txt +++ b/kcm/CMakeLists.txt @@ -8,6 +8,7 @@ ${CMAKE_SOURCE_DIR}/common/utils.cpp ${CMAKE_SOURCE_DIR}/common/control.cpp ${CMAKE_SOURCE_DIR}/common/globals.cpp + ${CMAKE_SOURCE_DIR}/common/orientation_sensor.cpp ) ecm_qt_declare_logging_category(kcm_kscreen_SRCS @@ -22,6 +23,7 @@ add_library(kcm_kscreen MODULE ${kcm_kscreen_SRCS}) target_link_libraries(kcm_kscreen + Qt5::Sensors KF5::ConfigCore KF5::CoreAddons KF5::I18n diff --git a/kcm/config_handler.h b/kcm/config_handler.h --- a/kcm/config_handler.h +++ b/kcm/config_handler.h @@ -32,7 +32,7 @@ ~ConfigHandler() override = default; void setConfig(KScreen::ConfigPtr config); - void updateInitialConfig(); + void updateInitialData(); OutputModel* outputModel() const { return m_outputs; @@ -53,6 +53,11 @@ KScreen::OutputPtr replicationSource(const KScreen::OutputPtr &output) const; void setReplicationSource(KScreen::OutputPtr &output, const KScreen::OutputPtr &source); + bool autoRotate(const KScreen::OutputPtr &output) const; + void setAutoRotate(KScreen::OutputPtr &output, bool autoRotate); + bool autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const; + void setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value); + void writeControl(); void checkNeedsSave(); @@ -79,6 +84,7 @@ OutputModel *m_outputs = nullptr; std::unique_ptr m_control; + std::unique_ptr m_initialControl; Control::OutputRetention m_initialRetention = Control::OutputRetention:: Undefined; QSize m_lastNormalizedScreenSize; diff --git a/kcm/config_handler.cpp b/kcm/config_handler.cpp --- a/kcm/config_handler.cpp +++ b/kcm/config_handler.cpp @@ -36,6 +36,8 @@ { m_config = config; m_initialConfig = m_config->clone(); + m_initialControl.reset(new ControlConfig(m_initialConfig)); + KScreen::ConfigMonitor::instance()->addConfig(m_config); m_control.reset(new ControlConfig(config)); @@ -49,6 +51,8 @@ initOutput(output); } m_lastNormalizedScreenSize = screenSize(); + + // TODO: put this into m_initialControl m_initialRetention = getRetention(); Q_EMIT retentionChanged(); @@ -94,7 +98,7 @@ }); } -void ConfigHandler::updateInitialConfig() +void ConfigHandler::updateInitialData() { m_initialRetention = getRetention(); connect(new GetConfigOperation(), &GetConfigOperation::finished, @@ -106,6 +110,7 @@ for (auto output : m_config->outputs()) { resetScale(output); } + m_initialControl.reset(new ControlConfig(m_initialConfig)); checkNeedsSave(); }); } @@ -147,7 +152,10 @@ || output->pos() != initialOutput->pos() || output->scale() != initialOutput->scale() || output->rotation() != initialOutput->rotation() - || output->replicationSource() != initialOutput->replicationSource(); + || output->replicationSource() != initialOutput->replicationSource() + || autoRotate(output) != m_initialControl->getAutoRotate(output) + || autoRotateOnlyInTabletMode(output) + != m_initialControl->getAutoRotateOnlyInTabletMode(output); } if (needsSave) { Q_EMIT needsSaveChecked(true); @@ -302,6 +310,26 @@ m_control->setReplicationSource(output, source); } +bool ConfigHandler::autoRotate(const KScreen::OutputPtr &output) const +{ + return m_control->getAutoRotate(output); +} + +void ConfigHandler::setAutoRotate(KScreen::OutputPtr &output, bool autoRotate) +{ + m_control->setAutoRotate(output, autoRotate); +} + +bool ConfigHandler::autoRotateOnlyInTabletMode(const KScreen::OutputPtr &output) const +{ + return m_control->getAutoRotateOnlyInTabletMode(output); +} + +void ConfigHandler::setAutoRotateOnlyInTabletMode(KScreen::OutputPtr &output, bool value) +{ + m_control->setAutoRotateOnlyInTabletMode(output, value); +} + void ConfigHandler::writeControl() { if (!m_control) { diff --git a/kcm/kcm.h b/kcm/kcm.h --- a/kcm/kcm.h +++ b/kcm/kcm.h @@ -24,6 +24,7 @@ } class ConfigHandler; +class OrientationSensor; class OutputIdentifier; class OutputModel; @@ -47,6 +48,13 @@ NOTIFY globalScaleChanged) Q_PROPERTY(int outputRetention READ outputRetention WRITE setOutputRetention NOTIFY outputRetentionChanged) + Q_PROPERTY(bool autoRotationSupported READ autoRotationSupported + NOTIFY autoRotationSupportedChanged) + Q_PROPERTY(bool orientationSensorAvailable READ orientationSensorAvailable + NOTIFY orientationSensorAvailableChanged) + Q_PROPERTY(bool tabletModeAvailable READ tabletModeAvailable + NOTIFY tabletModeAvailableChanged) + public: explicit KCMKScreen (QObject *parent = nullptr, @@ -76,6 +84,10 @@ int outputRetention() const; void setOutputRetention(int retention); + bool autoRotationSupported() const; + bool orientationSensorAvailable() const; + bool tabletModeAvailable() const; + Q_INVOKABLE void forceSave(); void doSave(bool force); @@ -90,6 +102,9 @@ void outputReplicationSupportedChanged(); void globalScaleChanged(); void outputRetentionChanged(); + void autoRotationSupportedChanged(); + void orientationSensorAvailableChanged(); + void tabletModeAvailableChanged(); void dangerousSave(); void errorOnSave(); void globalScaleWritten(); @@ -107,6 +122,7 @@ std::unique_ptr m_outputIdentifier; std::unique_ptr m_config; + OrientationSensor *m_orientationSensor; bool m_backendReady = false; bool m_screenNormalized = true; double m_globalScale = 1.; diff --git a/kcm/kcm.cpp b/kcm/kcm.cpp --- a/kcm/kcm.cpp +++ b/kcm/kcm.cpp @@ -21,6 +21,7 @@ #include "output_identifier.h" #include "output_model.h" #include "../common/control.h" +#include "../common/orientation_sensor.h" #include #include @@ -70,22 +71,34 @@ m_loadCompressor->setInterval(1000); m_loadCompressor->setSingleShot(true); connect (m_loadCompressor, &QTimer::timeout, this, &KCMKScreen::load); + + m_orientationSensor = new OrientationSensor(this); + connect(m_orientationSensor, &OrientationSensor::availableChanged, + this, &KCMKScreen::orientationSensorAvailableChanged); } void KCMKScreen::configReady(ConfigOperation *op) { qCDebug(KSCREEN_KCM) << "Reading in config now."; - if (op->hasError()) { m_config.reset(); Q_EMIT backendError(); return; } - m_config->setConfig(qobject_cast(op)->config()); + + KScreen::ConfigPtr config = qobject_cast(op)->config(); + const bool autoRotationSupported = + config->supportedFeatures() & (KScreen::Config::Feature::AutoRotation + | KScreen::Config::Feature::TabletMode); + m_orientationSensor->setEnabled(autoRotationSupported); + + m_config->setConfig(config); setBackendReady(true); Q_EMIT perOutputScalingChanged(); Q_EMIT primaryOutputSupportedChanged(); Q_EMIT outputReplicationSupportedChanged(); + Q_EMIT tabletModeAvailableChanged(); + Q_EMIT autoRotationSupportedChanged(); } void KCMKScreen::forceSave() @@ -159,7 +172,7 @@ setNeedsSave(false); return; } - m_config->updateInitialConfig(); + m_config->updateInitialData(); } ); } @@ -239,6 +252,28 @@ OutputReplication); } +bool KCMKScreen::autoRotationSupported() const +{ + if (!m_config || !m_config->config()) { + return false; + } + return m_config->config()->supportedFeatures() & (KScreen::Config::Feature::AutoRotation + | KScreen::Config::Feature::TabletMode); +} + +bool KCMKScreen::orientationSensorAvailable() const +{ + return m_orientationSensor->available(); +} + +bool KCMKScreen::tabletModeAvailable() const +{ + if (!m_config || !m_config->config()) { + return false; + } + return m_config->config()->tabletModeAvailable(); +} + void KCMKScreen::setScreenNormalized(bool normalized) { if (m_screenNormalized == normalized) { diff --git a/kcm/output_model.h b/kcm/output_model.h --- a/kcm/output_model.h +++ b/kcm/output_model.h @@ -30,12 +30,15 @@ public: enum OutputRoles { EnabledRole = Qt::UserRole + 1, + InternalRole, PrimaryRole, SizeRole, /** Position in the graphical view relative to some arbitrary but fixed origin. */ PositionRole, /** Position for backend relative to most northwest display corner. */ NormalizedPositionRole, + AutoRotateRole, + AutoRotateOnlyInTabletModeRole, RotationRole, ScaleRole, ResolutionIndexRole, @@ -113,6 +116,8 @@ bool setResolution(int outputIndex, int resIndex); bool setRefreshRate(int outputIndex, int refIndex); bool setRotation(int outputIndex, KScreen::Output::Rotation rotation); + bool setAutoRotate(int outputIndex, bool value); + bool setAutoRotateOnlyInTabletMode(int outputIndex, bool value); int resolutionIndex(const KScreen::OutputPtr &output) const; int refreshRateIndex(const KScreen::OutputPtr &output) const; diff --git a/kcm/output_model.cpp b/kcm/output_model.cpp --- a/kcm/output_model.cpp +++ b/kcm/output_model.cpp @@ -49,14 +49,20 @@ return Utils::outputName(output); case EnabledRole: return output->isEnabled(); + case InternalRole: + return output->type() == KScreen::Output::Type::Panel; case PrimaryRole: return output->isPrimary(); case SizeRole: return output->geometry().size(); case PositionRole: return m_outputs[index.row()].pos; case NormalizedPositionRole: return output->geometry().topLeft(); + case AutoRotateRole: + return m_config->autoRotate(output); + case AutoRotateOnlyInTabletModeRole: + return m_config->autoRotateOnlyInTabletMode(output); case RotationRole: return output->rotation(); case ScaleRole: @@ -133,6 +139,16 @@ return setRefreshRate(index.row(), value.toInt()); } break; + case AutoRotateRole: + if (value.canConvert()) { + return setAutoRotate(index.row(), value.value()); + } + break; + case AutoRotateOnlyInTabletModeRole: + if (value.canConvert()) { + return setAutoRotateOnlyInTabletMode(index.row(), value.value()); + } + break; case RotationRole: if (value.canConvert()) { return setRotation(index.row(), @@ -162,10 +178,13 @@ QHash OutputModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles[EnabledRole] = "enabled"; + roles[InternalRole] = "internal"; roles[PrimaryRole] = "primary"; roles[SizeRole] = "size"; roles[PositionRole] = "position"; roles[NormalizedPositionRole] = "normalizedPosition"; + roles[AutoRotateRole] = "autoRotate"; + roles[AutoRotateOnlyInTabletModeRole] = "autoRotateOnlyInTabletMode"; roles[RotationRole] = "rotation"; roles[ScaleRole] = "scale"; roles[ResolutionIndexRole] = "resolutionIndex"; @@ -366,6 +385,34 @@ return true; } +bool OutputModel::setAutoRotate(int outputIndex, bool value) +{ + Output &output = m_outputs[outputIndex]; + + if (m_config->autoRotate(output.ptr) == value) { + return false; + } + m_config->setAutoRotate(output.ptr, value); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {AutoRotateRole}); + return true; +} + +bool OutputModel::setAutoRotateOnlyInTabletMode(int outputIndex, bool value) +{ + Output &output = m_outputs[outputIndex]; + + if (m_config->autoRotateOnlyInTabletMode(output.ptr) == value) { + return false; + } + m_config->setAutoRotateOnlyInTabletMode(output.ptr, value); + + QModelIndex index = createIndex(outputIndex, 0); + Q_EMIT dataChanged(index, index, {AutoRotateOnlyInTabletModeRole}); + return true; +} + bool OutputModel::setRotation(int outputIndex, KScreen::Output::Rotation rotation) { const Output &output = m_outputs[outputIndex]; diff --git a/kcm/package/contents/ui/Orientation.qml b/kcm/package/contents/ui/Orientation.qml --- a/kcm/package/contents/ui/Orientation.qml +++ b/kcm/package/contents/ui/Orientation.qml @@ -19,24 +19,64 @@ import QtQuick.Controls 2.3 as Controls import org.kde.kirigami 2.4 as Kirigami -RowLayout { - id: orientation - Kirigami.FormData.label: i18n("Orientation:") - - Controls.ButtonGroup { - buttons: orientation.children - } - - RotationButton { - value: 0 - } - RotationButton { - value: 90 - } - RotationButton { - value: 180 - } - RotationButton { - value: 270 - } +ColumnLayout { + Kirigami.FormData.label: i18n("Orientation:") + Kirigami.FormData.buddyFor: autoRotateRadio + spacing: Kirigami.Units.smallSpacing + + ColumnLayout { + id: autoRotateColumn + + // TODO: Make this dependend on tablet mode being available + enabled: kcm.orientationSensorAvailable && element.internal + visible: kcm.autoRotationSupported + + ColumnLayout { + Controls.RadioButton { + id: autoRotateRadio + text: i18n("Automatic") + checked: autoRotateColumn.enabled && element.autoRotate + onClicked: element.autoRotate = true + } + + Controls.CheckBox { + id: autoRotateOnlyInTabletMode + Layout.leftMargin: Kirigami.Units.largeSpacing + + text: i18n("Only when in tablet mode.") + enabled: autoRotateRadio.checked + checked: enabled && element.autoRotateOnlyInTabletMode + onClicked: element.autoRotateOnlyInTabletMode = checked + } + } + + Controls.RadioButton { + id: manualRotateRadio + text: i18n("Manual") + checked: !element.autoRotate || !autoRotateColumn.enabled + onClicked: element.autoRotate = false + } + } + + RowLayout { + id: orientation + enabled: !element.autoRotate || !autoRotateColumn.enabled || !autoRotateColumn.visible + + Controls.ButtonGroup { + buttons: orientation.children + } + + RotationButton { + value: 0 + } + RotationButton { + value: 90 + } + RotationButton { + value: 180 + } + RotationButton { + value: 270 + } + } } diff --git a/kded/config.cpp b/kded/config.cpp --- a/kded/config.cpp +++ b/kded/config.cpp @@ -83,7 +83,11 @@ if (!m_control->getAutoRotate(output)) { continue; } - if (Output::updateOrientation(output, orientation)) { + auto finalOrientation = orientation; + if (m_control->getAutoRotateOnlyInTabletMode(output) && !m_data->tabletModeEngaged()) { + finalOrientation = QOrientationReading::Orientation::TopUp; + } + if (Output::updateOrientation(output, finalOrientation)) { // TODO: call Layouter to find fitting positions for other outputs again return; }