diff --git a/.reviewboardrc b/.reviewboardrc deleted file mode 100644 index 15ef0b5..0000000 --- a/.reviewboardrc +++ /dev/null @@ -1,2 +0,0 @@ -REVIEWBOARD_URL = 'https://git.reviewboard.kde.org' -REPOSITORY = 'git://anongit.kde.org/kscreen' diff --git a/CMakeLists.txt b/CMakeLists.txt index 65d3c9b..05777d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,40 +1,40 @@ project(KScreen) -set(PROJECT_VERSION "5.11.90") +set(PROJECT_VERSION "5.12.80") cmake_minimum_required(VERSION 2.8.12) set(KSCREEN_VERSION ${PROJECT_VERSION}) add_definitions("-DKSCREEN_VERSION=\"${KSCREEN_VERSION}\"") find_package(ECM 1.6.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) find_package(Qt5 REQUIRED COMPONENTS QuickWidgets Test) find_package(KF5 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) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMInstallIcons) include(ECMMarkAsTest) include(FeatureSummary) include(ECMQtDeclareLoggingCategory) add_subdirectory(icons) add_subdirectory(kcm) #add_subdirectory(plasma) add_subdirectory(kded) 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/kcm/kcm_kscreen.desktop.cmake b/kcm/kcm_kscreen.desktop.cmake index 9773eef..ad26004 100644 --- a/kcm/kcm_kscreen.desktop.cmake +++ b/kcm/kcm_kscreen.desktop.cmake @@ -1,132 +1,132 @@ [Desktop Entry] Exec=kcmshell5 kcm_kscreen Icon=preferences-desktop-display-randr Type=Service X-KDE-ServiceTypes=KCModule X-KDE-Library=kcm_kscreen X-KDE-ParentApp=kcontrol X-KDE-System-Settings-Parent-Category=display X-KDE-Weight=40 X-KDE-PluginInfo-Version=@KSCREEN_VERSION@ Name=Displays Name[ca]=Pantalles Name[ca@valencia]=Pantalles Name[cs]=Obrazovky Name[da]=Skærme Name[de]=Anzeigen Name[el]=Οθόνες Name[en_GB]=Displays Name[es]=Pantallas Name[et]=Monitorid Name[eu]=Bistaratzaileak Name[fi]=Näytöt Name[fr]=Affichages Name[gl]=Pantallas Name[he]=תצוגות Name[hu]=Megjelenítők Name[ia]=Monstratores Name[id]=Displays Name[it]=Schermi Name[ko]=디스플레이 Name[lt]=Ekranai Name[nl]=Schermen Name[nn]=Skjermar Name[pl]=Ekrany Name[pt]=Ecrãs Name[pt_BR]=Telas Name[ru]=Экраны Name[sk]=Obrazovky Name[sl]=Zasloni Name[sr]=Екрани Name[sr@ijekavian]=Екрани Name[sr@ijekavianlatin]=Ekrani Name[sr@latin]=Ekrani Name[sv]=Bildskärmar Name[tr]=Ekranlar Name[uk]=Дисплеї Name[x-test]=xxDisplaysxx Name[zh_CN]=显示 Name[zh_TW]=顯示 Comment=Manage and configure monitors and displays Comment[ar]=أدر واضبط الشّاشات والعروض Comment[bs]=Upravljanje i konfiguracija monitora i ekrana Comment[ca]=Gestiona i configura els monitors i pantalles Comment[ca@valencia]=Gestiona i configura els monitors i pantalles Comment[cs]=Spravovat a nastavit monitory a zobrazení Comment[da]=Håndtér og indstil monitorer og skærme Comment[de]=Verwaltung und Einrichtung vom Monitoren und Anzeigen Comment[el]=Διαχείριση και διαμόρφωση οθονών και απεικονίσεων Comment[en_GB]=Manage and configure monitors and displays Comment[es]=Gestionar y configurar monitores y pantallas Comment[et]=Monitoride ja ekraanide haldamine ja seadistamine Comment[eu]=Kudeatu eta konfiguratu monitoreak eta bistaratzaileak Comment[fi]=Näyttöjen asetusten hallinta Comment[fr]=Gère et configure les moniteurs et les écrans Comment[gl]=Xestiona a configura os monitores e a resolución Comment[he]=נהל את הגדרות המסכים והתצוגות שלך Comment[hu]=Monitorok és kijelzők kezelése és beállítása Comment[ia]=Gere e configura monitors e monstratores -Comment[id]=Kelola dan atur monitor dan display +Comment[id]=Kelola dan konfigurasi monitor dan display Comment[it]=Gestisci e configura monitor e schermi Comment[ko]=모니터와 디스플레이 설정 및 관리 Comment[lt]=Tvarkyti ir konfigūruoti monitorius ir ekranus Comment[nb]=Håndter og sett opp monitorer og skjermer Comment[nl]=Monitoren en schermen beheren en instellen Comment[nn]=Set opp skjermar og skjermbilete Comment[pa]=ਮਾਨੀਟਰ ਤੇ ਡਿਸਪਲੇਅ ਦਾ ਪਰਬੰਧ ਤੇ ਸੰਰਚਨਾ ਕਰੋ Comment[pl]=Zarządzanie i ustawienia ekranów Comment[pt]=Faz a gestão e configuração dos monitores e ecrãs Comment[pt_BR]=Gerencia e configura monitores e telas Comment[ro]=Gestionează și configurează monitoare și afișaje Comment[ru]=Настройка экранов и видеовыходов Comment[sk]=Správa a nastavenie monitorov a obrazoviek Comment[sl]=Upravljajte in nastavljajte zaslone in prikazovalnike Comment[sr]=Управљање и подешавање монитора̂ и екрана̂ Comment[sr@ijekavian]=Управљање и подешавање монитора̂ и екрана̂ Comment[sr@ijekavianlatin]=Upravljanje i podešavanje monitorâ̂ i ekranâ̂ Comment[sr@latin]=Upravljanje i podešavanje monitorâ̂ i ekranâ̂ Comment[sv]=Hantera och ställ in bildskärmar Comment[tr]=Ekranları ve görüntüleri yönet, yapılandır Comment[uk]=Керування і налаштовування моніторів і дисплеїв Comment[x-test]=xxManage and configure monitors and displaysxx Comment[zh_CN]=管理和配置显示和监视器 Comment[zh_TW]=管理與設定螢幕與顯示 X-KDE-Keywords=display,monitor,scale,scaling,resolution,orientation,outputs X-KDE-Keywords[ca]=pantalla,monitor,escala,escalat,resolució,orientació,sortides X-KDE-Keywords[ca@valencia]=pantalla,monitor,escala,escalat,resolució,orientació,eixides X-KDE-Keywords[da]=skærm,monitor,skaler,opløsning,orientering,output X-KDE-Keywords[de]=Anzeige,Monitor,Bildschirm,Skalierung,Auflösung,Ausrichtung,Ausgabe X-KDE-Keywords[el]=απεικόνιση,οθόνη,κλίμακα,κλιμάκωση,ανάλυση,προσανατολισμός,έξοδοι X-KDE-Keywords[en_GB]=display,monitor,scale,scaling,resolution,orientation,outputs X-KDE-Keywords[es]=pantalla,monitor,escala,escalado,resolución,orientación,salidas X-KDE-Keywords[et]=kuva,monitor,ekraan,skaleerimine,lahutusvõime,eraldusvõime,orientatsioon,väljundid X-KDE-Keywords[eu]=bistaratzaile,monitore,eskalatu,eskalatze,bereizmen,orientazioa,irteerak X-KDE-Keywords[fi]=display,monitor,scale,scaling,resolution,orientation,outputs,näyttö,monitori,skaalaus,resoluutio,tarkkuus,suunta,kierto,ulostulot X-KDE-Keywords[fr]=affichage,moniteur,échelle,mise à l'échelle,résolution,orientation,sorties X-KDE-Keywords[gl]=display,pantalla,monitor,scale,cambiar de escala,scaling,cambio de escala,resolution,resolución,orientation,orientación,outputs,saída,saídas X-KDE-Keywords[he]=display,monitor,scale,scaling,resolution,orientation,outputs,תצוגה,מוניטור,רצולוציה,כיוון X-KDE-Keywords[hu]=megjelenítés,monitor,skála,skálázás,felbontás,tájolás,kimenetek X-KDE-Keywords[ia]=monstrator, monitor, scala, scalar, resolution, orientation, exitos X-KDE-Keywords[id]=display,monitor,scale,scaling,resolution,orientation,outputs X-KDE-Keywords[it]=schermo,monitor,scala,riscalatura,risoluzione,orientazione,uscite X-KDE-Keywords[ko]=display,monitor,scale,scaling,resolution,orientation,outputs,디스플레이,모니터,비율,배율,해상도,출력,방향 X-KDE-Keywords[lt]=ekranas,vaizduoklis,monitorius,mastelis,skiriamoji geba,padėtis,orientacija,išvedimas X-KDE-Keywords[nl]=scherm,monitor,schaal,schalen,resolutie,oriëntatie,uitvoer X-KDE-Keywords[nn]=skjermbilete,skjerm,skala,skalering,oppløysing,skjermoppløysing,retning,utgang X-KDE-Keywords[pl]=wyświetlacz,monitor,skala,skalowanie,rozdzielczość,kierunek,orientacja,wyjścia X-KDE-Keywords[pt]=ecrã,monitor,escala,resolução,orientação,saídas X-KDE-Keywords[pt_BR]=tela,monitor,escala,ajuste,resolução,orientação,saídas X-KDE-Keywords[ru]=display,monitor,scale,scaling,resolution,orientation,outputs,экран,монитор,масштаб,разрешение,ориентация,выводы X-KDE-Keywords[sk]=displej,monitor,mierka,škálovanie,rozlíšenie,orientácia,výstupy X-KDE-Keywords[sl]=prikaz,zaslon,monitor,umerjanje,merilo,ločljivost,resolucija,orientacija,usmerjenost,izhodi X-KDE-Keywords[sr]=display,monitor,scale,scaling,resolution,oritentation,outputs,приказ,монитор,екран,скалирање,резолуција,оријентација,излаз X-KDE-Keywords[sr@ijekavian]=display,monitor,scale,scaling,resolution,oritentation,outputs,приказ,монитор,екран,скалирање,резолуција,оријентација,излаз X-KDE-Keywords[sr@ijekavianlatin]=display,monitor,scale,scaling,resolution,oritentation,outputs,prikaz,monitor,ekran,skaliranje,rezolucija,orijentacija,izlaz X-KDE-Keywords[sr@latin]=display,monitor,scale,scaling,resolution,oritentation,outputs,prikaz,monitor,ekran,skaliranje,rezolucija,orijentacija,izlaz X-KDE-Keywords[sv]=skärm,bildskärm,skala,skalning,upplösning,orientering,utgångar X-KDE-Keywords[tr]=ekran,monitör,ölçek,ölçeklendirme,çözünürlük,yönelim,çıktılar X-KDE-Keywords[uk]=display,monitor,scale,scaling,resolution,orientation,outputs,дисплей,монітор,масштаб,масштабування,роздільність,здатність,орієнтація,виведення,виходи X-KDE-Keywords[x-test]=xxdisplayxx,xxmonitorxx,xxscalexx,xxscalingxx,xxresolutionxx,xxorientationxx,xxoutputsxx X-KDE-Keywords[zh_CN]=display,monitor,scale,scaling,resolution,orientation,outputs,显示,监视器,缩放,分辨率,旋转,方位,输出 X-KDE-Keywords[zh_TW]=display,monitor,scale,scaling,resolution,orientation,outputs diff --git a/kded/daemon.cpp b/kded/daemon.cpp index 5a6e85c..9ce3dee 100644 --- a/kded/daemon.cpp +++ b/kded/daemon.cpp @@ -1,499 +1,502 @@ /************************************************************************************* * Copyright (C) 2012 by Alejandro Fiestas Olivares * * Copyright 2016 by 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 "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_iteration(Generator::None) , m_monitoring(false) , m_changeCompressor(new QTimer(this)) , m_buttonTimer(new QTimer(this)) , m_saveTimer(new QTimer(this)) , 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, [&](bool) { displayButton(); }); new KScreenAdaptor(this); // Initialize OSD manager to register its dbus interface KScreen::OsdManager::self(); m_buttonTimer->setInterval(300); m_buttonTimer->setSingleShot(true); connect(m_buttonTimer, &QTimer::timeout, this, &KScreenDaemon::applyGenericConfig); m_saveTimer->setInterval(300); m_saveTimer->setSingleShot(true); connect(m_saveTimer, &QTimer::timeout, this, &KScreenDaemon::saveCurrentConfig); 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("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 + QStringLiteral("_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)) { return applyIdealConfig(); } doApplyConfig(config); } void KScreenDaemon::applyOsdAction(KScreen::OsdAction *self, KScreen::OsdAction::Action action) { self->deleteLater(); switch (action) { case KScreen::OsdAction::NoAction: qCDebug(KSCREEN_KDED) << "OSD: no action"; return; case KScreen::OsdAction::SwitchToInternal: qCDebug(KSCREEN_KDED) << "OSD: swutch 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) { doApplyConfig(Generator::self()->idealConfig(m_monitoredConfig)); } else { qCDebug(KSCREEN_KDED) << "Getting ideal config from user..."; auto action = KScreen::OsdManager::self()->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 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() { KScreen::OsdManager::self()->showOutputIdentifiers(); } void KScreenDaemon::displayButton() { qCDebug(KSCREEN_KDED) << "displayBtn triggered"; QString message = i18nc("OSD text after XF86Display button press", "No External Display"); if (m_monitoredConfig && m_monitoredConfig->connectedOutputs().count() > 1) { message = i18nc("OSD text after XF86Display button press", "Changing Screen Layout"); } if (m_buttonTimer->isActive()) { qCDebug(KSCREEN_KDED) << "Too fast, cowboy"; return; } m_buttonTimer->start(); } void KScreenDaemon::resetDisplaySwitch() { qCDebug(KSCREEN_KDED) << "resetDisplaySwitch()"; m_iteration = Generator::None; } void KScreenDaemon::applyGenericConfig() { if (m_iteration == Generator::ExtendToRight) { m_iteration = Generator::None; } m_iteration = Generator::DisplaySwitchAction(static_cast(m_iteration) + 1); qCDebug(KSCREEN_KDED) << "displayButton: " << m_iteration; static QHash actionMessages({ {Generator::DisplaySwitchAction::None, i18nc("osd when displaybutton is pressed", "No Action")}, {Generator::DisplaySwitchAction::Clone, i18nc("osd when displaybutton is pressed", "Cloned Display")}, {Generator::DisplaySwitchAction::ExtendToLeft, i18nc("osd when displaybutton is pressed", "Extend Left")}, {Generator::DisplaySwitchAction::TurnOffEmbedded, i18nc("osd when displaybutton is pressed", "External Only")}, {Generator::DisplaySwitchAction::TurnOffExternal, i18nc("osd when displaybutton is pressed", "Internal Only")}, {Generator::DisplaySwitchAction::ExtendToRight, i18nc("osd when displaybutton is pressed", "Extended Right")} }); const QString &message = actionMessages.value(m_iteration); // We delay the OSD for two seconds and hope that X and hardware are done setting everything up. QTimer::singleShot(2000, [message]() { KScreen::OsdManager::self()->showOsd(QStringLiteral("preferences-desktop-display-randr"), message); } ); doApplyConfig(Generator::self()->displaySwitch(m_iteration)); } 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) + QStringLiteral("_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 simply 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 no 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) + QStringLiteral("_lidOpened"); Serializer::saveConfig(m_monitoredConfig, configId); disableOutput(m_monitoredConfig, output); doApplyConfig(m_monitoredConfig); return; } } } } void KScreenDaemon::outputConnectedChanged() { if (!m_changeCompressor->isActive()) { m_changeCompressor->start(); } resetDisplaySwitch(); 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); } KScreen::OutputPtr KScreenDaemon::findEmbeddedOutput(const KScreen::ConfigPtr &config) { Q_FOREACH (const KScreen::OutputPtr &output, config->outputs()) { if (output->type() == KScreen::Output::Panel) { return output; } } return KScreen::OutputPtr(); } #include "daemon.moc" diff --git a/kded/serializer.cpp b/kded/serializer.cpp index b68651f..bcb202d 100644 --- a/kded/serializer.cpp +++ b/kded/serializer.cpp @@ -1,327 +1,328 @@ /************************************************************************************* * 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 * *************************************************************************************/ #include "serializer.h" #include "kscreen_daemon_debug.h" #include "generator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include QString Serializer::sConfigPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QStringLiteral("/kscreen/"); void Serializer::setConfigPath(const QString &path) { sConfigPath = path; if (!sConfigPath.endsWith(QLatin1Char('/'))) { sConfigPath += QLatin1Char('/'); } } QString Serializer::configFileName(const QString &configId) { if (!QDir().mkpath(sConfigPath)) { return QString(); } return sConfigPath % configId; } QString Serializer::configId(const KScreen::ConfigPtr ¤tConfig) { if (!currentConfig) { return QString(); } KScreen::OutputList outputs = currentConfig->outputs(); QStringList hashList; //qCDebug(KSCREEN_KDED) << "Calculating config ID for" << currentConfig.data(); Q_FOREACH(const KScreen::OutputPtr &output, outputs) { if (!output->isConnected()) { continue; } //qCDebug(KSCREEN_KDED) << "\tPart of the Id: " << Serializer::outputId(output); hashList.insert(0, Serializer::outputId(output)); } qSort(hashList.begin(), hashList.end()); const QByteArray hash = QCryptographicHash::hash(hashList.join(QString()).toLatin1(), QCryptographicHash::Md5).toHex(); //qCDebug(KSCREEN_KDED) << "\tConfig ID:" << hash; return hash; } bool Serializer::configExists(const KScreen::ConfigPtr &config) { return Serializer::configExists(Serializer::configId(config)); } bool Serializer::configExists(const QString &id) { return QFile::exists(sConfigPath % id); } KScreen::ConfigPtr Serializer::config(const KScreen::ConfigPtr ¤tConfig, const QString &id) { KScreen::ConfigPtr config = currentConfig->clone(); QFile file(configFileName(id)); if (!file.open(QIODevice::ReadOnly)) { qCDebug(KSCREEN_KDED) << "failed to open file" << id; return KScreen::ConfigPtr(); } KScreen::OutputList outputList = config->outputs(); QJsonDocument parser; QVariantList outputs = parser.fromJson(file.readAll()).toVariant().toList(); Q_FOREACH(KScreen::OutputPtr output, outputList) { if (!output->isConnected() && output->isEnabled()) { output->setEnabled(false); } } QSize screenSize; Q_FOREACH(const QVariant &info, outputs) { KScreen::OutputPtr output = Serializer::findOutput(config, info.toMap()); if (!output) { continue; } if (output->isEnabled()) { const QRect geom = output->geometry(); if (geom.x() + geom.width() > screenSize.width()) { screenSize.setWidth(geom.x() + geom.width()); } if (geom.y() + geom.height() > screenSize.height()) { screenSize.setHeight(geom.y() + geom.height()); } } outputList.remove(output->id()); outputList.insert(output->id(), output); } config->setOutputs(outputList); config->screen()->setCurrentSize(screenSize); return config; } bool Serializer::saveConfig(const KScreen::ConfigPtr &config, const QString &configId) { if (!config || configId.isEmpty()) { return false; } const KScreen::OutputList outputs = config->outputs(); QVariantList outputList; Q_FOREACH(const KScreen::OutputPtr &output, outputs) { if (!output->isConnected()) { continue; } QVariantMap info; info[QStringLiteral("id")] = Serializer::outputId(output); info[QStringLiteral("primary")] = output->isPrimary(); info[QStringLiteral("enabled")] = output->isEnabled(); info[QStringLiteral("rotation")] = output->rotation(); info[QStringLiteral("scale")] = output->scale(); QVariantMap pos; pos[QStringLiteral("x")] = output->pos().x(); pos[QStringLiteral("y")] = output->pos().y(); info[QStringLiteral("pos")] = pos; if (output->isEnabled()) { const KScreen::ModePtr mode = output->currentMode(); if (!mode) { qWarning() << "CurrentMode is null" << output->name(); return false; } QVariantMap modeInfo; modeInfo[QStringLiteral("refresh")] = mode->refreshRate(); QVariantMap modeSize; modeSize[QStringLiteral("width")] = mode->size().width(); modeSize[QStringLiteral("height")] = mode->size().height(); modeInfo[QStringLiteral("size")] = modeSize; info[QStringLiteral("mode")] = modeInfo; } info[QStringLiteral("metadata")] = Serializer::metadata(output); outputList.append(info); } QFile file(configFileName(configId)); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KSCREEN_KDED) << "Failed to open config file for writing! " << file.errorString(); return false; } file.write(QJsonDocument::fromVariant(outputList).toJson()); qCDebug(KSCREEN_KDED) << "Config saved on: " << file.fileName(); return true; } void Serializer::removeConfig(const QString &id) { QFile::remove(configFileName(id)); } bool Serializer::moveConfig(const QString &srcId, const QString &destId) { const QFile srcFile(configFileName(srcId)); if (srcFile.exists()) { removeConfig(destId); if (QFile::copy(configFileName(srcId), configFileName(destId))) { removeConfig(srcId); qCDebug(KSCREEN_KDED) << "Restored config" << srcId << "to" << destId; return true; } } return false; } KScreen::OutputPtr Serializer::findOutput(const KScreen::ConfigPtr &config, const QVariantMap& info) { const KScreen::OutputList outputs = config->outputs(); // As individual outputs are indexed by a hash of their edid, which is not unique, // to be able to tell apart multiple identical outputs, these need special treatment QStringList duplicateIds; QStringList allIds; allIds.reserve(outputs.count()); Q_FOREACH (const KScreen::OutputPtr &output, outputs) { const auto outputId = Serializer::outputId(output); if (allIds.contains(outputId) && !duplicateIds.contains(outputId)) { duplicateIds << outputId; } allIds << outputId; } allIds.clear(); Q_FOREACH(KScreen::OutputPtr output, outputs) { if (!output->isConnected()) { continue; } const auto outputId = Serializer::outputId(output); if (outputId != info[QStringLiteral("id")].toString()) { continue; } // We may have identical outputs connected, these will have the same id in the config // in order to find the right one, also check the output's name (usually the connector) if (!output->name().isEmpty() && duplicateIds.contains(outputId)) { const auto metadata = info[QStringLiteral("metadata")].toMap(); const auto outputName = metadata[QStringLiteral("name")].toString(); if (output->name() != outputName) { continue; } } const QVariantMap posInfo = info[QStringLiteral("pos")].toMap(); QPoint point(posInfo[QStringLiteral("x")].toInt(), posInfo[QStringLiteral("y")].toInt()); output->setPos(point); output->setPrimary(info[QStringLiteral("primary")].toBool()); output->setEnabled(info[QStringLiteral("enabled")].toBool()); output->setRotation(static_cast(info[QStringLiteral("rotation")].toInt())); + output->setScale(info.value(QStringLiteral("scale"), 1).toInt()); const QVariantMap modeInfo = info[QStringLiteral("mode")].toMap(); const QVariantMap modeSize = modeInfo[QStringLiteral("size")].toMap(); const QSize size = QSize(modeSize[QStringLiteral("width")].toInt(), modeSize[QStringLiteral("height")].toInt()); qCDebug(KSCREEN_KDED) << "Finding a mode for" << size << "@" << modeInfo[QStringLiteral("refresh")].toFloat(); KScreen::ModeList modes = output->modes(); KScreen::ModePtr matchingMode; Q_FOREACH(const KScreen::ModePtr &mode, modes) { if (mode->size() != size) { continue; } if (!qFuzzyCompare(mode->refreshRate(), modeInfo[QStringLiteral("refresh")].toFloat())) { continue; } qCDebug(KSCREEN_KDED) << "\tFound: " << mode->id() << " " << mode->size() << "@" << mode->refreshRate(); matchingMode = mode; break; } if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to find a matching mode - this means that our config is corrupted" "or a different device with the same serial number has been connected (very unlikely)." "Falling back to preferred modes."; matchingMode = output->preferredMode(); if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get a preferred mode, falling back to biggest mode."; matchingMode = Generator::biggestMode(modes); if (!matchingMode) { qCWarning(KSCREEN_KDED) << "\tFailed to get biggest mode. Which means there are no modes. Turning off the screen."; output->setEnabled(false); return output; } } } output->setCurrentModeId(matchingMode->id()); return output; } qCWarning(KSCREEN_KDED) << "\tFailed to find a matching output in the current config - this means that our config is corrupted" "or a different device with the same serial number has been connected (very unlikely)."; return KScreen::OutputPtr(); } QString Serializer::outputId(const KScreen::OutputPtr &output) { if (output->edid() && output->edid()->isValid()) { return output->edid()->hash(); } return output->name(); } QVariantMap Serializer::metadata(const KScreen::OutputPtr &output) { QVariantMap metadata; metadata[QStringLiteral("name")] = output->name(); if (!output->edid() || !output->edid()->isValid()) { return metadata; } metadata[QStringLiteral("fullname")] = output->edid()->deviceId(); return metadata; }