21 #include "AccountsServiceDBusAdaptor.h"
22 #include "launcherbackend.h"
25 #include <QDBusArgument>
29 #include <QStandardPaths>
31 class LauncherBackendItem
40 LauncherBackend::LauncherBackend(QObject *parent):
41 QDBusVirtualObject(parent),
44 #ifndef LAUNCHER_TESTING
45 m_accounts =
new AccountsServiceDBusAdaptor(
this);
47 m_user = qgetenv(
"USER");
51 QDBusConnection con = QDBusConnection::sessionBus();
52 if (!con.registerService(
"com.canonical.Unity.Launcher")) {
53 qDebug() <<
"Unable to register launcher name";
55 if (!con.registerVirtualObject(
"/com/canonical/Unity/Launcher",
this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
56 qDebug() <<
"Unable to register launcher object";
60 LauncherBackend::~LauncherBackend()
63 QDBusConnection con = QDBusConnection::sessionBus();
64 con.unregisterService(
"com.canonical.Unity.Launcher");
65 con.unregisterObject(
"/com/canonical/Unity/Launcher");
70 Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
76 QStringList LauncherBackend::storedApplications()
const
81 void LauncherBackend::setStoredApplications(
const QStringList &appIds)
83 if (appIds.count() < m_storedApps.count()) {
84 Q_FOREACH(
const QString &appId, m_storedApps) {
85 if (!appIds.contains(appId)) {
86 delete m_itemCache.take(appId);
90 m_storedApps = appIds;
91 Q_FOREACH(
const QString &appId, appIds) {
92 if (!m_itemCache.contains(appId)) {
93 QString df = findDesktopFile(appId);
95 LauncherBackendItem *item = parseDesktopFile(df);
96 m_itemCache.insert(appId, item);
99 qWarning() <<
"cannot find desktop file for" << appId <<
". discarding app.";
100 m_storedApps.removeAll(appId);
107 QString LauncherBackend::desktopFile(
const QString &appId)
const
109 return findDesktopFile(appId);
112 QString LauncherBackend::displayName(
const QString &appId)
const
114 LauncherBackendItem *item = m_itemCache.value(appId,
nullptr);
116 return item->displayName;
119 QString df = findDesktopFile(appId);
121 LauncherBackendItem *item = parseDesktopFile(df);
122 m_itemCache.insert(appId, item);
123 return item->displayName;
129 QString LauncherBackend::icon(
const QString &appId)
const
132 LauncherBackendItem *item = getItem(appId);
134 iconName = item->icon;
140 QList<QuickListEntry> LauncherBackend::quickList(
const QString &appId)
const
147 return QList<QuickListEntry>();
150 int LauncherBackend::progress(const QString &appId)
const
158 int LauncherBackend::count(const QString &appId)
const
161 LauncherBackendItem *item = getItem(appId);
164 if (item->countVisible) {
172 void LauncherBackend::setCount(
const QString &appId,
int count)
const
174 LauncherBackendItem *item = getItem(appId);
176 bool emitchange =
false;
178 emitchange = (item->count != count);
186 Q_EMIT countChanged(appId, this->count(appId));
187 QVariant vcount(item->count);
188 emitPropChangedDbus(appId,
"count", vcount);
192 bool LauncherBackend::countVisible(
const QString &appId)
const
194 bool visible =
false;
195 LauncherBackendItem *item = getItem(appId);
198 visible = item->countVisible;
204 void LauncherBackend::setCountVisible(
const QString &appId,
bool visible)
const
206 LauncherBackendItem *item = getItem(appId);
208 bool emitchange =
false;
210 emitchange = (item->countVisible != visible);
211 item->countVisible = visible;
213 qDebug() <<
"Unable to find:" << appId;
219 Q_EMIT countChanged(appId, this->count(appId));
220 Q_EMIT countVisibleChanged(appId, item->countVisible);
221 QVariant vCountVisible(item->countVisible);
222 emitPropChangedDbus(appId,
"countVisible", vCountVisible);
226 void LauncherBackend::setUser(
const QString &username)
228 if (qgetenv(
"USER") ==
"lightdm" && m_user != username) {
234 void LauncherBackend::triggerQuickListAction(
const QString &appId,
const QString &quickListId)
238 Q_UNUSED(quickListId)
241 void LauncherBackend::syncFromAccounts()
243 QList<QVariantMap> apps;
244 bool defaults =
true;
246 m_storedApps.clear();
248 if (m_accounts && !m_user.isEmpty()) {
249 QVariant variant = m_accounts->getUserProperty(m_user,
"com.canonical.unity.AccountsService",
"launcher-items");
250 if (variant.isValid() && variant.canConvert<QDBusArgument>()) {
251 apps = qdbus_cast<QList<QVariantMap>>(variant.value<QDBusArgument>());
252 defaults = isDefaultsItem(apps);
256 if (m_accounts && defaults) {
257 QGSettings gSettings(
"com.canonical.Unity.Launcher",
"/com/canonical/unity/launcher/");
258 Q_FOREACH(
const QString &entry, gSettings.get(
"favorites").toStringList()) {
259 if (entry.startsWith(
"application://")) {
260 QString appId = entry;
262 appId.remove(
"application://");
263 if (appId.endsWith(
".desktop")) {
266 QString df = findDesktopFile(appId);
269 m_storedApps << appId;
271 if (!m_itemCache.contains(appId)) {
272 m_itemCache.insert(appId, parseDesktopFile(df));
276 if (entry.startsWith(
"appid://")) {
277 QString appId = entry;
278 appId.remove(
"appid://");
281 if (appId.split(
'/').count() != 3) {
282 qWarning() <<
"ignoring entry " + appId +
". Not a valid appId.";
285 appId = appId.split(
'/').first() +
"_" + appId.split(
'/').at(1);
286 QString df = findDesktopFile(appId);
289 m_storedApps << appId;
291 if (!m_itemCache.contains(appId)) {
292 m_itemCache.insert(appId, parseDesktopFile(df));
298 for (
const QVariant &app: apps) {
299 loadFromVariant(app.toMap());
304 void LauncherBackend::syncToAccounts()
306 if (m_accounts && !m_user.isEmpty()) {
307 QList<QVariantMap> items;
309 Q_FOREACH(
const QString &appId, m_storedApps) {
310 items << itemToVariant(appId);
313 m_accounts->setUserProperty(m_user,
"com.canonical.unity.AccountsService",
"launcher-items", QVariant::fromValue(items));
317 QString LauncherBackend::findDesktopFile(
const QString &appId)
const
320 QString helper = appId;
322 QStringList searchDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
323 #ifdef LAUNCHER_TESTING
329 helper = helper.replace(dashPos, 1,
'/');
332 Q_FOREACH(
const QString &searchDirName, searchDirs) {
333 QDir searchDir(searchDirName);
334 Q_FOREACH(
const QString &desktopFile, searchDir.entryList(QStringList() <<
"*.desktop")) {
335 if (desktopFile.startsWith(helper)) {
336 QFileInfo fileInfo(searchDir, desktopFile);
337 return fileInfo.absoluteFilePath();
342 dashPos = helper.indexOf(
"-");
343 }
while (dashPos != -1);
348 LauncherBackendItem* LauncherBackend::parseDesktopFile(
const QString &desktopFile)
const
350 QSettings settings(desktopFile, QSettings::IniFormat);
352 LauncherBackendItem* item =
new LauncherBackendItem();
353 item->displayName = settings.value(
"Desktop Entry/Name").toString();
355 QString iconString = settings.value(
"Desktop Entry/Icon").toString();
356 QString pathString = settings.value(
"Desktop Entry/Path").toString();
357 if (QFileInfo(iconString).exists()) {
358 item->icon = QFileInfo(iconString).absoluteFilePath();
359 }
else if (QFileInfo(pathString +
'/' + iconString).exists()) {
360 item->icon = pathString +
'/' + iconString;
362 item->icon =
"image://theme/" + iconString;
367 item->countVisible =
false;
373 LauncherBackendItem* LauncherBackend::getItem(
const QString &appId)
const
375 LauncherBackendItem *item = m_itemCache.value(appId,
nullptr);
377 QString df = findDesktopFile(appId);
379 item = parseDesktopFile(df);
381 m_itemCache[appId] = item;
383 qWarning() <<
"Unable to parse desktop file for" << appId <<
"path" << df;
386 qWarning() <<
"Unable to find desktop file for:" << appId;
391 qWarning() <<
"Unable to find item for: " << appId;
396 void LauncherBackend::loadFromVariant(
const QVariantMap &details)
398 if (!details.contains(
"id")) {
401 QString appId = details.value(
"id").toString();
403 LauncherBackendItem *item = m_itemCache.value(appId,
nullptr);
408 item =
new LauncherBackendItem();
410 item->displayName = details.value(
"name").toString();
411 item->icon = details.value(
"icon").toString();
412 item->count = details.value(
"count").toInt();
413 item->countVisible = details.value(
"countVisible").toBool();
415 m_itemCache.insert(appId, item);
416 m_storedApps.append(appId);
419 QVariantMap LauncherBackend::itemToVariant(
const QString &appId)
const
421 LauncherBackendItem *item = m_itemCache.value(appId);
423 details.insert(
"id", appId);
424 details.insert(
"name", item->displayName);
425 details.insert(
"icon", item->icon);
426 details.insert(
"count", item->count);
427 details.insert(
"countVisible", item->countVisible);
431 bool LauncherBackend::isDefaultsItem(
const QList<QVariantMap> &apps)
const
436 return (apps.size() == 1 && apps[0].value(
"defaults").toBool());
439 bool LauncherBackend::handleMessage(
const QDBusMessage& message,
const QDBusConnection& connection)
442 if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
445 if (message.interface() !=
"org.freedesktop.DBus.Properties") {
448 if (message.arguments()[0].toString() !=
"com.canonical.Unity.Launcher.Item") {
453 QString pathtemp = message.path();
454 if (!pathtemp.startsWith(
"/com/canonical/Unity/Launcher/")) {
457 pathtemp.remove(
"/com/canonical/Unity/Launcher/");
458 if (pathtemp.indexOf(
'/') >= 0) {
463 QString appid = decodeAppId(pathtemp);
466 if (message.member() ==
"Get") {
467 if (message.arguments()[1].toString() ==
"count") {
468 retval.append(QVariant::fromValue(QDBusVariant(this->count(appid))));
469 }
else if (message.arguments()[1].toString() ==
"countVisible") {
470 retval.append(QVariant::fromValue(QDBusVariant(this->countVisible(appid))));
472 }
else if (message.member() ==
"Set") {
473 if (message.arguments()[1].toString() ==
"count") {
474 this->setCount(appid, message.arguments()[2].value<QDBusVariant>().variant().toInt());
475 }
else if (message.arguments()[1].toString() ==
"countVisible") {
476 this->setCountVisible(appid, message.arguments()[2].value<QDBusVariant>().variant().toBool());
478 }
else if (message.member() ==
"GetAll") {
479 retval.append(this->itemToVariant(appid));
484 QDBusMessage reply = message.createReply(retval);
485 return connection.send(reply);
488 QString LauncherBackend::introspect(
const QString &path)
const
491 if (path ==
"/com/canonical/Unity/Launcher/" || path ==
"/com/canonical/Unity/Launcher") {
494 Q_FOREACH(
const QString &appId, m_itemCache.keys()) {
495 nodes.append(
"<node name=\"");
496 nodes.append(encodeAppId(appId));
497 nodes.append(
"\"/>\n");
504 if (!path.startsWith(
"/com/canonical/Unity/Launcher")) {
510 "<interface name=\"com.canonical.Unity.Launcher.Item\">"
511 "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
512 "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
517 QString LauncherBackend::decodeAppId(
const QString& path)
519 QByteArray bytes = path.toUtf8();
522 for (
int i = 0; i < bytes.size(); ++i) {
523 char chr = bytes.at(i);
527 number.append(bytes.at(i+1));
528 number.append(bytes.at(i+2));
531 char newchar = number.toUInt(&okay, 16);
533 decoded.append(newchar);
541 return QString::fromUtf8(decoded);
544 QString LauncherBackend::encodeAppId(
const QString& appId)
546 QByteArray bytes = appId.toUtf8();
549 for (
int i = 0; i < bytes.size(); ++i) {
550 uchar chr = bytes.at(i);
552 if ((chr >=
'a' && chr <=
'z') ||
553 (chr >=
'A' && chr <=
'Z') ||
554 (chr >=
'0' && chr <=
'9'&& i != 0)) {
557 QString hexval = QString(
"_%1").arg(chr, 2, 16, QChar(
'0'));
558 encoded.append(hexval.toUpper());
565 void LauncherBackend::emitPropChangedDbus(
const QString& appId,
const QString& property, QVariant &value)
const
567 QString path(
"/com/canonical/Unity/Launcher/");
568 path.append(encodeAppId(appId));
570 QDBusMessage message = QDBusMessage::createSignal(path,
"org.freedesktop.DBus.Properties",
"PropertiesChanged");
572 QList<QVariant> arguments;
573 QVariantHash changedprops;
574 changedprops[property] = QVariant::fromValue(QDBusVariant(value));
575 QVariantList deletedprops;
577 arguments.append(changedprops);
578 arguments.append(deletedprops);
580 message.setArguments(arguments);
582 QDBusConnection con = QDBusConnection::sessionBus();