1/*
2 This file is part of the KDE project.
3
4 Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
5 Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
6
7 Permission is hereby granted, free of charge, to any person obtaining a
8 copy of this software and associated documentation files (the "Software"),
9 to deal in the Software without restriction, including without limitation
10 the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 and/or sell copies of the Software, and to permit persons to whom the
12 Software is furnished to do so, subject to the following conditions:
13
14 The above copyright notice and this permission notice shall be included in
15 all copies or substantial portions of the Software.
16
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 DEALINGS IN THE SOFTWARE.
24*/
25
26#include "menuimporter.h"
27#include "menuimporteradaptor.h"
28
29#include <QApplication>
30#include <QDBusMessage>
31#include <QDBusObjectPath>
32#include <QDBusServiceWatcher>
33
34#include <KDebug>
35#include <KWindowSystem>
36#include <KWindowInfo>
37
38static const char* DBUS_SERVICE = "com.canonical.AppMenu.Registrar";
39static const char* DBUS_OBJECT_PATH = "/com/canonical/AppMenu/Registrar";
40
41// Marshalling code for DBusMenuLayoutItem
42QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj)
43{
44 argument.beginStructure();
45 argument << obj.id << obj.properties;
46 argument.beginArray(qMetaTypeId<QDBusVariant>());
47 Q_FOREACH(const DBusMenuLayoutItem& child, obj.children) {
48 argument << QDBusVariant(QVariant::fromValue<DBusMenuLayoutItem>(child));
49 }
50 argument.endArray();
51 argument.endStructure();
52 return argument;
53}
54
55const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj)
56{
57 argument.beginStructure();
58 argument >> obj.id >> obj.properties;
59 argument.beginArray();
60 while (!argument.atEnd()) {
61 QDBusVariant dbusVariant;
62 argument >> dbusVariant;
63 QDBusArgument childArgument = dbusVariant.variant().value<QDBusArgument>();
64
65 DBusMenuLayoutItem child;
66 childArgument >> child;
67 obj.children.append(child);
68 }
69 argument.endArray();
70 argument.endStructure();
71 return argument;
72}
73
74MenuImporter::MenuImporter(QObject* parent)
75: QObject(parent)
76, m_serviceWatcher(new QDBusServiceWatcher(this))
77{
78 qDBusRegisterMetaType<DBusMenuLayoutItem>();
79 m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
80 m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
81 connect(m_serviceWatcher, SIGNAL(serviceUnregistered(const QString&)), SLOT(slotServiceUnregistered(const QString&)));
82
83 QDBusConnection::sessionBus().connect("", "", "com.canonical.dbusmenu", "LayoutUpdated",
84 this, SLOT(slotLayoutUpdated(uint,int)));
85}
86
87MenuImporter::~MenuImporter()
88{
89 QDBusConnection::sessionBus().unregisterService(DBUS_SERVICE);
90 QDBusConnection::sessionBus().disconnect("", "", "com.canonical.dbusmenu", "LayoutUpdated",
91 this, SLOT(slotLayoutUpdated(uint,int)));
92}
93
94bool MenuImporter::connectToBus()
95{
96 if (!QDBusConnection::sessionBus().registerService(DBUS_SERVICE)) {
97 return false;
98 }
99 new MenuImporterAdaptor(this);
100 QDBusConnection::sessionBus().registerObject(DBUS_OBJECT_PATH, this);
101
102 return true;
103}
104
105WId MenuImporter::recursiveMenuId(WId id)
106{
107 KWindowInfo info = KWindowSystem::windowInfo(id, 0, NET::WM2WindowClass);
108 QString classClass = info.windowClassClass();
109 WId classId = 0;
110
111 // First look at transient windows
112 WId tid = KWindowSystem::transientFor(id);
113 while (tid) {
114 if (serviceExist(tid)) {
115 classId = tid;
116 break;
117 }
118 tid = KWindowSystem::transientFor(tid);
119 }
120
121 if (classId == 0) {
122 // Look at friends windows
123 QHashIterator<WId, QString> i(m_windowClasses);
124 while (i.hasNext()) {
125 i.next();
126 if (i.value() == classClass) {
127 classId = i.key();
128 }
129 }
130 }
131
132 return classId;
133}
134
135void MenuImporter::RegisterWindow(WId id, const QDBusObjectPath& path)
136{
137 KWindowInfo info = KWindowSystem::windowInfo(id, NET::WMWindowType);
138 unsigned long mask = NET::AllTypesMask;
139
140 // Menu can try to register, right click in gimp for exemple
141 if (info.windowType(mask) & (NET::Menu|NET::DropdownMenu||NET::PopupMenu)) {
142 return;
143 }
144
145 if (path.path().isEmpty()) //prevent bad dbusmenu usage
146 return;
147
148 QString service = message().service();
149
150 info = KWindowSystem::windowInfo(id, 0, NET::WM2WindowClass);
151 QString classClass = info.windowClassClass();
152 m_windowClasses.insert(id, classClass);
153 m_menuServices.insert(id, service);
154 m_menuPaths.insert(id, path);
155 if (! m_serviceWatcher->watchedServices().contains(service)) {
156 m_serviceWatcher->addWatchedService(service);
157 }
158 emit WindowRegistered(id, service, path);
159}
160
161void MenuImporter::UnregisterWindow(WId id)
162{
163 m_menuServices.remove(id);
164 m_menuPaths.remove(id);
165 m_windowClasses.remove(id);
166
167 emit WindowUnregistered(id);
168}
169
170QString MenuImporter::GetMenuForWindow(WId id, QDBusObjectPath& path)
171{
172 path = m_menuPaths.value(id);
173 return m_menuServices.value(id);
174}
175
176void MenuImporter::slotServiceUnregistered(const QString& service)
177{
178 WId id = m_menuServices.key(service);
179 m_menuServices.remove(id);
180 m_menuPaths.remove(id);
181 m_windowClasses.remove(id);
182 emit WindowUnregistered(id);
183 m_serviceWatcher->removeWatchedService(service);
184}
185
186void MenuImporter::slotLayoutUpdated(uint /*revision*/, int parentId)
187{
188 // Fake unity-panel-service weird behavior of calling aboutToShow on
189 // startup. This is necessary for Firefox menubar to work correctly at
190 // startup.
191 // See: https://bugs.launchpad.net/plasma-idget-menubar/+bug/878165
192
193 if (parentId == 0) { //root menu
194 fakeUnityAboutToShow();
195 }
196}
197
198void MenuImporter::fakeUnityAboutToShow()
199{
200 QDBusInterface iface(message().service(), message().path(), "com.canonical.dbusmenu");
201 QDBusPendingCall call = iface.asyncCall("GetLayout", 0, 1, QStringList());
202 QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(call, this);
203 watcher->setProperty("service", message().service());
204 watcher->setProperty("path", message().path());
205 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
206 SLOT(finishFakeUnityAboutToShow(QDBusPendingCallWatcher*)));
207}
208
209void MenuImporter::finishFakeUnityAboutToShow(QDBusPendingCallWatcher* watcher)
210{
211 QDBusPendingReply<uint, DBusMenuLayoutItem> reply = *watcher;
212 if (reply.isError()) {
213 kWarning() << "Call to GetLayout failed:" << reply.error().message();
214 return;
215 }
216 QString service = watcher->property("service").toString();
217 QString path = watcher->property("path").toString();
218 DBusMenuLayoutItem root = reply.argumentAt<1>();
219
220 QDBusInterface iface(service, path, "com.canonical.dbusmenu");
221 Q_FOREACH(const DBusMenuLayoutItem& dbusMenuItem, root.children) {
222 iface.asyncCall("AboutToShow", dbusMenuItem.id);
223 }
224}
225