1 /*
2     SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org>
3     SPDX-FileCopyrightText: 2016 Sebastian Kügler <sebas@kde.org>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "osd.h"
9 
10 #include "kscreen_daemon_debug.h"
11 
12 #include "../common/utils.h"
13 
14 #include <KScreen/Mode>
15 
16 #include <QCursor>
17 #include <QGuiApplication>
18 #include <QScreen>
19 #include <QStandardPaths>
20 #include <QTimer>
21 
22 #include <KDeclarative/QmlObjectSharedEngine>
23 
24 using namespace KScreen;
25 
Osd(const KScreen::OutputPtr & output,QObject * parent)26 Osd::Osd(const KScreen::OutputPtr &output, QObject *parent)
27     : QObject(parent)
28     , m_output(output)
29 {
30     connect(output.data(), &KScreen::Output::isConnectedChanged, this, &Osd::onOutputAvailabilityChanged);
31     connect(output.data(), &KScreen::Output::isEnabledChanged, this, &Osd::onOutputAvailabilityChanged);
32     connect(output.data(), &KScreen::Output::currentModeIdChanged, this, &Osd::updatePosition);
33     connect(output.data(), &KScreen::Output::destroyed, this, &Osd::hideOsd);
34 }
35 
~Osd()36 Osd::~Osd()
37 {
38 }
39 
initOsd()40 bool Osd::initOsd()
41 {
42     if (m_osdObject) {
43         return true;
44     }
45 
46     const QString osdPath = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kded_kscreen/qml/Osd.qml"));
47     if (osdPath.isEmpty()) {
48         qCWarning(KSCREEN_KDED) << "Failed to find OSD QML file" << osdPath;
49         return false;
50     }
51 
52     m_osdObject = new KDeclarative::QmlObjectSharedEngine(this);
53     m_osdObject->setSource(QUrl::fromLocalFile(osdPath));
54 
55     if (m_osdObject->status() != QQmlComponent::Ready) {
56         qCWarning(KSCREEN_KDED) << "Failed to load OSD QML file" << osdPath;
57         delete m_osdObject;
58         m_osdObject = nullptr;
59         return false;
60     }
61 
62     m_timeout = m_osdObject->rootObject()->property("timeout").toInt();
63     m_osdTimer = new QTimer(this);
64     m_osdTimer->setSingleShot(true);
65     connect(m_osdTimer, &QTimer::timeout, this, &Osd::hideOsd);
66     return true;
67 }
68 
showGenericOsd(const QString & icon,const QString & text)69 void Osd::showGenericOsd(const QString &icon, const QString &text)
70 {
71     if (!initOsd()) {
72         return;
73     }
74 
75     m_outputGeometry = m_output->geometry();
76     auto *rootObject = m_osdObject->rootObject();
77     rootObject->setProperty("itemSource", QStringLiteral("OsdItem.qml"));
78     rootObject->setProperty("infoText", text);
79     rootObject->setProperty("icon", icon);
80 
81     showOsd();
82 }
83 
showOutputIdentifier(const KScreen::OutputPtr & output)84 void Osd::showOutputIdentifier(const KScreen::OutputPtr &output)
85 {
86     if (!initOsd()) {
87         return;
88     }
89 
90     m_outputGeometry = output->geometry();
91 
92     auto *rootObject = m_osdObject->rootObject();
93     auto mode = output->currentMode();
94     QSize realSize = mode->size();
95     if (!output->isHorizontal()) {
96         realSize.transpose();
97     }
98     rootObject->setProperty("itemSource", QStringLiteral("OutputIdentifier.qml"));
99     rootObject->setProperty("modeName", Utils::sizeToString(realSize));
100     rootObject->setProperty("outputName", Utils::outputName(output));
101     showOsd();
102 }
103 
showActionSelector()104 void Osd::showActionSelector()
105 {
106     if (!m_osdActionSelector) {
107         const QString osdPath = QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, QStringLiteral("kded_kscreen/qml/OsdSelector.qml"));
108         if (osdPath.isEmpty()) {
109             qCWarning(KSCREEN_KDED) << "Failed to find action selector OSD QML file" << osdPath;
110             return;
111         }
112         m_osdActionSelector = new KDeclarative::QmlObjectSharedEngine(this);
113         m_osdActionSelector->setSource(QUrl::fromLocalFile(osdPath));
114 
115         if (m_osdActionSelector->status() != QQmlComponent::Ready) {
116             qCWarning(KSCREEN_KDED) << "Failed to load OSD QML file" << osdPath;
117             delete m_osdActionSelector;
118             m_osdActionSelector = nullptr;
119             return;
120         }
121 
122         auto *rootObject = m_osdActionSelector->rootObject();
123         connect(rootObject, SIGNAL(clicked(int)), this, SLOT(onOsdActionSelected(int)));
124     }
125     if (auto *rootObject = m_osdActionSelector->rootObject()) {
126         // On wayland, we use m_output to set an action on OSD position
127         if (qGuiApp->platformName() == QLatin1String("wayland")) {
128             rootObject->setProperty("screenGeometry", m_output->geometry());
129         }
130         rootObject->setProperty("visible", true);
131     } else {
132         qCWarning(KSCREEN_KDED) << "Could not get root object for action selector.";
133     }
134 }
135 
onOsdActionSelected(int action)136 void Osd::onOsdActionSelected(int action)
137 {
138     Q_EMIT osdActionSelected(static_cast<OsdAction::Action>(action));
139     hideOsd();
140 }
141 
onOutputAvailabilityChanged()142 void Osd::onOutputAvailabilityChanged()
143 {
144     if (!m_output || !m_output->isConnected() || !m_output->isEnabled() || !m_output->currentMode()) {
145         hideOsd();
146     }
147 }
148 
updatePosition()149 void Osd::updatePosition()
150 {
151     if (!initOsd()) {
152         return;
153     }
154 
155     const auto geometry = m_output->geometry();
156     if (!geometry.isValid()) {
157         hideOsd();
158     }
159 
160     auto *rootObject = m_osdObject->rootObject();
161 
162     const int dialogWidth = rootObject->property("width").toInt();
163     const int dialogHeight = rootObject->property("height").toInt();
164     const int relx = geometry.x();
165     const int rely = geometry.y();
166     const int pos_x = relx + (geometry.width() - dialogWidth) / 2;
167     const int pos_y = rely + (geometry.height() - dialogHeight) / 2;
168 
169     rootObject->setProperty("x", pos_x);
170     rootObject->setProperty("y", pos_y);
171 }
172 
showOsd()173 void Osd::showOsd()
174 {
175     m_osdTimer->stop();
176 
177     auto *rootObject = m_osdObject->rootObject();
178 
179     // only animate on X11, wayland plugin doesn't support this and
180     // pukes loads of warnings into our logs
181     if (qGuiApp->platformName() == QLatin1String("xcb")) {
182         if (rootObject->property("timeout").toInt() > 0) {
183             rootObject->setProperty("animateOpacity", false);
184             rootObject->setProperty("opacity", 1);
185             rootObject->setProperty("animateOpacity", true);
186             rootObject->setProperty("opacity", 0);
187         }
188     }
189     rootObject->setProperty("visible", true);
190     QTimer::singleShot(0, this, &Osd::updatePosition);
191     if (m_timeout > 0) {
192         m_osdTimer->start(m_timeout);
193     }
194 }
195 
hideOsd()196 void Osd::hideOsd()
197 {
198     if (m_osdActionSelector) {
199         if (auto *rootObject = m_osdActionSelector->rootObject()) {
200             rootObject->setProperty("visible", false);
201         }
202     }
203     if (m_osdObject) {
204         if (auto *rootObject = m_osdObject->rootObject()) {
205             rootObject->setProperty("visible", false);
206         }
207     }
208 }
209