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("/com/canonical/Unity/Launcher", "com.canonical.Unity.Launcher", true, parent),
31  m_launcherModel(parent)
32 {
33 }
34 
35 DBusInterface::~DBusInterface()
36 {
37 }
38 
39 QString DBusInterface::introspect(const QString &path) const
40 {
41  /* This case we should just list the nodes */
42  if (path == "/com/canonical/Unity/Launcher/" || path == "/com/canonical/Unity/Launcher") {
43  QString nodes;
44 
45  // Add Refresh to introspect
46  nodes = "<interface name=\"com.canonical.Unity.Launcher\">"
47  "<method name=\"Refresh\"/>"
48  "</interface>";
49 
50  // Add dynamic properties for launcher emblems
51  for (int i = 0; i < m_launcherModel->rowCount(); i++) {
52  nodes.append("<node name=\"");
53  nodes.append(encodeAppId(m_launcherModel->get(i)->appId()));
54  nodes.append("\"/>\n");
55  }
56  return nodes;
57  }
58 
59  /* Should not happen, but let's handle it */
60  if (!path.startsWith("/com/canonical/Unity/Launcher")) {
61  return "";
62  }
63 
64  /* Now we should be looking at a node */
65  QString nodeiface =
66  "<interface name=\"com.canonical.Unity.Launcher.Item\">"
67  "<property name=\"count\" type=\"i\" access=\"readwrite\" />"
68  "<property name=\"countVisible\" type=\"b\" access=\"readwrite\" />"
69  "</interface>";
70  return nodeiface;
71 }
72 
73 
74 QString DBusInterface::decodeAppId(const QString& path)
75 {
76  QByteArray bytes = path.toUtf8();
77  QByteArray decoded;
78 
79  for (int i = 0; i < bytes.size(); ++i) {
80  char chr = bytes.at(i);
81 
82  if (chr == '_') {
83  QString number;
84  number.append(bytes.at(i+1));
85  number.append(bytes.at(i+2));
86 
87  bool okay;
88  char newchar = number.toUInt(&okay, 16);
89  if (okay)
90  decoded.append(newchar);
91 
92  i += 2;
93  } else {
94  decoded.append(chr);
95  }
96  }
97 
98  return QString::fromUtf8(decoded);
99 }
100 
101 QString DBusInterface::encodeAppId(const QString& appId)
102 {
103  QByteArray bytes = appId.toUtf8();
104  QString encoded;
105 
106  for (int i = 0; i < bytes.size(); ++i) {
107  uchar chr = bytes.at(i);
108 
109  if ((chr >= 'a' && chr <= 'z') ||
110  (chr >= 'A' && chr <= 'Z') ||
111  (chr >= '0' && chr <= '9'&& i != 0)) {
112  encoded.append(chr);
113  } else {
114  QString hexval = QString("_%1").arg(chr, 2, 16, QChar('0'));
115  encoded.append(hexval.toUpper());
116  }
117  }
118 
119  return encoded;
120 }
121 
122 bool DBusInterface::handleMessage(const QDBusMessage& message, const QDBusConnection& connection)
123 {
124  /* Check to make sure we're getting properties on our interface */
125  if (message.type() != QDBusMessage::MessageType::MethodCallMessage) {
126  return false;
127  }
128 
129  // First handle methods of the Launcher interface
130  if (message.interface() == "com.canonical.Unity.Launcher") {
131  if (message.member() == "Refresh") {
132  QDBusMessage reply = message.createReply();
133  Q_EMIT refreshCalled();
134  return connection.send(reply);
135  }
136  }
137 
138  // Now handle dynamic properties (for launcher emblems)
139  if (message.interface() != "org.freedesktop.DBus.Properties") {
140  return false;
141  }
142 
143  if (message.member() != "GetAll" && message.arguments()[0].toString() != "com.canonical.Unity.Launcher.Item") {
144  return false;
145  }
146 
147  /* Break down the path to just the app id */
148  QString pathtemp = message.path();
149  if (!pathtemp.startsWith("/com/canonical/Unity/Launcher/")) {
150  return false;
151  }
152  pathtemp.remove("/com/canonical/Unity/Launcher/");
153  if (pathtemp.indexOf('/') >= 0) {
154  return false;
155  }
156 
157  /* Find ourselves an appid */
158  QString appid = decodeAppId(pathtemp);
159  int index = m_launcherModel->findApplication(appid);
160  LauncherItem *item = static_cast<LauncherItem*>(m_launcherModel->get(index));
161 
162  QVariantList retval;
163  if (message.member() == "Get") {
164  if (!item) {
165  return false;
166  }
167  if (message.arguments()[1].toString() == "count") {
168  retval.append(QVariant::fromValue(QDBusVariant(item->count())));
169  } else if (message.arguments()[1].toString() == "countVisible") {
170  retval.append(QVariant::fromValue(QDBusVariant(item->countVisible())));
171  }
172  } else if (message.member() == "Set") {
173  if (message.arguments()[1].toString() == "count") {
174  int newCount = message.arguments()[2].value<QDBusVariant>().variant().toInt();
175  if (!item || newCount != item->count()) {
176  Q_EMIT countChanged(appid, newCount);
177  notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "count", QVariant(newCount));
178  }
179  } else if (message.arguments()[1].toString() == "countVisible") {
180  bool newVisible = message.arguments()[2].value<QDBusVariant>().variant().toBool();
181  if (!item || newVisible != item->countVisible()) {
182  Q_EMIT countVisibleChanged(appid, newVisible);
183  notifyPropertyChanged("com.canonical.Unity.Launcher.Item", encodeAppId(appid), "countVisible", newVisible);
184  }
185  }
186  } else if (message.member() == "GetAll") {
187  if (item) {
188  QVariantMap all;
189  all.insert("count", item->count());
190  all.insert("countVisible", item->countVisible());
191  retval.append(all);
192  }
193  } else {
194  return false;
195  }
196 
197  QDBusMessage reply = message.createReply(retval);
198  return connection.send(reply);
199 }