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