Unity 8
dbusinterface.cpp
1 /*
2  * Copyright 2014 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authors:
17  * Michael Zanetti <michael.zanetti@canonical.com>
18  */
19 
20 #include "dbusinterface.h"
21 #include "launchermodel.h"
22 #include "launcheritem.h"
23 
24 #include <QDBusArgument>
25 #include <QDBusConnection>
26 #include <QDBusMessage>
27 #include <QDebug>
28 
29 DBusInterface::DBusInterface(LauncherModel *parent):
30  UnityDBusVirtualObject(QStringLiteral("/com/canonical/Unity/Launcher"), QStringLiteral("com.canonical.Unity.Launcher"), true, parent),
31  m_launcherModel(parent)
32 {
33 }
34 
35 QString DBusInterface::introspect(const QString &path) const
36 {
37  /* This case we should just list the nodes */
38  if (path == QLatin1String("/com/canonical/Unity/Launcher/") || path == QLatin1String("/com/canonical/Unity/Launcher")) {
39  QString nodes;
40 
41  // Add Refresh to introspect
42  nodes = QStringLiteral("<interface name=\"com.canonical.Unity.Launcher\">"
43  "<method name=\"Refresh\"/>"
44  "</interface>");
45 
46  // Add dynamic properties for launcher emblems
47  for (int i = 0; i < m_launcherModel->rowCount(); i++) {
48  nodes.append("<node name=\"");
49  nodes.append(encodeAppId(m_launcherModel->get(i)->appId()));
50  nodes.append("\"/>\n");
51  }
52  return nodes;
53  }
54 
55  /* Should not happen, but let's handle it */
56  if (!path.startsWith(QLatin1String("/com/canonical/Unity/Launcher"))) {
57  return QLatin1String("");
58  }
59 
60  /* Now we should be looking at a node */
61  QString nodeiface =
62  QStringLiteral("<interface name=\"com.canonical.Unity.Launcher.Item\">"
63  "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
64  "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
65  "<property name=\"progress\" type=\"i\" access=\"readwrite\" />"
66  "<method name=\"Alert\" />"
67  "</interface>");
68  return nodeiface;
69 }
70 
71 
72 QString DBusInterface::decodeAppId(const QString& path)
73 {
74  QByteArray bytes = path.toUtf8();
75  QByteArray decoded;
76 
77  for (int i = 0; i < bytes.size(); ++i) {
78  char chr = bytes.at(i);
79 
80  if (chr == '_') {
81  QString number;
82  number.append(bytes.at(i+1));
83  number.append(bytes.at(i+2));
84 
85  bool okay;
86  char newchar = number.toUInt(&okay, 16);
87  if (okay)
88  decoded.append(newchar);
89 
90  i += 2;
91  } else {
92  decoded.append(chr);
93  }
94  }
95 
96  return QString::fromUtf8(decoded);
97 }
98 
99 QString DBusInterface::encodeAppId(const QString& appId)
100 {
101  QByteArray bytes = appId.toUtf8();
102  QString encoded;
103 
104  for (int i = 0; i < bytes.size(); ++i) {
105  uchar chr = bytes.at(i);
106 
107  if ((chr >= 'a' && chr <= 'z') ||
108  (chr >= 'A' && chr <= 'Z') ||
109  (chr >= '0' && chr <= '9'&& i != 0)) {
110  encoded.append(chr);
111  } else {
112  QString hexval = QStringLiteral("_%1").arg(chr, 2, 16, QChar('0'));
113  encoded.append(hexval.toUpper());
114  }
115  }
116 
117  return encoded;
118 }
119 
120 bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
121 {
122  /* Check to make sure we're getting properties on our interface */
123  if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
124  return false;
125  }
126 
127  /* Break down the path to just the app id */
128  bool validpath = true;
129  QString pathtemp = message.path();
130  if (!pathtemp.startsWith(QLatin1String("/com/canonical/Unity/Launcher/"))) {
131  validpath = false;
132  }
133  pathtemp.remove(QStringLiteral("/com/canonical/Unity/Launcher/"));
134  if (pathtemp.indexOf('/') >= 0) {
135  validpath = false;
136  }
137 
138  /* Find ourselves an appid */
139  QString appid = decodeAppId(pathtemp);
140 
141  // First handle methods of the Launcher interface
142  if (message.interface() == QLatin1String("com.canonical.Unity.Launcher")) {
143  if (message.member() == QLatin1String("Refresh")) {
144  QDBusMessage reply = message.createReply();
145  Q_EMIT refreshCalled();
146  return connection.send(reply);
147  }
148  } else if (message.interface() == QLatin1String("com.canonical.Unity.Launcher.Item")) {
149  // Handle methods of the Launcher-Item interface
150  if (message.member() == QLatin1String("Alert") && validpath) {
151  QDBusMessage reply = message.createReply();
152  Q_EMIT alertCalled(appid);
153  return connection.send(reply);
154  }
155  }
156 
157  // Now handle dynamic properties (for launcher emblems)
158  if (message.interface() != QLatin1String("org.freedesktop.DBus.Properties")) {
159  return false;
160  }
161 
162  if (message.member() == QLatin1String("Get") && (message.arguments().count() != 2 || message.arguments()[0].toString() != QLatin1String("com.canonical.Unity.Launcher.Item"))) {
163  return false;
164  }
165 
166  if (message.member() == QLatin1String("Set") && (message.arguments().count() != 3 || message.arguments()[0].toString() != QLatin1String("com.canonical.Unity.Launcher.Item"))) {
167  return false;
168  }
169 
170  if (!validpath) {
171  return false;
172  }
173 
174  int index = m_launcherModel->findApplication(appid);
175  LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
176 
177  QVariantList retval;
178  if (message.member() == QLatin1String("Get")) {
179  QString cachedString = message.arguments()[1].toString();
180  if (!item) {
181  return false;
182  }
183  if (cachedString == QLatin1String("count")) {
184  retval.append(QVariant::fromValue(QDBusVariant(item->count())));
185  } else if (cachedString == QLatin1String("countVisible")) {
186  retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
187  } else if (cachedString == QLatin1String("progress")) {
188  retval.append(QVariant::fromValue(QDBusVariant(item->progress())));
189  }
190  } else if (message.member() == QLatin1String("Set")) {
191  QString cachedString = message.arguments()[1].toString();
192  if (cachedString == QLatin1String("count")) {
193  int newCount = message.arguments()[2].value<QDBusVariant>().variant().toInt();
194  if (!item || newCount != item->count()) {
195  Q_EMIT countChanged(appid, newCount);
196  notifyPropertyChanged(QStringLiteral("com.canonical.Unity.Launcher.Item"), encodeAppId(appid), QStringLiteral("count"), QVariant(newCount));
197  }
198  } else if (cachedString == QLatin1String("countVisible")) {
199  bool newVisible = message.arguments()[2].value<QDBusVariant>().variant().toBool();
200  if (!item || newVisible != item->countVisible()) {
201  Q_EMIT countVisibleChanged(appid, newVisible);
202  notifyPropertyChanged(QStringLiteral("com.canonical.Unity.Launcher.Item"), encodeAppId(appid), QStringLiteral("countVisible"), newVisible);
203  }
204  } else if (cachedString == QLatin1String("progress")) {
205  int newProgress = message.arguments()[2].value<QDBusVariant>().variant().toInt();
206  if (!item || newProgress != item->progress()) {
207  Q_EMIT progressChanged(appid, newProgress);
208  notifyPropertyChanged(QStringLiteral("com.canonical.Unity.Launcher.Item"), encodeAppId(appid), QStringLiteral("progress"), QVariant(newProgress));
209  }
210  }
211  } else if (message.member() == QLatin1String("GetAll")) {
212  if (item) {
213  QVariantMap all;
214  all.insert(QStringLiteral("count"), item->count());
215  all.insert(QStringLiteral("countVisible"), item->countVisible());
216  all.insert(QStringLiteral("progress"), item->progress());
217  retval.append(all);
218  }
219  } else {
220  return false;
221  }
222 
223  QDBusMessage reply = message.createReply(retval);
224  return connection.send(reply);
225 }