Lomiri
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 
22 #include <paths.h>
23 
24 
25 class IndicatorsManager::IndicatorData
26 {
27 public:
28  IndicatorData(const QString& name, const QFileInfo& fileInfo)
29  : m_name(name)
30  , m_fileInfo(fileInfo)
31  , m_verified (true)
32  {
33  }
34 
35  QString m_name;
36  QFileInfo m_fileInfo;
37 
38  bool m_verified;
39  Indicator::Ptr m_indicator;
40 };
41 
42 IndicatorsManager::IndicatorsManager(QObject* parent)
43  : QObject(parent)
44  , m_loaded(false)
45  , m_profile(QStringLiteral("phone"))
46 {
47 }
48 
49 IndicatorsManager::~IndicatorsManager()
50 {
51  unload();
52 }
53 
54 void IndicatorsManager::load()
55 {
56  unload();
57 
58  m_fsWatcher.reset(new QFileSystemWatcher(this));
59 
60  for (const auto xdgPath : shellDataDirs()) {
61  // For legacy reasons we keep the old unity indicator path
62  const auto unityPath = QDir::cleanPath(xdgPath + "/unity/indicators");
63  if (QFile::exists(unityPath)) {
64  // watch folder for changes.
65  m_fsWatcher->addPath(unityPath);
66  loadDir(unityPath);
67  }
68 
69  const auto ayatanaPath = QDir::cleanPath(xdgPath + "/ayatana/indicators");
70  if (QFile::exists(ayatanaPath)) {
71  // watch folder for changes.
72  m_fsWatcher->addPath(ayatanaPath);
73  loadDir(ayatanaPath);
74  }
75  }
76 
77  QObject::connect(m_fsWatcher.data(), &QFileSystemWatcher::directoryChanged, this, &IndicatorsManager::onDirectoryChanged);
78  QObject::connect(m_fsWatcher.data(), &QFileSystemWatcher::fileChanged, this, &IndicatorsManager::onFileChanged);
79  setLoaded(true);
80 }
81 
82 void IndicatorsManager::onDirectoryChanged(const QString& directory)
83 {
84  loadDir(QDir(directory));
85 }
86 
87 void IndicatorsManager::onFileChanged(const QString& file)
88 {
89  QFileInfo file_info(file);
90  if (!file_info.exists())
91  {
92  unloadFile(file_info);
93  return;
94  }
95  else
96  {
97  loadFile(QFileInfo(file));
98  }
99 }
100 
101 void IndicatorsManager::loadDir(const QDir& dir)
102 {
103  startVerify(dir.canonicalPath());
104 
105  const QFileInfoList indicator_files = dir.entryInfoList(QStringList(), QDir::Files|QDir::NoDotAndDotDot);
106  Q_FOREACH(const QFileInfo& indicator_file, indicator_files)
107  {
108  loadFile(indicator_file);
109  }
110 
111  endVerify(dir.canonicalPath());
112 }
113 
114 void IndicatorsManager::loadFile(const QFileInfo& file_info)
115 {
116  QSettings indicator_settings(file_info.absoluteFilePath(), QSettings::IniFormat, this);
117  const QString name = indicator_settings.value(QStringLiteral("Indicator Service/Name")).toString();
118 
119  auto iter = m_indicatorsData.constFind(name);
120  if (iter != m_indicatorsData.constEnd())
121  {
122  const QString newFileInfoDir = QDir::cleanPath(file_info.canonicalPath());
123  IndicatorData* currentData = (*iter);
124  currentData->m_verified = true;
125 
126  int file_info_location = -1;
127  int current_data_location = -1;
128 
129  const QString currentDataDir = QDir::cleanPath(currentData->m_fileInfo.canonicalPath());
130 
131  // if we've already got this indicator, we need to make sure we're not overwriting data which is
132  // from a lower priority standard path
133  QStringList xdgLocations = shellDataDirs();
134  for (int i = 0; i < xdgLocations.size(); i++)
135  {
136  const QString xdgLocation = QDir::cleanPath(xdgLocations[i]);
137 
138  if (newFileInfoDir.startsWith(xdgLocation))
139  {
140  file_info_location = i;
141  }
142  if (currentDataDir.startsWith(xdgLocation))
143  {
144  current_data_location = i;
145  }
146 
147  if (file_info_location != -1 && current_data_location != -1)
148  {
149  break;
150  }
151  }
152 
153  // file location is higher (or of equal) priority. overwrite.
154  if (file_info_location <= current_data_location &&
155  file_info != currentData->m_fileInfo)
156  {
157  currentData->m_fileInfo = file_info;
158  Q_EMIT indicatorLoaded(name);
159  }
160  }
161  else
162  {
163  IndicatorData* data = new IndicatorData(name, file_info);
164  data->m_verified = true;
165  m_indicatorsData[name]= data;
166  Q_EMIT indicatorLoaded(name);
167  }
168 }
169 
170 void IndicatorsManager::unload()
171 {
172  QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
173  while(iter.hasNext())
174  {
175  iter.next();
176  Q_EMIT indicatorAboutToBeUnloaded(iter.key());
177  }
178 
179  qDeleteAll(m_indicatorsData);
180  m_indicatorsData.clear();
181 
182  setLoaded(false);
183 }
184 
185 void IndicatorsManager::unloadFile(const QFileInfo& file)
186 {
187  QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
188  while(iter.hasNext())
189  {
190  iter.next();
191  IndicatorData* data = iter.value();
192  if (data->m_fileInfo.absoluteFilePath() == file.absoluteFilePath())
193  {
194  if (!data->m_verified)
195  {
196  const QString name = data->m_name;
197  Q_EMIT indicatorAboutToBeUnloaded(name);
198 
199  delete data;
200  iter.remove();
201  }
202  }
203  }
204 
205  setLoaded(m_indicatorsData.size() > 0);
206 }
207 
208 void IndicatorsManager::setLoaded(bool loaded)
209 {
210  if (loaded != m_loaded)
211  {
212  m_loaded = loaded;
213  Q_EMIT loadedChanged(m_loaded);
214  }
215 }
216 
217 QString IndicatorsManager::profile() const
218 {
219  return m_profile;
220 }
221 
222 void IndicatorsManager::setProfile(const QString& profile)
223 {
224  if (m_profile != profile) {
225  m_profile = profile;
226  Q_EMIT profileChanged(m_profile);
227  }
228 }
229 
230 void IndicatorsManager::startVerify(const QString& path)
231 {
232  QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
233  while(iter.hasNext())
234  {
235  iter.next();
236  IndicatorData* data = iter.value();
237  if (data->m_fileInfo.canonicalPath() == path)
238  {
239  data->m_verified = false;
240  }
241  }
242 }
243 
244 void IndicatorsManager::endVerify(const QString& path)
245 {
246  QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
247  while(iter.hasNext())
248  {
249  iter.next();
250  IndicatorData* data = iter.value();
251  if (data->m_fileInfo.canonicalPath() == path)
252  {
253  if (!data->m_verified)
254  {
255  const QString name = data->m_name;
256  Q_EMIT indicatorAboutToBeUnloaded(name);
257 
258  delete data;
259  iter.remove();
260  }
261  }
262  }
263 }
264 
265 Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
266 {
267  if (!m_indicatorsData.contains(indicator_name))
268  {
269  qWarning() << Q_FUNC_INFO << "Invalid indicator name: " << indicator_name;
270  return Indicator::Ptr();
271  }
272 
273  IndicatorData *data = m_indicatorsData.value(indicator_name);
274  if (data->m_indicator)
275  {
276  return data->m_indicator;
277  }
278 
279  Indicator::Ptr new_indicator(new Indicator(this));
280  data->m_indicator = new_indicator;
281  QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this);
282  new_indicator->init(data->m_fileInfo.fileName(), settings);
283 
284  // convergence:
285  // 1) enable session indicator
286  // 2) enable keyboard indicator
287  //
288  // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
289  if ((new_indicator->identifier() == QStringLiteral("indicator-session"))
290  || new_indicator->identifier() == QStringLiteral("indicator-keyboard")) {
291  new_indicator->setProfile(QString(m_profile).replace(QStringLiteral("phone"), QStringLiteral("desktop")));
292  } else {
293  new_indicator->setProfile(m_profile);
294  }
295 
296  QObject::connect(this, &IndicatorsManager::profileChanged, new_indicator.data(), &Indicator::setProfile);
297  return new_indicator;
298 }
299 
300 QVector<Indicator::Ptr> IndicatorsManager::indicators()
301 {
302  QVector<Indicator::Ptr> list;
303  Q_FOREACH(IndicatorData* data, m_indicatorsData)
304  {
305  Indicator::Ptr ret = indicator(data->m_name);
306  if (ret)
307  list.append(ret);
308  }
309  return list;
310 }
311 
312 bool IndicatorsManager::isLoaded() const
313 {
314  return m_loaded;
315 }