Lomiri
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  LomiriDBusVirtualObject(QStringLiteral("/com/lomiri/Shell/Launcher"), QStringLiteral("com.lomiri.Shell.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/lomiri/Shell/Launcher/") || path == QLatin1String("/com/lomiri/Shell/Launcher")) {
39  QString nodes;
40 
41  // Add Refresh to introspect
42  nodes = QStringLiteral("<interface name=\"com.lomiri.Shell.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/lomiri/Shell/Launcher"))) {
57  return QLatin1String("");
58  }
59 
60  /* Now we should be looking at a node */
61  QString nodeiface =
62  QStringLiteral("<interface name=\"com.lomiri.Shell.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/lomiri/Shell/Launcher/"))) {
131  validpath = false;
132  }
133  pathtemp.remove(QStringLiteral("/com/lomiri/Shell/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.lomiri.Shell.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.lomiri.Shell.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  const QList<QVariant> messageArguments = message.arguments();
163  if (message.member() == QLatin1String("Get") && (messageArguments.count() != 2 || messageArguments[0].toString() != QLatin1String("com.lomiri.Shell.Launcher.Item"))) {
164  return false;
165  }
166 
167  if (message.member() == QLatin1String("Set") && (messageArguments.count() != 3 || messageArguments[0].toString() != QLatin1String("com.lomiri.Shell.Launcher.Item"))) {
168  return false;
169  }
170 
171  if (!validpath) {
172  return false;
173  }
174 
175  int index = m_launcherModel->findApplication(appid);
176  LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
177 
178  QVariantList retval;
179  if (message.member() == QLatin1String("Get")) {
180  QString cachedString = messageArguments[1].toString();
181  if (!item) {
182  return false;
183  }
184  if (cachedString == QLatin1String("count")) {
185  retval.append(QVariant::fromValue(QDBusVariant(item->count())));
186  } else if (cachedString == QLatin1String("countVisible")) {
187  retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
188  } else if (cachedString == QLatin1String("progress")) {
189  retval.append(QVariant::fromValue(QDBusVariant(item->progress())));
190  }
191  } else if (message.member() == QLatin1String("Set")) {
192  QString cachedString = messageArguments[1].toString();
193  if (cachedString == QLatin1String("count")) {
194  int newCount = messageArguments[2].value<QDBusVariant>().variant().toInt();
195  if (!item || newCount != item->count()) {
196  Q_EMIT countChanged(appid, newCount);
197  notifyPropertyChanged(QStringLiteral("com.lomiri.Shell.Launcher.Item"), encodeAppId(appid), QStringLiteral("count"), QVariant(newCount));
198  }
199  } else if (cachedString == QLatin1String("countVisible")) {
200  bool newVisible = messageArguments[2].value<QDBusVariant>().variant().toBool();
201  if (!item || newVisible != item->countVisible()) {
202  Q_EMIT countVisibleChanged(appid, newVisible);
203  notifyPropertyChanged(QStringLiteral("com.lomiri.Shell.Launcher.Item"), encodeAppId(appid), QStringLiteral("countVisible"), newVisible);
204  }
205  } else if (cachedString == QLatin1String("progress")) {
206  int newProgress = messageArguments[2].value<QDBusVariant>().variant().toInt();
207  if (!item || newProgress != item->progress()) {
208  Q_EMIT progressChanged(appid, newProgress);
209  notifyPropertyChanged(QStringLiteral("com.lomiri.Shell.Launcher.Item"), encodeAppId(appid), QStringLiteral("progress"), QVariant(newProgress));
210  }
211  }
212  } else if (message.member() == QLatin1String("GetAll")) {
213  if (item) {
214  QVariantMap all;
215  all.insert(QStringLiteral("count"), item->count());
216  all.insert(QStringLiteral("countVisible"), item->countVisible());
217  all.insert(QStringLiteral("progress"), item->progress());
218  retval.append(all);
219  }
220  } else {
221  return false;
222  }
223 
224  QDBusMessage reply = message.createReply(retval);
225  return connection.send(reply);
226 }