Unity 8
indicatorsmanager.cpp
1 /*
2  * Copyright (C) 2013 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  * Author: Nick Dedekind <nick.dedekind@canonical.com>
17  */
18 
19 #include "indicatorsmanager.h"
20 
21 #include <QSettings>
22 #include <QDebug>
23 
24 #include <paths.h>
25 
26 
27 class IndicatorsManager::IndicatorData
28 {
29 public:
30  IndicatorData(const QString& name, const QFileInfo& fileInfo)
31  : m_name(name)
32  , m_fileInfo(fileInfo)
33  , m_verified (true)
34  {
35  }
36 
37  QString m_name;
38  QFileInfo m_fileInfo;
39 
40  bool m_verified;
41  Indicator::Ptr m_indicator;
42 };
43 
44 IndicatorsManager::IndicatorsManager(QObject* parent)
45  : QObject(parent)
46  , m_loaded(false)
47  , m_profile(QStringLiteral("phone"))
48 {
49 }
50 
51 IndicatorsManager::~IndicatorsManager()
52 {
53  unload();
54 }
55 
56 void IndicatorsManager::load()
57 {
58  unload();
59 
60  const QStringList xdgLocations = shellDataDirs();
61 
62  m_fsWatcher.reset(new QFileSystemWatcher(this));
63 
64  Q_FOREACH(const QString& xdgLocation, xdgLocations)
65  {
66  const QString indicator_path = QDir::cleanPath(xdgLocation + "/unity/indicators");
67  QDir indicator_dir(indicator_path);
68  if (indicator_dir.exists())
69  {
70  // watch folder for changes.
71  m_fsWatcher->addPath(indicator_path);
72 
73  loadDir(indicator_dir);
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  if (m_platform.isPC() && name == "indicator-keyboard") {
120  return; // convergence: skip this indicator until it works in Mir
121  }
122 
123  auto iter = m_indicatorsData.constFind(name);
124  if (iter != m_indicatorsData.constEnd())
125  {
126  const QString newFileInfoDir = QDir::cleanPath(file_info.canonicalPath());
127  IndicatorData* currentData = (*iter);
128  currentData->m_verified = true;
129 
130  int file_info_location = -1;
131  int current_data_location = -1;
132 
133  const QString currentDataDir = QDir::cleanPath(currentData->m_fileInfo.canonicalPath());
134 
135  // if we've already got this indicator, we need to make sure we're not overwriting data which is
136  // from a lower priority standard path
137  QStringList xdgLocations = shellDataDirs();
138  for (int i = 0; i < xdgLocations.size(); i++)
139  {
140  const QString indicatorDir = QDir::cleanPath(xdgLocations[i] + "/unity/indicators");
141 
142  if (newFileInfoDir == indicatorDir)
143  {
144  file_info_location = i;
145  }
146  if (currentDataDir == indicatorDir)
147  {
148  current_data_location = i;
149  }
150 
151  if (file_info_location != -1 && current_data_location != -1)
152  {
153  break;
154  }
155  }
156 
157  // file location is higher (or of equal) priority. overwrite.
158  if (file_info_location <= current_data_location &&
159  file_info != currentData->m_fileInfo)
160  {
161  currentData->m_fileInfo = file_info;
162  Q_EMIT indicatorLoaded(name);
163  }
164  }
165  else
166  {
167  IndicatorData* data = new IndicatorData(name, file_info);
168  data->m_verified = true;
169  m_indicatorsData[name]= data;
170  Q_EMIT indicatorLoaded(name);
171  }
172 }
173 
174 void IndicatorsManager::unload()
175 {
176  QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
177  while(iter.hasNext())
178  {
179  iter.next();
180  Q_EMIT indicatorAboutToBeUnloaded(iter.key());
181  }
182 
183  qDeleteAll(m_indicatorsData);
184  m_indicatorsData.clear();
185 
186  setLoaded(false);
187 }
188 
189 void IndicatorsManager::unloadFile(const QFileInfo& file)
190 {
191  QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
192  while(iter.hasNext())
193  {
194  iter.next();
195  IndicatorData* data = iter.value();
196  if (data->m_fileInfo.absoluteFilePath() == file.absoluteFilePath())
197  {
198  if (!data->m_verified)
199  {
200  const QString name = data->m_name;
201  Q_EMIT indicatorAboutToBeUnloaded(name);
202 
203  delete data;
204  iter.remove();
205  }
206  }
207  }
208 
209  setLoaded(m_indicatorsData.size() > 0);
210 }
211 
212 void IndicatorsManager::setLoaded(bool loaded)
213 {
214  if (loaded != m_loaded)
215  {
216  m_loaded = loaded;
217  Q_EMIT loadedChanged(m_loaded);
218  }
219 }
220 
221 QString IndicatorsManager::profile() const
222 {
223  return m_profile;
224 }
225 
226 void IndicatorsManager::setProfile(const QString& profile)
227 {
228  if (m_profile != profile) {
229  m_profile = profile;
230  Q_EMIT profileChanged(m_profile);
231  }
232 }
233 
234 void IndicatorsManager::startVerify(const QString& path)
235 {
236  QHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
237  while(iter.hasNext())
238  {
239  iter.next();
240  IndicatorData* data = iter.value();
241  if (data->m_fileInfo.canonicalPath() == path)
242  {
243  data->m_verified = false;
244  }
245  }
246 }
247 
248 void IndicatorsManager::endVerify(const QString& path)
249 {
250  QMutableHashIterator<QString, IndicatorData*> iter(m_indicatorsData);
251  while(iter.hasNext())
252  {
253  iter.next();
254  IndicatorData* data = iter.value();
255  if (data->m_fileInfo.canonicalPath() == path)
256  {
257  if (!data->m_verified)
258  {
259  const QString name = data->m_name;
260  Q_EMIT indicatorAboutToBeUnloaded(name);
261 
262  delete data;
263  iter.remove();
264  }
265  }
266  }
267 }
268 
269 Indicator::Ptr IndicatorsManager::indicator(const QString& indicator_name)
270 {
271  if (!m_indicatorsData.contains(indicator_name))
272  {
273  qWarning() << Q_FUNC_INFO << "Invalid indicator name: " << indicator_name;
274  return Indicator::Ptr();
275  }
276 
277  IndicatorData *data = m_indicatorsData.value(indicator_name);
278  if (data->m_indicator)
279  {
280  return data->m_indicator;
281  }
282 
283  Indicator::Ptr new_indicator(new Indicator(this));
284  data->m_indicator = new_indicator;
285  QSettings settings(data->m_fileInfo.absoluteFilePath(), QSettings::IniFormat, this);
286  new_indicator->init(data->m_fileInfo.fileName(), settings);
287 
288  // convergence:
289  // 1) enable session indicator conditionally, typically when running in a multisession/multiuser environment
290  // 2) on a PC, switch the battery/power indicator to desktop mode,
291  // can't control brightness for now and phone-on-desktop broken (FIXME)
292  //
293  // The rest of the indicators respect their default profile (which is "phone", even on desktop PCs)
294  if ((new_indicator->identifier() == QStringLiteral("indicator-session") && m_platform.isMultiSession())
295  || (new_indicator->identifier() == QStringLiteral("indicator-power") && m_platform.isPC())) {
296  new_indicator->setProfile("desktop");
297  } else {
298  new_indicator->setProfile(m_profile);
299  }
300 
301  QObject::connect(this, &IndicatorsManager::profileChanged, new_indicator.data(), &Indicator::setProfile);
302  return new_indicator;
303 }
304 
305 QVector<Indicator::Ptr> IndicatorsManager::indicators()
306 {
307  QVector<Indicator::Ptr> list;
308  Q_FOREACH(IndicatorData* data, m_indicatorsData)
309  {
310  Indicator::Ptr ret = indicator(data->m_name);
311  if (ret)
312  list.append(ret);
313  }
314  return list;
315 }
316 
317 bool IndicatorsManager::isLoaded() const
318 {
319  return m_loaded;
320 }