1 /*
2     SPDX-FileCopyrightText: 2012 Alejandro Fiestas Olivares <afiestas@kde.org>
3     SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
4     SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@broulik.de>
5     SPDX-FileCopyrightText: 2020 Roman Gilg <subdiff@gmail.com>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "daemon.h"
10 
11 #include "../../common/orientation_sensor.h"
12 #include "config.h"
13 #include "generator.h"
14 #include "kdisplay_daemon_debug.h"
15 #include "kdisplayadaptor.h"
16 #include "osdmanager.h"
17 
18 #include <disman/configmonitor.h>
19 #include <disman/getconfigoperation.h>
20 #include <disman/log.h>
21 #include <disman/output.h>
22 #include <disman/setconfigoperation.h>
23 
24 #include <KActionCollection>
25 #include <KGlobalAccel>
26 #include <KLocalizedString>
27 #include <KPluginFactory>
28 
29 #include <QAction>
30 #include <QOrientationReading>
31 
32 K_PLUGIN_CLASS_WITH_JSON(KDisplayDaemon, "kdisplayd.json")
33 
KDisplayDaemon(QObject * parent,const QList<QVariant> &)34 KDisplayDaemon::KDisplayDaemon(QObject* parent, const QList<QVariant>&)
35     : KDEDModule(parent)
36     , m_monitoring{false}
37     , m_orientationSensor(new OrientationSensor(this))
38 {
39     Disman::Log::instance();
40 
41     connect(new Disman::GetConfigOperation,
42             &Disman::GetConfigOperation::finished,
43             this,
44             &KDisplayDaemon::init);
45 }
46 
init(Disman::ConfigOperation * op)47 void KDisplayDaemon::init(Disman::ConfigOperation* op)
48 {
49     if (op->has_error()) {
50         qCWarning(KDISPLAY_KDED) << "Initial config has error.";
51         return;
52     }
53 
54     m_monitoredConfig = qobject_cast<Disman::GetConfigOperation*>(op)->config();
55     auto cfg = m_monitoredConfig.get();
56 
57     qCDebug(KDISPLAY_KDED) << "Config" << cfg << "is ready";
58     Disman::ConfigMonitor::instance()->add_config(m_monitoredConfig);
59 
60     update_auto_rotate();
61     setMonitorForChanges(true);
62 
63     KActionCollection* coll = new KActionCollection(this);
64     QAction* action = coll->addAction(QStringLiteral("display"));
65     action->setText(i18n("Switch Display"));
66     QList<QKeySequence> switchDisplayShortcuts({Qt::Key_Display, Qt::MetaModifier + Qt::Key_P});
67     KGlobalAccel::self()->setGlobalShortcut(action, switchDisplayShortcuts);
68     connect(action, &QAction::triggered, this, &KDisplayDaemon::displayButton);
69 
70     new KdisplayAdaptor(this);
71     // Initialize OSD manager to register its dbus interface
72     m_osdManager = new OsdManager(this);
73 
74     connect(cfg, &Disman::Config::output_added, this, &KDisplayDaemon::applyConfig);
75     connect(cfg, &Disman::Config::output_removed, this, &KDisplayDaemon::applyConfig);
76 
77     connect(m_orientationSensor,
78             &OrientationSensor::availableChanged,
79             this,
80             &KDisplayDaemon::updateOrientation);
81     connect(m_orientationSensor,
82             &OrientationSensor::valueChanged,
83             this,
84             &KDisplayDaemon::updateOrientation);
85 
86     applyConfig();
87 
88     m_startingUp = false;
89 }
90 
update_auto_rotate()91 void KDisplayDaemon::update_auto_rotate()
92 {
93     assert(m_monitoredConfig);
94     m_orientationSensor->setEnabled(Config(m_monitoredConfig).autoRotationRequested());
95 }
96 
updateOrientation()97 void KDisplayDaemon::updateOrientation()
98 {
99     assert(m_monitoredConfig);
100 
101     const auto features = m_monitoredConfig->supported_features();
102     if (!features.testFlag(Disman::Config::Feature::AutoRotation)
103         || !features.testFlag(Disman::Config::Feature::TabletMode)) {
104         return;
105     }
106 
107     if (!m_orientationSensor->available() || !m_orientationSensor->enabled()) {
108         return;
109     }
110 
111     const auto orientation = m_orientationSensor->value();
112     if (orientation == QOrientationReading::Undefined) {
113         // Orientation sensor went off. Do not change current orientation.
114         return;
115     }
116     if (orientation == QOrientationReading::FaceUp
117         || orientation == QOrientationReading::FaceDown) {
118         // We currently don't do anything with FaceUp/FaceDown, but in the future we could use them
119         // to shut off and switch on again a display when display is facing downwards/upwards.
120         return;
121     }
122 
123     Config(m_monitoredConfig).setDeviceOrientation(orientation);
124     if (m_monitoring) {
125         doApplyConfig(m_monitoredConfig);
126     } else {
127         m_configDirty = true;
128     }
129 }
130 
doApplyConfig(Disman::ConfigPtr const & config)131 void KDisplayDaemon::doApplyConfig(Disman::ConfigPtr const& config)
132 {
133     qCDebug(KDISPLAY_KDED) << "Do set and apply specific config";
134 
135     m_monitoredConfig->apply(config);
136     refreshConfig();
137 }
138 
refreshConfig()139 void KDisplayDaemon::refreshConfig()
140 {
141     setMonitorForChanges(false);
142     m_configDirty = false;
143     Disman::ConfigMonitor::instance()->add_config(m_monitoredConfig);
144 
145     connect(new Disman::SetConfigOperation(m_monitoredConfig),
146             &Disman::SetConfigOperation::finished,
147             this,
148             [this]() {
149                 qCDebug(KDISPLAY_KDED) << "Config applied";
150                 if (m_configDirty) {
151                     // Config changed in the meantime again, apply.
152                     doApplyConfig(m_monitoredConfig);
153                 } else {
154                     setMonitorForChanges(true);
155                 }
156             });
157 }
158 
applyConfig()159 void KDisplayDaemon::applyConfig()
160 {
161     qCDebug(KDISPLAY_KDED) << "Applying config";
162 
163     const bool showOsd = m_monitoredConfig->outputs().size() > 1 && !m_startingUp
164         && m_monitoredConfig->cause() == Disman::Config::Cause::generated;
165 
166     if (showOsd) {
167         qCDebug(KDISPLAY_KDED) << "Getting ideal config from user via OSD...";
168         auto action = m_osdManager->showActionSelector();
169         connect(action, &OsdAction::selected, this, &KDisplayDaemon::applyOsdAction);
170     } else {
171         m_osdManager->hideOsd();
172     }
173 }
174 
applyLayoutPreset(const QString & presetName)175 void KDisplayDaemon::applyLayoutPreset(const QString& presetName)
176 {
177     const QMetaEnum actionEnum = QMetaEnum::fromType<OsdAction::Action>();
178     Q_ASSERT(actionEnum.isValid());
179 
180     bool ok;
181     auto action
182         = static_cast<OsdAction::Action>(actionEnum.keyToValue(qPrintable(presetName), &ok));
183     if (!ok) {
184         qCWarning(KDISPLAY_KDED) << "Cannot apply unknown screen layout preset named" << presetName;
185         return;
186     }
187     applyOsdAction(action);
188 }
189 
getAutoRotate()190 bool KDisplayDaemon::getAutoRotate()
191 {
192     return Config(m_monitoredConfig).getAutoRotate();
193 }
194 
setAutoRotate(bool value)195 void KDisplayDaemon::setAutoRotate(bool value)
196 {
197     if (!m_monitoredConfig || !m_orientationSensor->available()) {
198         return;
199     }
200     Config(m_monitoredConfig).setAutoRotate(value);
201     refreshConfig();
202 }
203 
applyOsdAction(OsdAction::Action action)204 void KDisplayDaemon::applyOsdAction(OsdAction::Action action)
205 {
206     qCDebug(KDISPLAY_KDED) << "Applying OSD action:" << action;
207 
208     if (auto config = Generator::displaySwitch(action, m_monitoredConfig)) {
209         doApplyConfig(config);
210     }
211 }
212 
configChanged()213 void KDisplayDaemon::configChanged()
214 {
215     qCDebug(KDISPLAY_KDED) << "Change detected" << m_monitoredConfig;
216 
217     update_auto_rotate();
218     updateOrientation();
219 }
220 
showOsd(const QString & icon,const QString & text)221 void KDisplayDaemon::showOsd(const QString& icon, const QString& text)
222 {
223     QDBusMessage msg = QDBusMessage::createMethodCall(QLatin1String("org.kde.plasmashell"),
224                                                       QLatin1String("/org/kde/osdService"),
225                                                       QLatin1String("org.kde.osdService"),
226                                                       QLatin1String("showText"));
227     msg << icon << text;
228     QDBusConnection::sessionBus().asyncCall(msg);
229 }
230 
showOutputIdentifier()231 void KDisplayDaemon::showOutputIdentifier()
232 {
233     m_osdManager->showOutputIdentifiers();
234 }
235 
displayButton()236 void KDisplayDaemon::displayButton()
237 {
238     qCDebug(KDISPLAY_KDED) << "displayBtn triggered";
239 
240     auto action = m_osdManager->showActionSelector();
241     connect(action, &OsdAction::selected, this, &KDisplayDaemon::applyOsdAction);
242 }
243 
setMonitorForChanges(bool enabled)244 void KDisplayDaemon::setMonitorForChanges(bool enabled)
245 {
246     if (m_monitoring == enabled) {
247         return;
248     }
249 
250     qCDebug(KDISPLAY_KDED) << "Monitor for changes: " << enabled;
251     m_monitoring = enabled;
252 
253     if (m_monitoring) {
254         connect(Disman::ConfigMonitor::instance(),
255                 &Disman::ConfigMonitor::configuration_changed,
256                 this,
257                 &KDisplayDaemon::configChanged,
258                 Qt::UniqueConnection);
259     } else {
260         disconnect(Disman::ConfigMonitor::instance(),
261                    &Disman::ConfigMonitor::configuration_changed,
262                    this,
263                    &KDisplayDaemon::configChanged);
264     }
265 }
266 
267 #include "daemon.moc"
268