diff --git a/CMakeLists.txt b/CMakeLists.txt index 07082f2..eed0f87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,60 +1,61 @@ cmake_minimum_required(VERSION 3.0) project(KScreen) set(PROJECT_VERSION "5.13.80") set(KSCREEN_VERSION ${PROJECT_VERSION}) add_definitions("-DKSCREEN_VERSION=\"${KSCREEN_VERSION}\"") add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_ASCII -DQT_STRICT_ITERATORS -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY ) set(QT_MIN_VERSION "5.9.0") set(KF5_MIN_VERSION "5.42.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMQtDeclareLoggingCategory) include(FeatureSummary) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS QuickWidgets Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS DBusAddons Config ConfigWidgets I18n XmlGui GlobalAccel WidgetsAddons Declarative IconThemes Plasma ) # WARNING PlasmaQuick provides unversioned CMake config find_package(KF5 REQUIRED COMPONENTS PlasmaQuick) set(MIN_LIBKSCREEN_VERSION "5.2.91") find_package(KF5Screen ${MIN_LIBKSCREEN_VERSION} REQUIRED) add_subdirectory(icons) add_subdirectory(kcm) add_subdirectory(kded) +add_subdirectory(plasmoid) add_subdirectory(tests) add_subdirectory(console) install( FILES kscreen.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/kded/CMakeLists.txt b/kded/CMakeLists.txt index 3d7eb67..6adf2dc 100644 --- a/kded/CMakeLists.txt +++ b/kded/CMakeLists.txt @@ -1,53 +1,54 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kscreen\") include_directories(${CMAKE_CURRENT_BINARY_DIR}/../ ${CMAKE_SOURCE_DIR}/kcm/src) set(kscreen_daemon_SRCS daemon.cpp serializer.cpp generator.cpp device.cpp osd.cpp osdmanager.cpp + osdaction.cpp ${CMAKE_SOURCE_DIR}/kcm/src/utils.cpp ) ecm_qt_declare_logging_category(kscreen_daemon_SRCS HEADER kscreen_daemon_debug.h IDENTIFIER KSCREEN_KDED CATEGORY_NAME kscreen.kded) qt5_add_dbus_interface(kscreen_daemon_SRCS org.freedesktop.DBus.Properties.xml freedesktop_interface) qt5_add_dbus_adaptor(kscreen_daemon_SRCS org.kde.KScreen.xml daemon.h KScreenDaemon ) add_library(kscreen MODULE ${kscreen_daemon_SRCS}) target_link_libraries(kscreen Qt5::Widgets Qt5::DBus Qt5::Quick KF5::Declarative KF5::Screen KF5::DBusAddons KF5::I18n KF5::XmlGui KF5::GlobalAccel) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kscreen.desktop.cmake ${CMAKE_CURRENT_BINARY_DIR}/kscreen.desktop @ONLY) kcoreaddons_desktop_to_json(kscreen ${CMAKE_CURRENT_BINARY_DIR}/kscreen.desktop) install(TARGETS kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kded) set(QML_FILES qml/Osd.qml qml/OsdItem.qml qml/OsdSelector.qml qml/OutputIdentifier.qml ) install(FILES ${QML_FILES} DESTINATION ${KDE_INSTALL_DATADIR}/kded_kscreen/qml) diff --git a/kded/daemon.cpp b/kded/daemon.cpp index 74dcf75..04f88a6 100644 --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -1,443 +1,460 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright 2016 by Sebastian Kügler * + * Copyright (c) 2018 Kai Uwe Broulik * + * Work sponsored by the LiMux project of * + * the city of Munich. * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "daemon.h" #include "serializer.h" #include "generator.h" #include "device.h" #include "kscreenadaptor.h" #include "kscreen_daemon_debug.h" #include "osdmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KScreenDaemonFactory, "kscreen.json", registerPlugin();) KScreenDaemon::KScreenDaemon(QObject* parent, const QList< QVariant >& ) : KDEDModule(parent) , m_monitoredConfig(nullptr) , m_monitoring(false) , m_changeCompressor(new QTimer(this)) , m_saveTimer(nullptr) , m_lidClosedTimer(new QTimer(this)) { KScreen::Log::instance(); QMetaObject::invokeMethod(this, "requestConfig", Qt::QueuedConnection); } void KScreenDaemon::requestConfig() { connect(new KScreen::GetConfigOperation, &KScreen::GetConfigOperation::finished, this, &KScreenDaemon::configReady); } void KScreenDaemon::configReady(KScreen::ConfigOperation* op) { if (op->hasError()) { return; } m_monitoredConfig = qobject_cast(op)->config(); qCDebug(KSCREEN_KDED) << "Config" << m_monitoredConfig.data() << "is ready"; KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig); init(); } KScreenDaemon::~KScreenDaemon() { Generator::destroy(); Device::destroy(); } void KScreenDaemon::init() { KActionCollection *coll = new KActionCollection(this); QAction* action = coll->addAction(QStringLiteral("display")); action->setText(i18n("Switch Display" )); QList switchDisplayShortcuts({Qt::Key_Display, Qt::MetaModifier + Qt::Key_P}); KGlobalAccel::self()->setGlobalShortcut(action, switchDisplayShortcuts); connect(action, &QAction::triggered, this, &KScreenDaemon::displayButton); new KScreenAdaptor(this); // Initialize OSD manager to register its dbus interface m_osdManager = new KScreen::OsdManager(this); m_changeCompressor->setInterval(10); m_changeCompressor->setSingleShot(true); connect(m_changeCompressor, &QTimer::timeout, this, &KScreenDaemon::applyConfig); m_lidClosedTimer->setInterval(1000); m_lidClosedTimer->setSingleShot(true); connect(m_lidClosedTimer, &QTimer::timeout, this, &KScreenDaemon::lidClosedTimeout); connect(Device::self(), &Device::lidClosedChanged, this, &KScreenDaemon::lidClosedChanged); connect(Device::self(), &Device::resumingFromSuspend, this, [&]() { KScreen::Log::instance()->setContext(QStringLiteral("resuming")); qCDebug(KSCREEN_KDED) << "Resumed from suspend, checking for screen changes"; // We don't care about the result, we just want to force the backend // to query XRandR so that it will detect possible changes that happened // while the computer was suspended, and will emit the change events. new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID, this); }); connect(Device::self(), &Device::aboutToSuspend, this, [&]() { qCDebug(KSCREEN_KDED) << "System is going to suspend, won't be changing config (waited for " << (m_lidClosedTimer->interval() - m_lidClosedTimer->remainingTime()) << "ms)"; m_lidClosedTimer->stop(); }); connect(Generator::self(), &Generator::ready, this, &KScreenDaemon::applyConfig); Generator::self()->setCurrentConfig(m_monitoredConfig); monitorConnectedChange(); } void KScreenDaemon::doApplyConfig(const KScreen::ConfigPtr& config) { qCDebug(KSCREEN_KDED) << "doApplyConfig()"; setMonitorForChanges(false); m_monitoredConfig = config; KScreen::ConfigMonitor::instance()->addConfig(m_monitoredConfig); connect(new KScreen::SetConfigOperation(config), &KScreen::SetConfigOperation::finished, this, [&]() { qCDebug(KSCREEN_KDED) << "Config applied"; setMonitorForChanges(true); }); } void KScreenDaemon::applyConfig() { qCDebug(KSCREEN_KDED) << "Applying config"; if (Serializer::configExists(m_monitoredConfig)) { applyKnownConfig(); return; } applyIdealConfig(); } void KScreenDaemon::applyKnownConfig() { const QString configId = Serializer::configId(m_monitoredConfig); qCDebug(KSCREEN_KDED) << "Applying known config" << configId; // We may look for a config that has been set when the lid was closed, Bug: 353029 if (Device::self()->isLaptop() && !Device::self()->isLidClosed()) { Serializer::moveConfig(configId + QLatin1String("_lidOpened"), configId); } KScreen::ConfigPtr config = Serializer::config(m_monitoredConfig, configId); // It's possible that the Serializer returned a nullptr if (!config || !KScreen::Config::canBeApplied(config, KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen)) { applyIdealConfig(); return; } doApplyConfig(config); } +void KScreenDaemon::applyLayoutPreset(const QString &presetName) +{ + const QMetaEnum actionEnum = QMetaEnum::fromType(); + Q_ASSERT(actionEnum.isValid()); + + bool ok; + auto action = static_cast(actionEnum.keyToValue(qPrintable(presetName), &ok)); + if (!ok) { + qCWarning(KSCREEN_KDED) << "Cannot apply unknown screen layout preset named" << presetName; + return; + } + applyOsdAction(action); +} + void KScreenDaemon::applyOsdAction(KScreen::OsdAction::Action action) { switch (action) { case KScreen::OsdAction::NoAction: qCDebug(KSCREEN_KDED) << "OSD: no action"; return; case KScreen::OsdAction::SwitchToInternal: qCDebug(KSCREEN_KDED) << "OSD: switch to internal"; doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffExternal)); return; case KScreen::OsdAction::SwitchToExternal: qCDebug(KSCREEN_KDED) << "OSD: switch to external"; doApplyConfig(Generator::self()->displaySwitch(Generator::TurnOffEmbedded)); return; case KScreen::OsdAction::ExtendLeft: qCDebug(KSCREEN_KDED) << "OSD: extend left"; doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToLeft)); return; case KScreen::OsdAction::ExtendRight: qCDebug(KSCREEN_KDED) << "OSD: extend right"; doApplyConfig(Generator::self()->displaySwitch(Generator::ExtendToRight)); return; case KScreen::OsdAction::Clone: qCDebug(KSCREEN_KDED) << "OSD: clone"; doApplyConfig(Generator::self()->displaySwitch(Generator::Clone)); return; } Q_UNREACHABLE(); } void KScreenDaemon::applyIdealConfig() { if (m_monitoredConfig->connectedOutputs().count() < 2) { m_osdManager->hideOsd(); doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig)); } else { qCDebug(KSCREEN_KDED) << "Getting ideal config from user via OSD..."; auto action = m_osdManager->showActionSelector(); connect(action, &KScreen::OsdAction::selected, this, &KScreenDaemon::applyOsdAction); } } void logConfig(const KScreen::ConfigPtr &config) { if (config) { foreach (auto o, config->outputs()) { if (o->isConnected()) { qCDebug(KSCREEN_KDED) << o; } } } } void KScreenDaemon::configChanged() { qCDebug(KSCREEN_KDED) << "Change detected"; logConfig(m_monitoredConfig); // Modes may have changed, fix-up current mode id Q_FOREACH(const KScreen::OutputPtr &output, m_monitoredConfig->outputs()) { if (output->isConnected() && output->isEnabled() && output->currentMode().isNull()) { qCDebug(KSCREEN_KDED) << "Current mode" << output->currentModeId() << "invalid, setting preferred mode" << output->preferredModeId(); output->setCurrentModeId(output->preferredModeId()); doApplyConfig(m_monitoredConfig); } } // Reset timer, delay the writeback if (!m_saveTimer) { m_saveTimer = new QTimer(this); m_saveTimer->setInterval(300); m_saveTimer->setSingleShot(true); connect(m_saveTimer, &QTimer::timeout, this, &KScreenDaemon::saveCurrentConfig); } m_saveTimer->start(); } void KScreenDaemon::saveCurrentConfig() { qCDebug(KSCREEN_KDED) << "Saving current config to file"; // We assume the config is valid, since it's what we got, but we are interested // in the "at least one enabled screen" check const bool valid = KScreen::Config::canBeApplied(m_monitoredConfig, KScreen::Config::ValidityFlag::RequireAtLeastOneEnabledScreen); if (valid) { Serializer::saveConfig(m_monitoredConfig, Serializer::configId(m_monitoredConfig)); logConfig(m_monitoredConfig); } else { qCWarning(KSCREEN_KDED) << "Config does not have at least one screen enabled, WILL NOT save this config, this is not what user wants."; logConfig(m_monitoredConfig); } } void KScreenDaemon::showOsd(const QString &icon, const QString &text) { QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1Literal("org.kde.plasmashell"), QLatin1Literal("/org/kde/osdService"), QLatin1Literal("org.kde.osdService"), QLatin1Literal("showText") ); msg << icon << text; QDBusConnection::sessionBus().asyncCall(msg); } void KScreenDaemon::showOutputIdentifier() { m_osdManager->showOutputIdentifiers(); } void KScreenDaemon::displayButton() { qCDebug(KSCREEN_KDED) << "displayBtn triggered"; auto action = m_osdManager->showActionSelector(); connect(action, &KScreen::OsdAction::selected, this, &KScreenDaemon::applyOsdAction); } void KScreenDaemon::lidClosedChanged(bool lidIsClosed) { // Ignore this when we don't have any external monitors, we can't turn off our // only screen if (m_monitoredConfig->connectedOutputs().count() == 1) { return; } if (lidIsClosed) { // Lid is closed, now we wait for couple seconds to find out whether it // will trigger a suspend (see Device::aboutToSuspend), or whether we should // turn off the screen qCDebug(KSCREEN_KDED) << "Lid closed, waiting to see if the computer goes to sleep..."; m_lidClosedTimer->start(); return; } else { qCDebug(KSCREEN_KDED) << "Lid opened!"; // We should have a config with "_lidOpened" suffix lying around. If not, // then the configuration has changed while the lid was closed and we just // use applyConfig() and see what we can do ... const QString openConfigId = Serializer::configId(m_monitoredConfig) + QLatin1String("_lidOpened"); if (Serializer::configExists(openConfigId)) { const KScreen::ConfigPtr openedConfig = Serializer::config(m_monitoredConfig, openConfigId); Serializer::removeConfig(openConfigId); doApplyConfig(openedConfig); } } } void KScreenDaemon::lidClosedTimeout() { // Make sure nothing has changed in the past second... :-) if (!Device::self()->isLidClosed()) { return; } // If we are here, it means that closing the lid did not result in suspend // action. // FIXME: This could be because the suspend took longer than m_lidClosedTimer // timeout. Ideally we need to be able to look into PowerDevil config to see // what's the configured action for lid events, but there's no API to do that // and I'm not parsing PowerDevil's configs... qCDebug(KSCREEN_KDED) << "Lid closed without system going to suspend -> turning off the screen"; for (KScreen::OutputPtr &output : m_monitoredConfig->outputs()) { if (output->type() == KScreen::Output::Panel) { if (output->isConnected() && output->isEnabled()) { // Save the current config with opened lid, just so that we know // how to restore it later const QString configId = Serializer::configId(m_monitoredConfig) + QLatin1String("_lidOpened"); Serializer::saveConfig(m_monitoredConfig, configId); disableOutput(m_monitoredConfig, output); doApplyConfig(m_monitoredConfig); return; } } } } void KScreenDaemon::outputConnectedChanged() { if (!m_changeCompressor->isActive()) { m_changeCompressor->start(); } KScreen::Output *output = qobject_cast(sender()); qCDebug(KSCREEN_KDED) << "outputConnectedChanged():" << output->name(); if (output->isConnected()) { Q_EMIT outputConnected(output->name()); if (!Serializer::configExists(m_monitoredConfig)) { Q_EMIT unknownOutputConnected(output->name()); } } } void KScreenDaemon::monitorConnectedChange() { KScreen::OutputList outputs = m_monitoredConfig->outputs(); Q_FOREACH(const KScreen::OutputPtr &output, outputs) { connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); } connect(m_monitoredConfig.data(), &KScreen::Config::outputAdded, this, [this] (const KScreen::OutputPtr output) { if (output->isConnected()) { m_changeCompressor->start(); } connect(output.data(), &KScreen::Output::isConnectedChanged, this, &KScreenDaemon::outputConnectedChanged, Qt::UniqueConnection); }, Qt::UniqueConnection ); connect(m_monitoredConfig.data(), &KScreen::Config::outputRemoved, this, &KScreenDaemon::applyConfig, static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); } void KScreenDaemon::setMonitorForChanges(bool enabled) { if (m_monitoring == enabled) { return; } qCDebug(KSCREEN_KDED) << "Monitor for changes: " << enabled; m_monitoring = enabled; if (m_monitoring) { connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged, Qt::UniqueConnection); } else { disconnect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenDaemon::configChanged); } } void KScreenDaemon::disableOutput(KScreen::ConfigPtr &config, KScreen::OutputPtr &output) { const QRect geom = output->geometry(); qCDebug(KSCREEN_KDED) << "Laptop geometry:" << geom << output->pos() << (output->currentMode() ? output->currentMode()->size() : QSize()); // Move all outputs right from the @p output to left for (KScreen::OutputPtr &otherOutput : config->outputs()) { if (otherOutput == output || !otherOutput->isConnected() || !otherOutput->isEnabled()) { continue; } QPoint otherPos = otherOutput->pos(); if (otherPos.x() >= geom.right() && otherPos.y() >= geom.top() && otherPos.y() <= geom.bottom()) { otherPos.setX(otherPos.x() - geom.width()); } qCDebug(KSCREEN_KDED) << "Moving" << otherOutput->name() << "from" << otherOutput->pos() << "to" << otherPos; otherOutput->setPos(otherPos); } // Disable the output output->setEnabled(false); } #include "daemon.moc" diff --git a/kded/daemon.h b/kded/daemon.h index be32894..46c7a12 100644 --- a/kded/daemon.h +++ b/kded/daemon.h @@ -1,84 +1,87 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * * * 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, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef KSCREEN_DAEMON_H #define KSCREEN_DAEMON_H #include #include #include #include "generator.h" #include "osdmanager.h" class QTimer; namespace KScreen { class ConfigOperation; } class Q_DECL_EXPORT KScreenDaemon : public KDEDModule { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KScreen") public: KScreenDaemon(QObject *parent, const QList&); ~KScreenDaemon() override; public Q_SLOTS: virtual void requestConfig(); void configReady(KScreen::ConfigOperation *op); void init(); void applyConfig(); void applyKnownConfig(); void applyIdealConfig(); void configChanged(); void saveCurrentConfig(); void displayButton(); void lidClosedChanged(bool lidIsClosed); void lidClosedTimeout(); void setMonitorForChanges(bool enabled); void outputConnectedChanged(); void showOutputIdentifier(); void applyOsdAction(KScreen::OsdAction::Action action); + // DBus + void applyLayoutPreset(const QString &presetName); Q_SIGNALS: + // DBus void outputConnected(const QString &outputName); void unknownOutputConnected(const QString &outputName); protected: virtual void doApplyConfig(const KScreen::ConfigPtr &config); void monitorConnectedChange(); void disableOutput(KScreen::ConfigPtr &config, KScreen::OutputPtr &output); void showOsd(const QString &icon, const QString &text); KScreen::ConfigPtr m_monitoredConfig; bool m_monitoring; QTimer* m_changeCompressor; QTimer* m_saveTimer; QTimer* m_lidClosedTimer; KScreen::OsdManager *m_osdManager; }; #endif /*KSCREEN_DAEMON_H*/ diff --git a/kded/org.kde.KScreen.xml b/kded/org.kde.KScreen.xml index 5bac83c..23f9933 100644 --- a/kded/org.kde.KScreen.xml +++ b/kded/org.kde.KScreen.xml @@ -1,12 +1,15 @@ + + + \ No newline at end of file diff --git a/kded/osdaction.cpp b/kded/osdaction.cpp new file mode 100644 index 0000000..1b4f432 --- /dev/null +++ b/kded/osdaction.cpp @@ -0,0 +1,76 @@ +/* + * Copyright 2016 Sebastian Kügler + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "osdaction.h" + +#include + +using namespace KScreen; + +OsdAction::OsdAction(QObject *parent) + : QObject(parent) +{ +} + +QVector OsdAction::actionOrder() const +{ + return { + SwitchToExternal, + SwitchToInternal, + Clone, + ExtendLeft, + ExtendRight, + NoAction + }; +} + +QString OsdAction::actionLabel(OsdAction::Action action) const +{ + switch (action) { + // this is built by both daemon and plasmoid, needs explicit translation domain here + case SwitchToExternal: return i18nd("kscreen", "Switch to external screen"); + case SwitchToInternal: return i18nd("kscreen", "Switch to laptop screen"); + case Clone: return i18nd("kscreen", "Unify outputs"); + case ExtendLeft: return i18nd("kscreen", "Extend to left"); + case ExtendRight: return i18nd("kscreen", "Extend to right"); + case NoAction: return i18nd("kscreen", "Leave unchanged"); + } + + Q_UNREACHABLE(); + return QString(); +} + +QString OsdAction::actionIconName(OsdAction::Action action) const +{ + switch (action) { + case SwitchToExternal: return QStringLiteral("osd-shutd-laptop"); + case SwitchToInternal: return QStringLiteral("osd-shutd-screen"); + case Clone: return QStringLiteral("osd-duplicate"); + case ExtendLeft: return QStringLiteral("osd-sbs-left"); + case ExtendRight: return QStringLiteral("osd-sbs-sright"); + case NoAction: return QStringLiteral("dialog-cancel"); + } + + Q_UNREACHABLE(); + return QString(); +} + +#include "osdaction.moc" diff --git a/kded/osdmanager.h b/kded/osdaction.h similarity index 63% copy from kded/osdmanager.h copy to kded/osdaction.h index 68748f9..6a7328e 100644 --- a/kded/osdmanager.h +++ b/kded/osdaction.h @@ -1,78 +1,55 @@ /* * Copyright 2016 Sebastian Kügler + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef OSDM_H -#define OSDM_H +#pragma once #include #include -#include -#include - -class QmlObject; +#include namespace KScreen { -class ConfigOperation; -class Osd; -class Output; - class OsdAction : public QObject { Q_OBJECT public: enum Action { NoAction, SwitchToExternal, SwitchToInternal, Clone, ExtendLeft, ExtendRight }; Q_ENUM(Action) -Q_SIGNALS: - void selected(Action action); - -protected: explicit OsdAction(QObject *parent = nullptr); -}; + Q_INVOKABLE QVector actionOrder() const; + Q_INVOKABLE QString actionLabel(Action action) const; + Q_INVOKABLE QString actionIconName(Action action) const; -class OsdManager : public QObject { - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "org.kde.kscreen.osdService") - -public: - OsdManager(QObject *parent = nullptr); - ~OsdManager() override; - -public Q_SLOTS: - void showOutputIdentifiers(); - void showOsd(const QString &icon, const QString &text); - void hideOsd(); - OsdAction *showActionSelector(); +Q_SIGNALS: + void selected(Action action); -private: - void slotIdentifyOutputs(KScreen::ConfigOperation *op); - QMap m_osds; - QTimer* m_cleanupTimer; }; -} // ns -#endif // OSDM_H +} // namespace KScreen diff --git a/kded/osdmanager.cpp b/kded/osdmanager.cpp index 2dabf00..9c6a116 100644 --- a/kded/osdmanager.cpp +++ b/kded/osdmanager.cpp @@ -1,211 +1,208 @@ /* * Copyright 2016 Sebastian Kügler * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "osdmanager.h" #include "osd.h" #include "kscreen_daemon_debug.h" #include #include #include #include #include namespace KScreen { -OsdAction::OsdAction(QObject *parent) - : QObject(parent) -{ -} - class OsdActionImpl : public OsdAction { Q_OBJECT public: OsdActionImpl(QObject *parent = nullptr) : OsdAction(parent) {} void setOsd(Osd *osd) { connect(osd, &Osd::osdActionSelected, this, [this](Action action) { Q_EMIT selected(action); deleteLater(); }); } }; OsdManager::OsdManager(QObject *parent) : QObject(parent) , m_cleanupTimer(new QTimer(this)) { - qmlRegisterUncreatableType("org.kde.KScreen", 1, 0, "OsdAction", QStringLiteral("You cannot create OsdAction")); + qmlRegisterSingletonType("org.kde.KScreen", 1, 0, "OsdAction", [](QQmlEngine *, QJSEngine *) -> QObject* { + return new KScreen::OsdAction(); + }); // free up memory when the osd hasn't been used for more than 1 minute m_cleanupTimer->setInterval(60000); m_cleanupTimer->setSingleShot(true); connect(m_cleanupTimer, &QTimer::timeout, this, [this]() { hideOsd(); }); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kscreen.osdService")); if (!QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/kde/kscreen/osdService"), this, QDBusConnection::ExportAllSlots)) { qCWarning(KSCREEN_KDED) << "Failed to registerObject"; } } void OsdManager::hideOsd() { qDeleteAll(m_osds); m_osds.clear(); } OsdManager::~OsdManager() { } void OsdManager::showOutputIdentifiers() { connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, &OsdManager::slotIdentifyOutputs); } void OsdManager::slotIdentifyOutputs(KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } auto osd = m_osds.value(output->name()); if (!osd) { osd = new KScreen::Osd(output, this); m_osds.insert(output->name(), osd); } osd->showOutputIdentifier(output); } m_cleanupTimer->start(); } void OsdManager::showOsd(const QString& icon, const QString& text) { hideOsd(); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this, icon, text] (KScreen::ConfigOperation *op) { if (op->hasError()) { return; } const KScreen::ConfigPtr config = qobject_cast(op)->config(); Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } auto osd = m_osds.value(output->name()); if (!osd) { osd = new KScreen::Osd(output, this); m_osds.insert(output->name(), osd); } osd->showGenericOsd(icon, text); } m_cleanupTimer->start(); } ); } OsdAction *OsdManager::showActionSelector() { hideOsd(); OsdActionImpl *action = new OsdActionImpl(this); connect(action, &OsdActionImpl::selected, this, [this]() { for (auto osd : qAsConst(m_osds)) { osd->hideOsd(); } }); connect(new KScreen::GetConfigOperation(), &KScreen::GetConfigOperation::finished, this, [this, action](const KScreen::ConfigOperation *op) { if (op->hasError()) { qCWarning(KSCREEN_KDED) << op->errorString(); return; } // Show selector on alll enabled screens const auto outputs = op->config()->outputs(); KScreen::OutputPtr osdOutput; for (const auto &output : outputs) { if (!output->isConnected() || !output->isEnabled() || !output->currentMode()) { continue; } // Prefer laptop screen if (output->type() == KScreen::Output::Panel) { osdOutput = output; break; } // Fallback to primary if (output->isPrimary()) { osdOutput = output; break; } } // no laptop or primary screen, just take the first usable one if (!osdOutput) { for (const auto &output : outputs) { if (output->isConnected() && output->isEnabled() && output->currentMode()) { osdOutput = output; break; } } } if (!osdOutput) { // huh!? return; } KScreen::Osd* osd = nullptr; if (m_osds.contains(osdOutput->name())) { osd = m_osds.value(osdOutput->name()); } else { osd = new KScreen::Osd(osdOutput, this); m_osds.insert(osdOutput->name(), osd); } action->setOsd(osd); osd->showActionSelector(); m_cleanupTimer->start(); } ); return action; } } #include "osdmanager.moc" diff --git a/kded/osdmanager.h b/kded/osdmanager.h index 68748f9..4a23a8d 100644 --- a/kded/osdmanager.h +++ b/kded/osdmanager.h @@ -1,78 +1,58 @@ /* * Copyright 2016 Sebastian Kügler * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OSDM_H #define OSDM_H #include #include #include #include +#include "osdaction.h" + class QmlObject; namespace KScreen { class ConfigOperation; class Osd; class Output; -class OsdAction : public QObject -{ - Q_OBJECT -public: - enum Action { - NoAction, - SwitchToExternal, - SwitchToInternal, - Clone, - ExtendLeft, - ExtendRight - }; - Q_ENUM(Action) - -Q_SIGNALS: - void selected(Action action); - -protected: - explicit OsdAction(QObject *parent = nullptr); -}; - - class OsdManager : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kscreen.osdService") public: OsdManager(QObject *parent = nullptr); ~OsdManager() override; public Q_SLOTS: void showOutputIdentifiers(); void showOsd(const QString &icon, const QString &text); void hideOsd(); OsdAction *showActionSelector(); private: void slotIdentifyOutputs(KScreen::ConfigOperation *op); QMap m_osds; QTimer* m_cleanupTimer; }; } // ns #endif // OSDM_H diff --git a/kded/qml/OsdSelector.qml b/kded/qml/OsdSelector.qml index 54de7e2..c8d8f89 100644 --- a/kded/qml/OsdSelector.qml +++ b/kded/qml/OsdSelector.qml @@ -1,179 +1,156 @@ /* * Copyright 2017 Daniel Vrátil * * 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 . */ import QtQuick 2.5 import QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.KScreen 1.0 PlasmaCore.Dialog { id: root location: PlasmaCore.Types.Floating type: PlasmaCore.Dialog.Normal property string infoText signal clicked(int actionId) mainItem: Item { height: Math.min(units.gridUnit * 15, Screen.desktopAvailableHeight / 5) width: buttonRow.width Row { id: buttonRow spacing: theme.defaultFont.pointSize height: parent.height - label.height - ((units.smallSpacing/2) * 3) width: (actionRepeater.count * height) + ((actionRepeater.count - 1) * buttonRow.spacing); Repeater { id: actionRepeater property int currentIndex: 0 - model: [ - { - iconSource: "osd-shutd-laptop", - label: i18n("Switch to external screen"), - action: OsdAction.SwitchToExternal - }, - { - iconSource: "osd-shutd-screen", - label: i18n("Switch to laptop screen"), - action: OsdAction.SwitchToInternal - }, - { - iconSource: "osd-duplicate", - label: i18n("Unify outputs"), - action: OsdAction.Clone - }, - { - iconSource: "osd-sbs-left", - label: i18n("Extend to left"), - action: OsdAction.ExtendLeft - }, - { - iconSource: "osd-sbs-sright", - label: i18n("Extend to right"), - action: OsdAction.ExtendRight - }, - { - iconSource: "dialog-cancel", - label: i18n("Leave unchanged"), - action: OsdAction.NoAction + model: { + return OsdAction.actionOrder().map(function (layout) { + return { + iconSource: OsdAction.actionIconName(layout), + label: OsdAction.actionLabel(layout), + action: layout } - ] + }); + } delegate: PlasmaComponents.Button { property var action: modelData.action Accessible.name: modelData.label PlasmaCore.IconItem { source: modelData.iconSource height: buttonRow.height - ((units.smallSpacing / 2) * 3) width: height anchors.centerIn: parent } height: parent.height width: height onHoveredChanged: { actionRepeater.currentIndex = index } onClicked: root.clicked(action) activeFocusOnTab: true // use checked only indirectly, since its binding will break property bool current: index == actionRepeater.currentIndex onCurrentChanged: { if (current) { checked = true root.infoText = modelData.label forceActiveFocus() } else { checked = false } } onActiveFocusChanged: { if (activeFocus) { actionRepeater.currentIndex = index } } } } } PlasmaExtras.Heading { id: label anchors { bottom: parent.bottom left: parent.left right: parent.right margins: Math.floor(units.smallSpacing / 2) } text: root.infoText horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap maximumLineCount: 2 elide: Text.ElideLeft minimumPointSize: theme.defaultFont.pointSize fontSizeMode: Text.HorizontalFit } Component.onCompleted: print("OsdSelector loaded..."); function next() { var index = actionRepeater.currentIndex + 1 if (index >= actionRepeater.count) { index = 0 } actionRepeater.currentIndex = index } function previous() { var index = actionRepeater.currentIndex - 1 if (index < 0) { index = actionRepeater.count - 1 } actionRepeater.currentIndex = index } Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { event.accepted = true clicked(buttonRow.children[actionRepeater.currentIndex].action) return } if (event.key === Qt.Key_Right) { event.accepted = true next() return } if (event.key === Qt.Key_Left) { event.accepted = true previous() return } if (event.key === Qt.Key_Escape) { event.accepted = true clicked(OsdAction.NoAction) return } } } } diff --git a/plasmoid/CMakeLists.txt b/plasmoid/CMakeLists.txt new file mode 100644 index 0000000..dfb3fed --- /dev/null +++ b/plasmoid/CMakeLists.txt @@ -0,0 +1,21 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.kscreen\") + +set(kscreenapplet_SRCS + kscreenapplet.cpp + ../kded/osdaction.cpp +) + +add_library(plasma_applet_kscreen MODULE ${kscreenapplet_SRCS}) + +kcoreaddons_desktop_to_json(plasma_applet_kscreen package/metadata.desktop) + +target_link_libraries(plasma_applet_kscreen + Qt5::Qml + Qt5::DBus + KF5::I18n + KF5::Plasma + KF5::Screen) + +install(TARGETS plasma_applet_kscreen DESTINATION ${KDE_INSTALL_PLUGINDIR}/plasma/applets) + +plasma_install_package(package org.kde.kscreen) diff --git a/plasmoid/Messages.sh b/plasmoid/Messages.sh new file mode 100755 index 0000000..fc9d42d --- /dev/null +++ b/plasmoid/Messages.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env bash +$XGETTEXT `find . -name \*.qml -o -name \*.cpp` -o $podir/plasma_applet_org.kde.kscreen.pot diff --git a/plasmoid/kscreenapplet.cpp b/plasmoid/kscreenapplet.cpp new file mode 100644 index 0000000..b7f607a --- /dev/null +++ b/plasmoid/kscreenapplet.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 "kscreenapplet.h" + +#include // for qmlRegisterType +#include + +#include +#include + +#include +#include +#include +#include + +#include "../kded/osdaction.h" + +#include + +KScreenApplet::KScreenApplet(QObject *parent, const QVariantList &data) + : Plasma::Applet(parent, data) +{ + +} + +KScreenApplet::~KScreenApplet() = default; + +void KScreenApplet::init() +{ + qmlRegisterSingletonType("org.kde.private.kscreen", 1, 0, "OsdAction", [](QQmlEngine *, QJSEngine *) -> QObject* { + return new KScreen::OsdAction(); + }); + + connect(new KScreen::GetConfigOperation(KScreen::GetConfigOperation::NoEDID), &KScreen::ConfigOperation::finished, + this, [this](KScreen::ConfigOperation *op) { + m_screenConfiguration = qobject_cast(op)->config(); + + KScreen::ConfigMonitor::instance()->addConfig(m_screenConfiguration); + connect(KScreen::ConfigMonitor::instance(), &KScreen::ConfigMonitor::configurationChanged, this, &KScreenApplet::checkOutputs); + + checkOutputs(); + }); +} + +int KScreenApplet::connectedOutputCount() const +{ + return m_connectedOutputCount; +} + +void KScreenApplet::applyLayoutPreset(Action action) +{ + const QMetaEnum actionEnum = QMetaEnum::fromType(); + Q_ASSERT(actionEnum.isValid()); + + const QString presetName = QString::fromLatin1(actionEnum.valueToKey(action)); + if (presetName.isEmpty()) { + return; + } + + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.kded5"), + QStringLiteral("/modules/kscreen"), + QStringLiteral("org.kde.KScreen"), + QStringLiteral("applyLayoutPreset") + ); + + msg.setArguments({presetName}); + + QDBusConnection::sessionBus().call(msg, QDBus::NoBlock); +} + +void KScreenApplet::checkOutputs() +{ + if (!m_screenConfiguration) { + return; + } + + const int oldConnectedOutputCount = m_connectedOutputCount; + + const auto outputs = m_screenConfiguration->outputs(); + m_connectedOutputCount = std::count_if(outputs.begin(), outputs.end(), [](const KScreen::OutputPtr &output) { + return output->isConnected(); + }); + + if (m_connectedOutputCount != oldConnectedOutputCount) { + emit connectedOutputCountChanged(); + } +} + +K_EXPORT_PLASMA_APPLET_WITH_JSON(kscreen, KScreenApplet, "metadata.json") + +#include "kscreenapplet.moc" diff --git a/plasmoid/kscreenapplet.h b/plasmoid/kscreenapplet.h new file mode 100644 index 0000000..58c790c --- /dev/null +++ b/plasmoid/kscreenapplet.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 KScreenApplet : public Plasma::Applet +{ + Q_OBJECT + + /** + * The number of currently connected (not neccessarily enabled) outputs + */ + Q_PROPERTY(int connectedOutputCount READ connectedOutputCount NOTIFY connectedOutputCountChanged) + +public: + explicit KScreenApplet(QObject *parent, const QVariantList &data); + ~KScreenApplet() override; + + enum Action { + SwitchToExternal, + SwitchToInternal, + Clone, + ExtendLeft, + ExtendRight + }; + Q_ENUM(Action) + + void init() override; + + int connectedOutputCount() const; + + Q_INVOKABLE void applyLayoutPreset(Action action); + +signals: + void connectedOutputCountChanged(); + +private: + void checkOutputs(); + + KScreen::ConfigPtr m_screenConfiguration; + int m_connectedOutputCount = 0; + +}; diff --git a/plasmoid/package/contents/ui/PresentationModeItem.qml b/plasmoid/package/contents/ui/PresentationModeItem.qml new file mode 100644 index 0000000..00d9e0f --- /dev/null +++ b/plasmoid/package/contents/ui/PresentationModeItem.qml @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + * + */ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras + +ColumnLayout { + spacing: units.smallSpacing + + PlasmaComponents.CheckBox { + id: checkBox + Layout.fillWidth: true + // Remove spacing between checkbox and the explanatory label below + Layout.bottomMargin: -parent.spacing + text: i18n("Enable Presentation Mode") + + onCheckedChanged: { + if (checked === root.presentationModeEnabled) { + return; + } + + // disable CheckBox while job is running + checkBox.enabled = false; + + var service = pmSource.serviceForSource("PowerDevil"); + + if (checked) { + var op = service.operationDescription("beginSuppressingScreenPowerManagement"); + op.reason = i18n("User enabled presentation mode"); + + var job = service.startOperationCall(op); + job.finished.connect(function (job) { + presentationModeCookie = job.result; + checkBox.enabled = true; + }); + } else { + var op = service.operationDescription("stopSuppressingScreenPowerManagement"); + op.cookie = presentationModeCookie; + + var job = service.startOperationCall(op); + job.finished.connect(function (job) { + presentationModeCookie = -1; + checkBox.enabled = true; + }); + } + } + } + + // so we can align the labels below with the checkbox + PlasmaComponents.CheckBox { + id: checkBoxMetrics + visible: false + } + + PlasmaExtras.DescriptiveLabel { + Layout.fillWidth: true + Layout.leftMargin: checkBoxMetrics.width + font.pointSize: theme.smallestFont.pointSize + text: i18n("This will prevent your screen and computer from turning off automatically.") + wrapMode: Text.WordWrap + } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: checkBoxMetrics.width + spacing: units.smallSpacing + + PlasmaCore.IconItem { + Layout.preferredWidth: units.iconSizes.medium + Layout.preferredHeight: units.iconSizes.medium + source: pmSource.inhibitions[0] ? pmSource.inhibitions[0].Icon || "" : "" + visible: valid + } + + PlasmaComponents.Label { + Layout.fillWidth: true + Layout.maximumWidth: Math.min(units.gridUnit * 20, implicitWidth) + font.pointSize: theme.smallestFont.pointSize + wrapMode: Text.WordWrap + elide: Text.ElideRight + textFormat: Text.PlainText + text: { + var inhibitions = pmSource.inhibitions; + if (inhibitions.length > 1) { + return i18ncp("Some Application and n others enforce presentation mode", + "%2 and %1 other application are enforcing presentation mode.", + "%2 and %1 other applications are enforcing presentation mode.", + inhibitions.length - 1, inhibitions[0].Name) // plural only works on %1 + } else if (inhibitions.length === 1) { + if (!inhibitions[0].Reason) { + return i18nc("Some Application enforce presentation mode", + "%1 is enforcing presentation mode.", inhibitions[0].Name) + } else { + return i18nc("Some Application enforce presentation mode: Reason provided by the app", + "%1 is enforcing presentation mode: %2", inhibitions[0].Name, inhibitions[0].Reason) + } + } else { + return ""; + } + } + } + } +} diff --git a/plasmoid/package/contents/ui/ScreenLayoutSelection.qml b/plasmoid/package/contents/ui/ScreenLayoutSelection.qml new file mode 100644 index 0000000..629d33f --- /dev/null +++ b/plasmoid/package/contents/ui/ScreenLayoutSelection.qml @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + * + */ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras + +ColumnLayout { + spacing: 0 + + states: [ + State { + // only makes sense to offer screen layout setup if there's more than one screen connected + when: plasmoid.nativeInterface.connectedOutputCount < 2 + + PropertyChanges { + target: screenLayoutRow + enabled: false + } + PropertyChanges { + target: noScreenLabel + visible: true + } + } + ] + + PlasmaExtras.Heading { + Layout.fillWidth: true + level: 3 + text: i18n("Screen Layout") + } + + // Screen layout selector section + Row { + id: screenLayoutRow + readonly property int buttonSize: Math.floor((width - spacing * (screenLayoutRepeater.count - 1)) / screenLayoutRepeater.count) + Layout.fillWidth: true + spacing: units.smallSpacing + + Repeater { + id: screenLayoutRepeater + model: root.screenLayouts + + PlasmaComponents.Button { + width: screenLayoutRow.buttonSize + height: width + tooltip: modelData.label + Accessible.name: tooltip + onClicked: plasmoid.nativeInterface.applyLayoutPreset(modelData.action) + + // HACK otherwise the icon won't expand to full button size + PlasmaCore.IconItem { + anchors.centerIn: parent + width: height + // FIXME use proper FrameSvg margins and stuff + height: parent.height - units.smallSpacing + source: modelData.iconName + active: parent.hovered + } + } + } + } + + PlasmaExtras.DescriptiveLabel { + id: noScreenLabel + Layout.fillWidth: true + Layout.maximumWidth: Math.min(units.gridUnit * 20, implicitWidth) + wrapMode: Text.Wrap + text: i18n("You can only apply a different screen layout when there is more than one display device plugged in.") + font.pointSize: theme.smallestFont.pointSize + visible: false + } +} diff --git a/plasmoid/package/contents/ui/main.qml b/plasmoid/package/contents/ui/main.qml new file mode 100644 index 0000000..307b9e6 --- /dev/null +++ b/plasmoid/package/contents/ui/main.qml @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2018 Kai Uwe Broulik + * Work sponsored by the LiMux project of + * the city of Munich. + * + * 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) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * 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 . + * + */ + +import QtQuick 2.8 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kquickcontrolsaddons 2.0 + +import org.kde.private.kscreen 1.0 + +Item { + id: root + + // Only show if there's screen layouts available or the user enabled presentation mode + Plasmoid.status: presentationModeEnabled || plasmoid.nativeInterface.connectedOutputCount > 1 ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.PassiveStatus + Plasmoid.toolTipSubText: presentationModeEnabled ? i18n("Presentation mode is enabled") : "" + + readonly property string kcmName: "kcm_kscreen" + // does this need an ellipsis? + readonly property string kcmLabel: i18nc("Open the full display settings module", "Advanced Display Settings") + readonly property string kcmIconName: "preferences-desktop-display-randr" + readonly property bool kcmAllowed: KCMShell.authorize(kcmName + ".desktop").length > 0 + + readonly property bool presentationModeEnabled: presentationModeCookie > 0 + property int presentationModeCookie: -1 + + readonly property var screenLayouts: { + var layouts = OsdAction.actionOrder().filter(function (layout) { + // We don't want the "No action" item in the plasmoid + return layout !== OsdAction.NoAction; + }); + + layouts.map(function (layout) { + return { + iconName: OsdAction.actionIconName(layout), + label: OsdAction.actionLabel(layout), + action: layout + } + }); + } + + PlasmaCore.DataSource { + id: pmSource + engine: "powermanagement" + connectedSources: ["PowerDevil", "Inhibitions"] + + onSourceAdded: { + disconnectSource(source); + connectSource(source); + } + onSourceRemoved: { + disconnectSource(source); + } + + readonly property var inhibitions: { + var inhibitions = []; + + var data = pmSource.data.Inhibitions; + if (data) { + for (var key in data) { + if (key === "plasmashell" || key === "plasmoidviewer") { // ignore our own inhibition + continue; + } + + inhibitions.push(data[key]); + } + } + + return inhibitions; + } + } + + function action_openKcm() { + KCMShell.open(kcmName); + } + + Component.onCompleted: { + if (kcmAllowed) { + plasmoid.setAction("openKcm", root.kcmLabel, root.kcmIconName) + } + } + + Plasmoid.fullRepresentation: ColumnLayout { + spacing: 0 + Layout.preferredWidth: units.gridUnit * 15 + + ScreenLayoutSelection { + Layout.fillWidth: true + } + + PresentationModeItem { + Layout.fillWidth: true + Layout.topMargin: units.largeSpacing + } + + // compact the layout, push settings button to the bottom + Item { + Layout.fillHeight: true + } + + PlasmaComponents.Button { + Layout.alignment: Qt.AlignRight + Layout.topMargin: units.smallSpacing + text: root.kcmLabel + iconName: root.kcmIconName + onClicked: action_openKcm() + } + } +} diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop new file mode 100644 index 0000000..2a702ae --- /dev/null +++ b/plasmoid/package/metadata.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Name=Display Configuration +Comment=Quickly switch between screen layouts and presentation mode +Icon=preferences-desktop-display-randr + +X-KDE-ServiceTypes=Plasma/Applet +Type=Service + +X-Plasma-API=declarativeappletscript +X-Plasma-MainScript=ui/main.qml + +X-KDE-PluginInfo-Author=Kai Uwe Broulik +X-KDE-PluginInfo-Email=kde@broulik.de +X-KDE-PluginInfo-Name=org.kde.kscreen +X-KDE-PluginInfo-Version=1.0 +X-KDE-PluginInfo-Website=http://plasma.kde.org/ +X-KDE-PluginInfo-Category=Utilities +X-KDE-PluginInfo-License=GPL + +X-Plasma-NotificationArea=true +X-Plasma-NotificationAreaCategory=Hardware diff --git a/tests/osd/CMakeLists.txt b/tests/osd/CMakeLists.txt index 49f1f32..eb8a6b0 100644 --- a/tests/osd/CMakeLists.txt +++ b/tests/osd/CMakeLists.txt @@ -1,23 +1,24 @@ include_directories( ${CMAKE_SOURCE_DIR}/kcm/src ${CMAKE_BINARY_DIR}/kded ) add_executable(osdtest main.cpp osdtest.cpp ../../kded/osd.cpp ../../kded/osdmanager.cpp + ../../kded/osdaction.cpp ../../kcm/src/utils.cpp ) target_link_libraries(osdtest Qt5::Core Qt5::DBus Qt5::Quick Qt5::Qml KF5::Screen KF5::I18n KF5::Declarative ) add_test(NAME kscreen-kded-osdtest COMMAND osdtest) ecm_mark_as_test(osdtest)