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 | |
38 | static const char* DBUS_SERVICE = "com.canonical.AppMenu.Registrar" ; |
39 | static const char* DBUS_OBJECT_PATH = "/com/canonical/AppMenu/Registrar" ; |
40 | |
41 | // Marshalling code for DBusMenuLayoutItem |
42 | QDBusArgument &(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 | |
55 | const QDBusArgument &(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 | |
74 | 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 | |
87 | MenuImporter::() |
88 | { |
89 | QDBusConnection::sessionBus().unregisterService(DBUS_SERVICE); |
90 | QDBusConnection::sessionBus().disconnect("" , "" , "com.canonical.dbusmenu" , "LayoutUpdated" , |
91 | this, SLOT(slotLayoutUpdated(uint,int))); |
92 | } |
93 | |
94 | bool MenuImporter::() |
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 | |
105 | WId MenuImporter::(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 | |
135 | void MenuImporter::(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 | |
161 | void MenuImporter::(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 | |
170 | QString MenuImporter::(WId id, QDBusObjectPath& path) |
171 | { |
172 | path = m_menuPaths.value(id); |
173 | return m_menuServices.value(id); |
174 | } |
175 | |
176 | void MenuImporter::(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 | |
186 | void MenuImporter::(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 | |
198 | void MenuImporter::() |
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 | |
209 | void MenuImporter::(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& , root.children) { |
222 | iface.asyncCall("AboutToShow" , dbusMenuItem.id); |
223 | } |
224 | } |
225 | |