Unity 8
 All Classes Functions Properties
launcherbackend.cpp
1 /*
2  * Copyright (C) 2013 Canonical, Ltd.
3  *
4  * Authors:
5  * Michael Terry <michael.terry@canonical.com>
6  * Michael Zanetti <michael.zanetti@canonical.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "AccountsServiceDBusAdaptor.h"
22 #include "launcherbackend.h"
23 
24 #include <QDir>
25 #include <QDBusArgument>
26 #include <QFileInfo>
27 #include <QGSettings>
28 #include <QDebug>
29 #include <QStandardPaths>
30 
31 class LauncherBackendItem
32 {
33 public:
34  QString desktopFile;
35  QString displayName;
36  QString icon;
37 };
38 
39 LauncherBackend::LauncherBackend(QObject *parent):
40  QObject(parent),
41  m_accounts(nullptr)
42 {
43 #ifndef LAUNCHER_TESTING
44  m_accounts = new AccountsServiceDBusAdaptor(this);
45 #endif
46  m_user = qgetenv("USER");
47  syncFromAccounts();
48 }
49 
50 LauncherBackend::~LauncherBackend()
51 {
52  m_storedApps.clear();
53 
54  Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
55  delete item;
56  }
57  m_itemCache.clear();
58 }
59 
60 QStringList LauncherBackend::storedApplications() const
61 {
62  return m_storedApps;
63 }
64 
65 void LauncherBackend::setStoredApplications(const QStringList &appIds)
66 {
67  if (appIds.count() < m_storedApps.count()) {
68  Q_FOREACH(const QString &appId, m_storedApps) {
69  if (!appIds.contains(appId)) {
70  delete m_itemCache.take(appId);
71  }
72  }
73  }
74  m_storedApps = appIds;
75  Q_FOREACH(const QString &appId, appIds) {
76  if (!m_itemCache.contains(appId)) {
77  QString df = findDesktopFile(appId);
78  if (!df.isEmpty()) {
79  LauncherBackendItem *item = parseDesktopFile(df);
80  m_itemCache.insert(appId, item);
81  } else {
82  // Cannot find any data for that app... ignoring it.
83  qWarning() << "cannot find desktop file for" << appId << ". discarding app.";
84  m_storedApps.removeAll(appId);
85  }
86  }
87  }
88  syncToAccounts();
89 }
90 
91 QString LauncherBackend::desktopFile(const QString &appId) const
92 {
93  LauncherBackendItem *item = m_itemCache.value(appId);
94  if (item) {
95  return item->desktopFile;
96  }
97 
98  return findDesktopFile(appId);
99 }
100 
101 QString LauncherBackend::displayName(const QString &appId) const
102 {
103  LauncherBackendItem *item = m_itemCache.value(appId);
104  if (item) {
105  return item->displayName;
106  }
107 
108  QString df = findDesktopFile(appId);
109  if (!df.isEmpty()) {
110  LauncherBackendItem *item = parseDesktopFile(df);
111  m_itemCache.insert(appId, item);
112  return item->displayName;
113  }
114 
115  return QString();
116 }
117 
118 QString LauncherBackend::icon(const QString &appId) const
119 {
120  QString iconName;
121  LauncherBackendItem *item = m_itemCache.value(appId);
122  if (item) {
123  iconName = item->icon;
124  } else {
125  QString df = findDesktopFile(appId);
126  if (!df.isEmpty()) {
127  LauncherBackendItem *item = parseDesktopFile(df);
128  m_itemCache.insert(appId, item);
129  iconName = item->icon;
130  }
131  }
132 
133  return iconName;
134 }
135 
136 QList<QuickListEntry> LauncherBackend::quickList(const QString &appId) const
137 {
138  // TODO: Get static (from .desktop file) and dynamic (from the app itself)
139  // entries and return them here. Frontend related entries (like "Pin to launcher")
140  // don't matter here. This is just the backend part.
141  // TODO: emit quickListChanged() when the dynamic part changes
142  Q_UNUSED(appId)
143  return QList<QuickListEntry>();
144 }
145 
146 int LauncherBackend::progress(const QString &appId) const
147 {
148  // TODO: Return value for progress emblem.
149  // TODO: emit progressChanged() when this value changes.
150  Q_UNUSED(appId)
151  return -1;
152 }
153 
154 int LauncherBackend::count(const QString &appId) const
155 {
156  // TODO: Return value for count emblem.
157  // TODO: emit countChanged() when this value changes.
158  Q_UNUSED(appId)
159  return 0;
160 }
161 
162 void LauncherBackend::setUser(const QString &username)
163 {
164  if (qgetenv("USER") == "lightdm" && m_user != username) {
165  m_user = username;
166  syncFromAccounts();
167  }
168 }
169 
170 void LauncherBackend::triggerQuickListAction(const QString &appId, const QString &quickListId)
171 {
172  // TODO: execute the given quicklist action
173  Q_UNUSED(appId)
174  Q_UNUSED(quickListId)
175 }
176 
177 void LauncherBackend::syncFromAccounts()
178 {
179  QList<QVariantMap> apps;
180  bool defaults = true;
181 
182  m_storedApps.clear();
183 
184  if (m_accounts && !m_user.isEmpty()) {
185  QVariant variant = m_accounts->getUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items");
186  if (variant.isValid() && variant.canConvert<QDBusArgument>()) {
187  apps = qdbus_cast<QList<QVariantMap>>(variant.value<QDBusArgument>());
188  defaults = isDefaultsItem(apps);
189  }
190  }
191 
192  if (m_accounts && defaults) { // Checking accounts as it'll be null when !useStorage
193  QGSettings gSettings("com.canonical.Unity.Launcher", "/com/canonical/unity/launcher/");
194  Q_FOREACH(const QString &entry, gSettings.get("favorites").toStringList()) {
195  if (entry.startsWith("application://")) {
196  QString appId = entry;
197  // Transform "application://foobar.desktop" to "foobar"
198  appId.remove("application://");
199  if (appId.endsWith(".desktop")) {
200  appId.chop(8);
201  }
202  QString df = findDesktopFile(appId);
203 
204  if (!df.isEmpty()) {
205  m_storedApps << appId;
206 
207  if (!m_itemCache.contains(appId)) {
208  m_itemCache.insert(appId, parseDesktopFile(df));
209  }
210  }
211  }
212  }
213  } else {
214  for (const QVariant &app: apps) {
215  loadFromVariant(app.toMap());
216  }
217  }
218 }
219 
220 void LauncherBackend::syncToAccounts()
221 {
222  if (m_accounts && !m_user.isEmpty()) {
223  QList<QVariantMap> items;
224 
225  Q_FOREACH(const QString &appId, m_storedApps) {
226  items << itemToVariant(appId);
227  }
228 
229  m_accounts->setUserProperty(m_user, "com.canonical.unity.AccountsService", "launcher-items", QVariant::fromValue(items));
230  }
231 }
232 
233 QString LauncherBackend::findDesktopFile(const QString &appId) const
234 {
235  int dashPos = -1;
236  QString helper = appId;
237 
238  QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
239 #ifdef LAUNCHER_TESTING
240  searchDirs << "";
241 #endif
242 
243  do {
244  if (dashPos != -1) {
245  helper = helper.replace(dashPos, 1, '/');
246  }
247 
248  Q_FOREACH(const QString &searchDir, searchDirs) {
249  QFileInfo fileInfo(QDir(searchDir), helper + ".desktop");
250  if (fileInfo.exists()) {
251  return fileInfo.absoluteFilePath();
252  }
253  }
254 
255  dashPos = helper.indexOf("-");
256  } while (dashPos != -1);
257 
258  return QString();
259 }
260 
261 LauncherBackendItem* LauncherBackend::parseDesktopFile(const QString &desktopFile) const
262 {
263  QSettings settings(desktopFile, QSettings::IniFormat);
264 
265  LauncherBackendItem* item = new LauncherBackendItem();
266  item->desktopFile = desktopFile;
267  item->displayName = settings.value("Desktop Entry/Name").toString();
268 
269  QString iconString = settings.value("Desktop Entry/Icon").toString();
270  QString pathString = settings.value("Desktop Entry/Path").toString();
271  if (QFileInfo(iconString).exists()) {
272  item->icon = QFileInfo(iconString).absoluteFilePath();
273  } else if (QFileInfo(pathString + '/' + iconString).exists()) {
274  item->icon = pathString + '/' + iconString;
275  } else {
276  item->icon = "image://theme/" + iconString;
277  }
278  return item;
279 }
280 
281 void LauncherBackend::loadFromVariant(const QVariantMap &details)
282 {
283  if (!details.contains("id")) {
284  return;
285  }
286  QString appId = details.value("id").toString();
287 
288  LauncherBackendItem *item = m_itemCache.value(appId);
289  if (item) {
290  delete item;
291  }
292 
293  item = new LauncherBackendItem();
294 
295  item->desktopFile = details.value("desktopFile").toString();
296  item->displayName = details.value("name").toString();
297  item->icon = details.value("icon").toString();
298 
299  m_itemCache.insert(appId, item);
300  m_storedApps.append(appId);
301 }
302 
303 QVariantMap LauncherBackend::itemToVariant(const QString &appId) const
304 {
305  LauncherBackendItem *item = m_itemCache.value(appId);
306  QVariantMap details;
307  details.insert("id", appId);
308  details.insert("name", item->displayName);
309  details.insert("icon", item->icon);
310  details.insert("desktopFile", item->desktopFile);
311  return details;
312 }
313 
314 bool LauncherBackend::isDefaultsItem(const QList<QVariantMap> &apps) const
315 {
316  // To differentiate between an empty list and a list that hasn't been set
317  // yet (and should thus be populated with the defaults), we use a special
318  // list of one item with the 'defaults' field set to true.
319  return (apps.size() == 1 && apps[0].value("defaults").toBool());
320 }