Lomiri
Loading...
Searching...
No Matches
indicatorsmanager.cpp
1/*
2 * Copyright (C) 2013-2016 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "indicatorsmanager.h"
18
19#include <QSettings>
20#include <QDebug>
21#include <QDBusConnection>
22#include <QDBusConnectionInterface>
23#include <QDBusServiceWatcher>
24#include <QRegularExpression>
25#include <paths.h>
26
27
28class IndicatorsManager::IndicatorData
29{
30public:
31 IndicatorData(const QString& name, const QFileInfo& fileInfo)
32 : m_name(name)
33 , m_fileInfo(fileInfo)
34 , m_verified (true)
35 {
36 }
37
38 QString m_name;
39 QFileInfo m_fileInfo;
40
41 bool m_verified;
42 Indicator::Ptr m_indicator;
43};
44
45IndicatorsManager::IndicatorsManager(QObject* parent)
46 : QObject(parent)
47 , m_loaded(false)
48 , m_profile(QStringLiteral("phone"))
49{
50}
51
52IndicatorsManager::~IndicatorsManager()
53{
54 unload();
55}
56
57void IndicatorsManager::load()
58{
59 unload();
60
61 m_fsWatcher.reset(new QFileSystemWatcher(this));
62
63 for (const auto &xdgPath : shellDataDirs()) {
64 // For legacy reasons we keep the old unity indicator path
65 const auto unityPath = QDir::cleanPath(xdgPath + "/unity/indicators");
66 if (QFile::exists(unityPath)) {
67 // watch folder for changes.
68 m_fsWatcher->addPath(unityPath);
69 loadDir(unityPath);
70 }
71
72 const auto ayatanaPath = QDir::cleanPath(xdgPath + "/ayatana/indicators");
73 if (QFile::exists(ayatanaPath)) {
74 // watch folder for changes.
75 m_fsWatcher->addPath(ayatanaPath);
76 loadDir(ayatanaPath);
77 }
78 }
79
80 QObject::connect(m_fsWatcher.data(), &QFileSystemWatcher::directoryChanged, this, &IndicatorsManager::onDirectoryChanged);
81 QObject::connect(m_fsWatcher.data(), &QFileSystemWatcher::fileChanged, this, &IndicatorsManager::onFileChanged);
82 setLoaded(true);
83}
84
85void IndicatorsManager::onDirectoryChanged(const QString& directory)
86{
87 loadDir(QDir(directory));
88}
89
90void IndicatorsManager::onFileChanged(const QString& file)
91{
92 QFileInfo file_info(file);
93 if (!file_info.exists())
94 {
95 unloadFile(file_info);
96 return;
97 }
98 else
99 {
100 loadFile(QFileInfo(file));
101 }
102}
103
104void IndicatorsManager::loadDir(const QDir& dir)
105{
106 startVerify(dir.canonicalPath());
107
108 const QFileInfoList indicator_files = dir.entryInfoList(QStringList(), QDir::Files|QDir::NoDotAndDotDot);
109 Q_FOREACH(const QFileInfo& indicator_file, indicator_files)
110 {
111 loadFile(indicator_file);
112 }
113
114 endVerify(dir.canonicalPath());
115}
116
117void IndicatorsManager::loadFile(const QFileInfo& file_info)
118{
119 // The fake indicators in the unit tests are not registered, so skip them
120 QString service = file_info.fileName ();
121 QRegularExpression regex ("^com\\.canonical\\.indicator\\.fake\\d$");
122 QRegularExpressionMatch match = regex.match (service);
123 bool test = match.hasMatch ();
124
125 if (!test)
126 {
127 QDBusConnection connection = QDBusConnection::sessionBus ();
128 QDBusConnectionInterface *interface = connection.interface ();
129
130 bool registered = interface->isServiceRegistered (service);
131
132 if (!registered)
133 {
134 auto watcher = new QDBusServiceWatcher (service, connection, QDBusServiceWatcher::WatchForRegistration, this);
135
136 connect (watcher, &QDBusServiceWatcher::serviceRegistered, this, [this, file_info, watcher] ()
137 {
138 watcher->deleteLater ();
139 this->loadFile (file_info);
140 });
141
142 return;
143 }
144 }
145
146 QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
147 const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
148
149 auto iter = m_indicatorsData.constFind(name);
150 if (iter != m_indicatorsData.constEnd())
151 {
152 const QString newFileInfoDir = QDir::cleanPath(file_info.canonicalPath());
153 IndicatorData* currentData = (*iter);
154 currentData->m_verified = true;
155
156 int file_info_location = -1;
157 int current_data_location = -1;
158
159 const QString currentDataDir = QDir::cleanPath(currentData->m_fileInfo.canonicalPath());
160
161 // if we've already got this indicator, we need to make sure we're not overwriting data which is
162 // from a lower priority standard path
163 QStringList xdgLocations = shellDataDirs();
164 for (int i = 0; i < xdgLocations.size(); i++)
165 {
166 const QString xdgLocation = QDir::cleanPath(xdgLocations[i]);
167
168 if (newFileInfoDir.startsWith(xdgLocation))
169 {
170 file_info_location = i;
171 }
172 if (currentDataDir.startsWith(xdgLocation))
173 {
174 current_data_location = i;
175 }
176
177 if (file_info_location != -1 && current_data_location != -1)
178 {
179 break;
180 }
181 }
182
183 // file location is higher (or of equal) priority. overwrite.
184 if (file_info_location <= current_data_location &&
185 file_info != currentData->m_fileInfo)
186 {
187 currentData->m_fileInfo = file_info;
188 Q_EMIT indicatorLoaded(name);
189 }
190 }
191 else
192 {
193 IndicatorData* data = new IndicatorData(name, file_info);
194 data->m_verified = true;
195 m_indicatorsData[name]= data;
196 Q_EMIT indicatorLoaded(name);
197 }
198}
199
200void IndicatorsManager::unload()
201{
202 QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
203 while(iter.hasNext())
204 {
205 iter.next();
206 Q_EMIT indicatorAboutToBeUnloaded(iter.key());
207 }
208
209 qDeleteAll(m_indicatorsData);
210 m_indicatorsData.clear();
211
212 setLoaded(false);
213}
214
215void IndicatorsManager::unloadFile(const QFileInfo& file)
216{
217 QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
218 while(iter.hasNext())
219 {
220 iter.next();
221 IndicatorData* data = iter.value();
222 if (data->m_fileInfo.absoluteFilePath() == file.absoluteFilePath())
223 {
224 if (!data->m_verified)
225 {
226 const QString name = data->m_name;
227 Q_EMIT indicatorAboutToBeUnloaded(name);
228
229 delete data;
230 iter.remove();
231 }
232 }
233 }
234
235 setLoaded(m_indicatorsData.size() > 0);
236}
237
238void IndicatorsManager::setLoaded(bool loaded)
239{
240 if (loaded != m_loaded)
241 {
242 m_loaded = loaded;
243 Q_EMIT loadedChanged(m_loaded);
244 }
245}
246
247QString IndicatorsManager::profile() const
248{
249 return m_profile;
250}
251
252void IndicatorsManager::setProfile(const QString& profile)
253{
254 if (m_profile != profile) {
255 m_profile = profile;
256 Q_EMIT profileChanged(m_profile);
257 }
258}
259
260void IndicatorsManager::startVerify(const QString& path)
261{
262 QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
263 while(iter.hasNext())
264 {
265 iter.next();
266 IndicatorData* data = iter.value();
267 if (data->m_fileInfo.canonicalPath() == path)
268 {
269 data->m_verified = false;
270 }
271 }
272}
273
274void IndicatorsManager::endVerify(const QString& path)
275{
276 QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
277 while(iter.hasNext())
278 {
279 iter.next();
280 IndicatorData* data = iter.value();
281 if (data->m_fileInfo.canonicalPath() == path)
282 {
283 if (!data->m_verified)
284 {
285 const QString name = data->m_name;
286 Q_EMIT indicatorAboutToBeUnloaded(name);
287
288 delete data;
289 iter.remove();
290 }
291 }
292 }
293}
294
295Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
296{
297 if (!m_indicatorsData.contains(indicator_name))
298 {
299 qWarning() << Q_FUNC_INFO << "Invalid indicator name: " << indicator_name;
300 return Indicator::Ptr();
301 }
302
303 IndicatorData *data = m_indicatorsData.value(indicator_name);
304 if (data->m_indicator)
305 {
306 return data->m_indicator;
307 }
308
309 Indicator::Ptr new_indicator(new Indicator(this));
310 data->m_indicator = new_indicator;
311 QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this);
312 new_indicator->init(data->m_fileInfo.fileName(), settings);
313
314 // convergence:
315 // 1) enable session indicator
316 // 2) enable keyboard indicator
317 //
318 // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
319 if ((new_indicator->identifier() == QStringLiteral("ayatana-indicator-session"))
320 || new_indicator->identifier() == QStringLiteral("ayatana-indicator-keyboard")) {
321 new_indicator->setProfile(QString(m_profile).replace(QStringLiteral("phone"), QStringLiteral("desktop")));
322 } else {
323 new_indicator->setProfile(m_profile);
324 }
325
326 QObject::connect(this, &IndicatorsManager::profileChanged, new_indicator.data(), &Indicator::setProfile);
327 return new_indicator;
328}
329
330QVector<Indicator::Ptr> IndicatorsManager::indicators()
331{
332 QVector<Indicator::Ptr> list;
333 Q_FOREACH(IndicatorData* data, m_indicatorsData)
334 {
335 Indicator::Ptr ret = indicator(data->m_name);
336 if (ret)
337 list.append(ret);
338 }
339 return list;
340}
341
342bool IndicatorsManager::isLoaded() const
343{
344 return m_loaded;
345}