@@ -34,10 +34,12 @@ public:
3434 QString desktopFile;
3535 QString displayName;
3636 QString icon;
37+ int count;
38+ bool countVisible;
3739};
3840
3941LauncherBackend::LauncherBackend(QObject *parent):
40− QObject(parent),
42+ QDBusVirtualObject(parent),
4143 m_accounts(nullptr)
4244{
4345#ifndef LAUNCHER_TESTING
@@ -45,10 +47,25 @@ LauncherBackend::LauncherBackend(QObject *parent):
4547#endif
4648 m_user = qgetenv("USER");
4749 syncFromAccounts();
50+
51+ /* Set up ourselves on DBus */
52+ QDBusConnection con = QDBusConnection::sessionBus();
53+ if (!con.registerService("com.canonical.Unity.Launcher")) {
54+ qDebug() << "Unable to register launcher name";
55+ }
56+ if (!con.registerVirtualObject("/com/canonical/Unity/Launcher", this, QDBusConnection::VirtualObjectRegisterOption::SubPath)) {
57+ qDebug() << "Unable to register launcher object";
58+ }
4859}
4960
5061LauncherBackend::~LauncherBackend()
5162{
63+ /* Remove oursevles from DBus */
64+ QDBusConnection con = QDBusConnection::sessionBus();
65+ con.unregisterService("com.canonical.Unity.Launcher");
66+ con.unregisterObject("/com/canonical/Unity/Launcher");
67+
68+ /* Clear data */
5269 m_storedApps.clear();
5370
5471 Q_FOREACH(LauncherBackendItem *item, m_itemCache) {
@@ -90,7 +107,7 @@ void LauncherBackend::setStoredApplications(const QStringList &appIds)
90107
91108QString LauncherBackend::desktopFile(const QString &appId) const
92109{
93− LauncherBackendItem *item = m_itemCache.value(appId);
110+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
94111 if (item) {
95112 return item->desktopFile;
96113 }
@@ -100,7 +117,7 @@ QString LauncherBackend::desktopFile(const QString &appId) const
100117
101118QString LauncherBackend::displayName(const QString &appId) const
102119{
103− LauncherBackendItem *item = m_itemCache.value(appId);
120+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
104121 if (item) {
105122 return item->displayName;
106123 }
@@ -118,16 +135,9 @@ QString LauncherBackend::displayName(const QString &appId) const
118135QString LauncherBackend::icon(const QString &appId) const
119136{
120137 QString iconName;
121− LauncherBackendItem *item = m_itemCache.value(appId);
138+ LauncherBackendItem *item = getItem(appId);
122139 if (item) {
123140 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− }
131141 }
132142
133143 return iconName;
@@ -153,10 +163,70 @@ int LauncherBackend::progress(const QString &appId) const
153163
154164int LauncherBackend::count(const QString &appId) const
155165{
156− // TODO: Return value for count emblem.
157− // TODO: emit countChanged() when this value changes.
158− Q_UNUSED(appId)
159− return 0;
166+ int count = -1;
167+ LauncherBackendItem *item = getItem(appId);
168+
169+ if (item) {
170+ if (item->countVisible) {
171+ count = item->count;
172+ }
173+ }
174+
175+ return count;
176+}
177+
178+void LauncherBackend::setCount(const QString &appId, int count) const
179+{
180+ LauncherBackendItem *item = getItem(appId);
181+
182+ bool emitchange = false;
183+ if (item) {
184+ emitchange = (item->count != count);
185+ item->count = count;
186+ }
187+
188+ if (emitchange) {
189+ /* TODO: This needs to use the accessor to handle the visibility
190+ correctly, but when we have the two properties we can just use
191+ the local value */
192+ Q_EMIT countChanged(appId, this->count(appId));
193+ QVariant vcount(item->count);
194+ emitPropChangedDbus(appId, "count", vcount);
195+ }
196+}
197+
198+bool LauncherBackend::countVisible(const QString &appId) const
199+{
200+ bool visible = false;
201+ LauncherBackendItem *item = getItem(appId);
202+
203+ if (item) {
204+ visible = item->countVisible;
205+ }
206+
207+ return visible;
208+}
209+
210+void LauncherBackend::setCountVisible(const QString &appId, bool visible) const
211+{
212+ LauncherBackendItem *item = getItem(appId);
213+
214+ bool emitchange = false;
215+ if (item) {
216+ emitchange = (item->countVisible != visible);
217+ item->countVisible = visible;
218+ } else {
219+ qDebug() << "Unable to find:" << appId;
220+ }
221+
222+ if (emitchange) {
223+ /* TODO: Because we're using visible in determining the
224+ count we need to emit a count changed as well */
225+ Q_EMIT countChanged(appId, this->count(appId));
226+ Q_EMIT countVisibleChanged(appId, item->countVisible);
227+ QVariant vCountVisible(item->countVisible);
228+ emitPropChangedDbus(appId, "countVisible", vCountVisible);
229+ }
160230}
161231
162232void LauncherBackend::setUser(const QString &username)
@@ -275,6 +345,35 @@ LauncherBackendItem* LauncherBackend::parseDesktopFile(const QString &desktopFil
275345 } else {
276346 item->icon = "image://theme/" + iconString;
277347 }
348+
349+ /* TODO: These should be looked up in a cache somewhere */
350+ item->count = 0;
351+ item->countVisible = false;
352+
353+ return item;
354+}
355+
356+/* Gets an item, and tries to create a new one if we need it to */
357+LauncherBackendItem* LauncherBackend::getItem(const QString &appId) const
358+{
359+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
360+ if (!item) {
361+ QString df = findDesktopFile(appId);
362+ if (!df.isEmpty()) {
363+ item = parseDesktopFile(df);
364+ if (item) {
365+ m_itemCache[appId] = item;
366+ } else {
367+ qWarning() << "Unable to parse desktop file for" << appId << "path" << df;
368+ }
369+ } else {
370+ qDebug() << "Unable to find desktop file for:" << appId;
371+ }
372+ }
373+
374+ if (!item)
375+ qWarning() << "Unable to find item for: " << appId;
376+
278377 return item;
279378}
280379
@@ -285,7 +384,7 @@ void LauncherBackend::loadFromVariant(const QVariantMap &details)
285384 }
286385 QString appId = details.value("id").toString();
287386
288− LauncherBackendItem *item = m_itemCache.value(appId);
387+ LauncherBackendItem *item = m_itemCache.value(appId, nullptr);
289388 if (item) {
290389 delete item;
291390 }
@@ -295,6 +394,8 @@ void LauncherBackend::loadFromVariant(const QVariantMap &details)
295394 item->desktopFile = details.value("desktopFile").toString();
296395 item->displayName = details.value("name").toString();
297396 item->icon = details.value("icon").toString();
397+ item->count = details.value("count").toInt();
398+ item->countVisible = details.value("countVisible").toBool();
298399
299400 m_itemCache.insert(appId, item);
300401 m_storedApps.append(appId);
@@ -308,6 +409,8 @@ QVariantMap LauncherBackend::itemToVariant(const QString &appId) const
308409 details.insert("name", item->displayName);
309410 details.insert("icon", item->icon);
310411 details.insert("desktopFile", item->desktopFile);
412+ details.insert("count", item->count);
413+ details.insert("countVisible", item->countVisible);
311414 return details;
312415}
313416
@@ -318,3 +421,150 @@ bool LauncherBackend::isDefaultsItem(const QList<QVariantMap> &apps) const
318421 // list of one item with the 'defaults' field set to true.
319422 return (apps.size() == 1 && apps[0].value("defaults").toBool());
320423}
424+
425+bool LauncherBackend::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
426+{
427+ /* Check to make sure we're getting properties on our interface */
428+ if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
429+ return false;
430+ }
431+ if (message.interface() != "org.freedesktop.DBus.Properties") {
432+ return false;
433+ }
434+ if (message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
435+ return false;
436+ }
437+
438+ /* Break down the path to just the app id */
439+ QString pathtemp = message.path();
440+ if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
441+ return false;
442+ }
443+ pathtemp.remove("/com/canonical/Unity/Launcher/");
444+ if (pathtemp.indexOf('/') >= 0) {
445+ return false;
446+ }
447+
448+ /* Find ourselves an appid */
449+ QString appid = decodeAppId(pathtemp);
450+ QVariantList retval;
451+
452+ if (message.member() == "Get") {
453+ if (message.arguments()[1].toString() == "count") {
454+ retval.append(QVariant::fromValue(QDBusVariant(this->count(appid))));
455+ } else if (message.arguments()[1].toString() == "countVisible") {
456+ retval.append(QVariant::fromValue(QDBusVariant(this->countVisible(appid))));
457+ }
458+ } else if (message.member() == "Set") {
459+ if (message.arguments()[1].toString() == "count") {
460+ this->setCount(appid, message.arguments()[2].value<QDBusVariant>().variant().toInt());
461+ } else if (message.arguments()[1].toString() == "countVisible") {
462+ this->setCountVisible(appid, message.arguments()[2].value<QDBusVariant>().variant().toBool());
463+ }
464+ } else if (message.member() == "GetAll") {
465+ retval.append(this->itemToVariant(appid));
466+ } else {
467+ return false;
468+ }
469+
470+ QDBusMessage reply = message.createReply(retval);
471+ return connection.send(reply);
472+}
473+
474+QString LauncherBackend::introspect(const QString &path) const
475+{
476+ /* This case we should just list the nodes */
477+ if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
478+ QString nodes;
479+
480+ Q_FOREACH(const QString &appId, m_itemCache.keys()) {
481+ nodes.append("<node name=\"");
482+ nodes.append(encodeAppId(appId));
483+ nodes.append("\"/>\n");
484+ }
485+
486+ return nodes;
487+ }
488+
489+ /* Should not happen, but let's handle it */
490+ if (!path.startsWith("/com/canonical/Unity/Launcher")) {
491+ return "";
492+ }
493+
494+ /* Now we should be looking at a node */
495+ QString nodeiface =
496+ "<interface name=\"com.canonical.Unity.Launcher.Item\">"
497+ "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
498+ "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
499+ "</interface>";
500+ return nodeiface;
501+}
502+
503+QString LauncherBackend::decodeAppId(const QString& path)
504+{
505+ QByteArray bytes = path.toUtf8();
506+ QByteArray decoded;
507+
508+ for (int i = 0; i < bytes.size(); ++i) {
509+ char chr = bytes.at(i);
510+
511+ if (chr == '_') {
512+ QString number;
513+ number.append(bytes.at(i+1));
514+ number.append(bytes.at(i+2));
515+
516+ bool okay;
517+ char newchar = number.toUInt(&okay, 16);
518+ if (okay)
519+ decoded.append(newchar);
520+
521+ i += 2;
522+ } else {
523+ decoded.append(chr);
524+ }
525+ }
526+
527+ return QString::fromUtf8(decoded);
528+}
529+
530+QString LauncherBackend::encodeAppId(const QString& appId)
531+{
532+ QByteArray bytes = appId.toUtf8();
533+ QString encoded;
534+
535+ for (int i = 0; i < bytes.size(); ++i) {
536+ uchar chr = bytes.at(i);
537+
538+ if ((chr >= 'a' && chr <= 'z') ||
539+ (chr >= 'A' && chr <= 'Z') ||
540+ (chr >= '0' && chr <= '9'&& i != 0)) {
541+ encoded.append(chr);
542+ } else {
543+ QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
544+ encoded.append(hexval.toUpper());
545+ }
546+ }
547+
548+ return encoded;
549+}
550+
551+void LauncherBackend::emitPropChangedDbus(const QString& appId, const QString& property, QVariant &value) const
552+{
553+ QString path("/com/canonical/Unity/Launcher/");
554+ path.append(encodeAppId(appId));
555+
556+ QDBusMessage message = QDBusMessage::createSignal(path, "org.freedesktop.DBus.Properties", "PropertiesChanged");
557+
558+ QList<QVariant> arguments;
559+ QVariantHash changedprops;
560+ changedprops[property] = QVariant::fromValue(QDBusVariant(value));
561+ QVariantList deletedprops;
562+
563+ arguments.append(changedprops);
564+ arguments.append(deletedprops);
565+
566+ message.setArguments(arguments);
567+
568+ QDBusConnection con = QDBusConnection::sessionBus();
569+ con.send(message);
570+}