Link Here
|
|
|
1 |
/* |
2 |
* Copyright (C) 2018 Kai Uwe Broulik <kde@privat.broulik.de> |
3 |
* |
4 |
* This library is free software; you can redistribute it and/or |
5 |
* modify it under the terms of the GNU Lesser General Public |
6 |
* License as published by the Free Software Foundation; either |
7 |
* version 2.1 of the License, or (at your option) any later version. |
8 |
* |
9 |
* This library is distributed in the hope that it will be useful, |
10 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 |
* Lesser General Public License for more details. |
13 |
* |
14 |
* You should have received a copy of the GNU Lesser General Public |
15 |
* License along with this library; if not, write to the Free Software |
16 |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
17 |
* |
18 |
*/ |
19 |
|
20 |
#include "window.h" |
21 |
|
22 |
#include "debug.h" |
23 |
|
24 |
#include <QDBusConnection> |
25 |
#include <QDBusMessage> |
26 |
#include <QDBusPendingCallWatcher> |
27 |
#include <QDBusPendingReply> |
28 |
#include <QDebug> |
29 |
#include <QList> |
30 |
#include <QMutableListIterator> |
31 |
#include <QVariantList> |
32 |
|
33 |
#include <algorithm> |
34 |
|
35 |
#include "actions.h" |
36 |
#include "dbusmenuadaptor.h" |
37 |
#include "icons.h" |
38 |
#include "menu.h" |
39 |
#include "utils.h" |
40 |
|
41 |
#include "../libdbusmenuqt/dbusmenushortcut_p.h" |
42 |
|
43 |
static const QString s_orgGtkActions = QStringLiteral("org.gtk.Actions"); |
44 |
static const QString s_orgGtkMenus = QStringLiteral("org.gtk.Menus"); |
45 |
|
46 |
static const QString s_applicationActionsPrefix = QStringLiteral("app."); |
47 |
static const QString s_unityActionsPrefix = QStringLiteral("unity."); |
48 |
static const QString s_windowActionsPrefix = QStringLiteral("win."); |
49 |
|
50 |
Window::Window(const QString &serviceName) |
51 |
: QObject() |
52 |
, m_serviceName(serviceName) |
53 |
{ |
54 |
qCDebug(DBUSMENUPROXY) << "Created menu on" << serviceName; |
55 |
|
56 |
Q_ASSERT(!serviceName.isEmpty()); |
57 |
|
58 |
GDBusMenuTypes_register(); |
59 |
DBusMenuTypes_register(); |
60 |
} |
61 |
|
62 |
Window::~Window() = default; |
63 |
|
64 |
void Window::init() |
65 |
{ |
66 |
qCDebug(DBUSMENUPROXY) << "Inited window with menu for" << m_winId << "on" << m_serviceName << "at app" << m_applicationObjectPath << "win" << m_windowObjectPath << "unity" << m_unityObjectPath; |
67 |
|
68 |
if (!m_applicationMenuObjectPath.isEmpty()) { |
69 |
m_applicationMenu = new Menu(m_serviceName, m_applicationMenuObjectPath, this); |
70 |
connect(m_applicationMenu, &Menu::menuAppeared, this, &Window::updateWindowProperties); |
71 |
connect(m_applicationMenu, &Menu::menuDisappeared, this, &Window::updateWindowProperties); |
72 |
connect(m_applicationMenu, &Menu::subscribed, this, &Window::onMenuSubscribed); |
73 |
// basically so it replies on DBus no matter what |
74 |
connect(m_applicationMenu, &Menu::failedToSubscribe, this, &Window::onMenuSubscribed); |
75 |
connect(m_applicationMenu, &Menu::itemsChanged, this, &Window::menuItemsChanged); |
76 |
connect(m_applicationMenu, &Menu::menusChanged, this, &Window::menuChanged); |
77 |
} |
78 |
|
79 |
if (!m_menuBarObjectPath.isEmpty()) { |
80 |
m_menuBar = new Menu(m_serviceName, m_menuBarObjectPath, this); |
81 |
connect(m_menuBar, &Menu::menuAppeared, this, &Window::updateWindowProperties); |
82 |
connect(m_menuBar, &Menu::menuDisappeared, this, &Window::updateWindowProperties); |
83 |
connect(m_menuBar, &Menu::subscribed, this, &Window::onMenuSubscribed); |
84 |
connect(m_menuBar, &Menu::failedToSubscribe, this, &Window::onMenuSubscribed); |
85 |
connect(m_menuBar, &Menu::itemsChanged, this, &Window::menuItemsChanged); |
86 |
connect(m_menuBar, &Menu::menusChanged, this, &Window::menuChanged); |
87 |
} |
88 |
|
89 |
if (!m_applicationObjectPath.isEmpty()) { |
90 |
m_applicationActions = new Actions(m_serviceName, m_applicationObjectPath, this); |
91 |
connect(m_applicationActions, &Actions::actionsChanged, this, [this](const QStringList &dirtyActions) { |
92 |
onActionsChanged(dirtyActions, s_applicationActionsPrefix); |
93 |
}); |
94 |
connect(m_applicationActions, &Actions::loaded, this, [this] { |
95 |
if (m_menuInited) { |
96 |
onActionsChanged(m_applicationActions->getAll().keys(), s_applicationActionsPrefix); |
97 |
} else { |
98 |
initMenu(); |
99 |
} |
100 |
}); |
101 |
m_applicationActions->load(); |
102 |
} |
103 |
|
104 |
if (!m_unityObjectPath.isEmpty()) { |
105 |
m_unityActions = new Actions(m_serviceName, m_unityObjectPath, this); |
106 |
connect(m_unityActions, &Actions::actionsChanged, this, [this](const QStringList &dirtyActions) { |
107 |
onActionsChanged(dirtyActions, s_unityActionsPrefix); |
108 |
}); |
109 |
connect(m_unityActions, &Actions::loaded, this, [this] { |
110 |
if (m_menuInited) { |
111 |
onActionsChanged(m_unityActions->getAll().keys(), s_unityActionsPrefix); |
112 |
} else { |
113 |
initMenu(); |
114 |
} |
115 |
}); |
116 |
m_unityActions->load(); |
117 |
} |
118 |
|
119 |
if (!m_windowObjectPath.isEmpty()) { |
120 |
m_windowActions = new Actions(m_serviceName, m_windowObjectPath, this); |
121 |
connect(m_windowActions, &Actions::actionsChanged, this, [this](const QStringList &dirtyActions) { |
122 |
onActionsChanged(dirtyActions, s_windowActionsPrefix); |
123 |
}); |
124 |
connect(m_windowActions, &Actions::loaded, this, [this] { |
125 |
if (m_menuInited) { |
126 |
onActionsChanged(m_windowActions->getAll().keys(), s_windowActionsPrefix); |
127 |
} else { |
128 |
initMenu(); |
129 |
} |
130 |
}); |
131 |
m_windowActions->load(); |
132 |
} |
133 |
} |
134 |
|
135 |
WId Window::winId() const |
136 |
{ |
137 |
return m_winId; |
138 |
} |
139 |
|
140 |
void Window::setWinId(WId winId) |
141 |
{ |
142 |
m_winId = winId; |
143 |
} |
144 |
|
145 |
QString Window::serviceName() const |
146 |
{ |
147 |
return m_serviceName; |
148 |
} |
149 |
|
150 |
QString Window::applicationObjectPath() const |
151 |
{ |
152 |
return m_applicationObjectPath; |
153 |
} |
154 |
|
155 |
void Window::setApplicationObjectPath(const QString &applicationObjectPath) |
156 |
{ |
157 |
m_applicationObjectPath = applicationObjectPath; |
158 |
} |
159 |
|
160 |
QString Window::unityObjectPath() const |
161 |
{ |
162 |
return m_unityObjectPath; |
163 |
} |
164 |
|
165 |
void Window::setUnityObjectPath(const QString &unityObjectPath) |
166 |
{ |
167 |
m_unityObjectPath = unityObjectPath; |
168 |
} |
169 |
|
170 |
QString Window::applicationMenuObjectPath() const |
171 |
{ |
172 |
return m_applicationMenuObjectPath; |
173 |
} |
174 |
|
175 |
void Window::setApplicationMenuObjectPath(const QString &applicationMenuObjectPath) |
176 |
{ |
177 |
m_applicationMenuObjectPath = applicationMenuObjectPath; |
178 |
} |
179 |
|
180 |
QString Window::menuBarObjectPath() const |
181 |
{ |
182 |
return m_menuBarObjectPath; |
183 |
} |
184 |
|
185 |
void Window::setMenuBarObjectPath(const QString &menuBarObjectPath) |
186 |
{ |
187 |
m_menuBarObjectPath = menuBarObjectPath; |
188 |
} |
189 |
|
190 |
QString Window::windowObjectPath() const |
191 |
{ |
192 |
return m_windowObjectPath; |
193 |
} |
194 |
|
195 |
void Window::setWindowObjectPath(const QString &windowObjectPath) |
196 |
{ |
197 |
m_windowObjectPath = windowObjectPath; |
198 |
} |
199 |
|
200 |
QString Window::currentMenuObjectPath() const |
201 |
{ |
202 |
return m_currentMenuObjectPath; |
203 |
} |
204 |
|
205 |
QString Window::proxyObjectPath() const |
206 |
{ |
207 |
return m_proxyObjectPath; |
208 |
} |
209 |
|
210 |
void Window::initMenu() |
211 |
{ |
212 |
if (m_menuInited) { |
213 |
return; |
214 |
} |
215 |
|
216 |
if (!registerDBusObject()) { |
217 |
return; |
218 |
} |
219 |
|
220 |
// appmenu-gtk-module always announces a menu bar on every GTK window even if there is none |
221 |
// so we subscribe to the menu bar as soon as it shows up so we can figure out |
222 |
// if we have a menu bar, an app menu, or just nothing |
223 |
if (m_applicationMenu) { |
224 |
m_applicationMenu->start(0); |
225 |
} |
226 |
|
227 |
if (m_menuBar) { |
228 |
m_menuBar->start(0); |
229 |
} |
230 |
|
231 |
m_menuInited = true; |
232 |
} |
233 |
|
234 |
void Window::menuItemsChanged(const QVector<uint> &itemIds) |
235 |
{ |
236 |
if (qobject_cast<Menu*>(sender()) != m_currentMenu) { |
237 |
return; |
238 |
} |
239 |
|
240 |
DBusMenuItemList items; |
241 |
|
242 |
for (uint id : itemIds) { |
243 |
const auto newItem = m_currentMenu->getItem(id); |
244 |
|
245 |
DBusMenuItem dBusItem{ |
246 |
// 0 is menu, items start at 1 |
247 |
static_cast<int>(id), |
248 |
gMenuToDBusMenuProperties(newItem) |
249 |
}; |
250 |
items.append(dBusItem); |
251 |
} |
252 |
|
253 |
emit ItemsPropertiesUpdated(items, {}); |
254 |
} |
255 |
|
256 |
void Window::menuChanged(const QVector<uint> &menuIds) |
257 |
{ |
258 |
if (qobject_cast<Menu*>(sender()) != m_currentMenu) { |
259 |
return; |
260 |
} |
261 |
|
262 |
for (uint menu : menuIds) { |
263 |
emit LayoutUpdated(3 /*revision*/, menu); |
264 |
} |
265 |
} |
266 |
|
267 |
void Window::onMenuSubscribed(uint id) |
268 |
{ |
269 |
// When it was a delayed GetLayout request, send the reply now |
270 |
const auto pendingReplies = m_pendingGetLayouts.values(id); |
271 |
if (!pendingReplies.isEmpty()) { |
272 |
for (const auto &pendingReply : pendingReplies) { |
273 |
if (pendingReply.type() != QDBusMessage::InvalidMessage) { |
274 |
auto reply = pendingReply.createReply(); |
275 |
|
276 |
DBusMenuLayoutItem item; |
277 |
uint revision = GetLayout(Utils::treeStructureToInt(id, 0, 0), 0, {}, item); |
278 |
|
279 |
reply << revision << QVariant::fromValue(item); |
280 |
|
281 |
QDBusConnection::sessionBus().send(reply); |
282 |
} |
283 |
} |
284 |
m_pendingGetLayouts.remove(id); |
285 |
} else { |
286 |
emit LayoutUpdated(2 /*revision*/, id); |
287 |
} |
288 |
} |
289 |
|
290 |
bool Window::getAction(const QString &name, GMenuAction &action) const |
291 |
{ |
292 |
QString lookupName; |
293 |
Actions *actions = getActionsForAction(name, lookupName); |
294 |
|
295 |
if (!actions) { |
296 |
return false; |
297 |
} |
298 |
|
299 |
return actions->get(lookupName, action); |
300 |
} |
301 |
|
302 |
void Window::triggerAction(const QString &name, uint timestamp) |
303 |
{ |
304 |
QString lookupName; |
305 |
Actions *actions = getActionsForAction(name, lookupName); |
306 |
|
307 |
if (!actions) { |
308 |
return; |
309 |
} |
310 |
|
311 |
actions->trigger(lookupName, timestamp); |
312 |
} |
313 |
|
314 |
Actions *Window::getActionsForAction(const QString &name, QString &lookupName) const |
315 |
{ |
316 |
if (name.startsWith(QLatin1String("app."))) { |
317 |
lookupName = name.mid(4); |
318 |
return m_applicationActions; |
319 |
} else if (name.startsWith(QLatin1String("unity."))) { |
320 |
lookupName = name.mid(6); |
321 |
return m_unityActions; |
322 |
} else if (name.startsWith(QLatin1String("win."))) { |
323 |
lookupName = name.mid(4); |
324 |
return m_windowActions; |
325 |
} |
326 |
|
327 |
return nullptr; |
328 |
} |
329 |
|
330 |
void Window::onActionsChanged(const QStringList &dirty, const QString &prefix) |
331 |
{ |
332 |
if (m_applicationMenu) { |
333 |
m_applicationMenu->actionsChanged(dirty, prefix); |
334 |
} |
335 |
if (m_menuBar) { |
336 |
m_menuBar->actionsChanged(dirty, prefix); |
337 |
} |
338 |
} |
339 |
|
340 |
bool Window::registerDBusObject() |
341 |
{ |
342 |
Q_ASSERT(m_proxyObjectPath.isEmpty()); |
343 |
|
344 |
static int menus = 0; |
345 |
++menus; |
346 |
|
347 |
new DbusmenuAdaptor(this); |
348 |
|
349 |
const QString objectPath = QStringLiteral("/MenuBar/%1").arg(QString::number(menus)); |
350 |
qCDebug(DBUSMENUPROXY) << "Registering DBus object path" << objectPath; |
351 |
|
352 |
if (!QDBusConnection::sessionBus().registerObject(objectPath, this)) { |
353 |
qCWarning(DBUSMENUPROXY) << "Failed to register object"; |
354 |
return false; |
355 |
} |
356 |
|
357 |
m_proxyObjectPath = objectPath; |
358 |
|
359 |
return true; |
360 |
} |
361 |
|
362 |
void Window::updateWindowProperties() |
363 |
{ |
364 |
const bool hasMenu = ((m_applicationMenu && m_applicationMenu->hasMenu()) |
365 |
|| (m_menuBar && m_menuBar->hasMenu())); |
366 |
|
367 |
if (!hasMenu) { |
368 |
emit requestRemoveWindowProperties(); |
369 |
return; |
370 |
} |
371 |
|
372 |
Menu *oldMenu = m_currentMenu; |
373 |
Menu *newMenu = qobject_cast<Menu*>(sender()); |
374 |
// set current menu as needed |
375 |
if (!m_currentMenu) { |
376 |
m_currentMenu = newMenu; |
377 |
// Menu Bar takes precedence over application menu |
378 |
} else if (m_currentMenu == m_applicationMenu && newMenu == m_menuBar) { |
379 |
qCDebug(DBUSMENUPROXY) << "Switching from application menu to menu bar"; |
380 |
m_currentMenu = newMenu; |
381 |
// TODO update layout |
382 |
} |
383 |
|
384 |
if (m_currentMenu != oldMenu) { |
385 |
// update entire menu now |
386 |
emit LayoutUpdated(4 /*revision*/, 0); |
387 |
} |
388 |
|
389 |
emit requestWriteWindowProperties(); |
390 |
} |
391 |
|
392 |
// DBus |
393 |
bool Window::AboutToShow(int id) |
394 |
{ |
395 |
// We always request the first time GetLayout is called and keep up-to-date internally |
396 |
// No need to have us prepare anything here |
397 |
Q_UNUSED(id); |
398 |
return false; |
399 |
} |
400 |
|
401 |
void Window::Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp) |
402 |
{ |
403 |
Q_UNUSED(data); |
404 |
|
405 |
if (!m_currentMenu) { |
406 |
return; |
407 |
} |
408 |
|
409 |
// GMenu dbus doesn't have any "opened" or "closed" signals, we'll only handle "clicked" |
410 |
|
411 |
if (eventId == QLatin1String("clicked")) { |
412 |
const QString action = m_currentMenu->getItem(id).value(QStringLiteral("action")).toString(); |
413 |
if (!action.isEmpty()) { |
414 |
triggerAction(action, timestamp); |
415 |
} |
416 |
} |
417 |
|
418 |
} |
419 |
|
420 |
DBusMenuItemList Window::GetGroupProperties(const QList<int> &ids, const QStringList &propertyNames) |
421 |
{ |
422 |
Q_UNUSED(ids); |
423 |
Q_UNUSED(propertyNames); |
424 |
return DBusMenuItemList(); |
425 |
} |
426 |
|
427 |
uint Window::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &dbusItem) |
428 |
{ |
429 |
Q_UNUSED(recursionDepth); // TODO |
430 |
Q_UNUSED(propertyNames); |
431 |
|
432 |
int subscription; |
433 |
int sectionId; |
434 |
int index; |
435 |
|
436 |
Utils::intToTreeStructure(parentId, subscription, sectionId, index); |
437 |
|
438 |
if (!m_currentMenu) { |
439 |
return 1; |
440 |
} |
441 |
|
442 |
if (!m_currentMenu->hasSubscription(subscription)) { |
443 |
// let's serve multiple similar requests in one go once we've processed them |
444 |
m_pendingGetLayouts.insertMulti(subscription, message()); |
445 |
setDelayedReply(true); |
446 |
|
447 |
m_currentMenu->start(subscription); |
448 |
return 1; |
449 |
} |
450 |
|
451 |
bool ok; |
452 |
const GMenuItem section = m_currentMenu->getSection(subscription, sectionId, &ok); |
453 |
|
454 |
if (!ok) { |
455 |
qCDebug(DBUSMENUPROXY) << "There is no section on" << subscription << "at" << sectionId << "with" << parentId; |
456 |
return 1; |
457 |
} |
458 |
|
459 |
// If a particular entry is requested, see what it is and resolve as neccessary |
460 |
// for example the "File" entry on root is 0,0,1 but is a menu reference to e.g. 1,0,0 |
461 |
// so resolve that and return the correct menu |
462 |
if (index > 0) { |
463 |
// non-zero index indicates item within a menu but the index in the list still starts at zero |
464 |
if (section.items.count() < index) { |
465 |
qCDebug(DBUSMENUPROXY) << "Requested index" << index << "on" << subscription << "at" << sectionId << "with" << parentId << "is out of bounds"; |
466 |
return 0; |
467 |
} |
468 |
|
469 |
const auto &requestedItem = section.items.at(index - 1); |
470 |
|
471 |
auto it = requestedItem.constFind(QStringLiteral(":submenu")); |
472 |
if (it != requestedItem.constEnd()) { |
473 |
const GMenuSection gmenuSection = qdbus_cast<GMenuSection>(it->value<QDBusArgument>()); |
474 |
return GetLayout(Utils::treeStructureToInt(gmenuSection.subscription, gmenuSection.menu, 0), recursionDepth, propertyNames, dbusItem); |
475 |
} else { |
476 |
// TODO |
477 |
return 0; |
478 |
} |
479 |
} |
480 |
|
481 |
dbusItem.id = parentId; // TODO |
482 |
dbusItem.properties = { |
483 |
{QStringLiteral("children-display"), QStringLiteral("submenu")} |
484 |
}; |
485 |
|
486 |
int count = 0; |
487 |
|
488 |
const auto itemsToBeAdded = section.items; |
489 |
for (const auto &item : itemsToBeAdded) { |
490 |
|
491 |
DBusMenuLayoutItem child{ |
492 |
Utils::treeStructureToInt(section.id, sectionId, ++count), |
493 |
gMenuToDBusMenuProperties(item), |
494 |
{} // children |
495 |
}; |
496 |
dbusItem.children.append(child); |
497 |
|
498 |
// Now resolve section aliases |
499 |
auto it = item.constFind(QStringLiteral(":section")); |
500 |
if (it != item.constEnd()) { |
501 |
|
502 |
// references another place, add it instead |
503 |
GMenuSection gmenuSection = qdbus_cast<GMenuSection>(it->value<QDBusArgument>()); |
504 |
|
505 |
// remember where the item came from and give it an appropriate ID |
506 |
// so updates signalled by the app will map to the right place |
507 |
int originalSubscription = gmenuSection.subscription; |
508 |
int originalMenu = gmenuSection.menu; |
509 |
|
510 |
// TODO start subscription if we don't have it |
511 |
auto items = m_currentMenu->getSection(gmenuSection.subscription, gmenuSection.menu).items; |
512 |
|
513 |
// Check whether it's an alias to an alias |
514 |
// FIXME make generic/recursive |
515 |
if (items.count() == 1) { |
516 |
const auto &aliasedItem = items.constFirst(); |
517 |
auto findIt = aliasedItem.constFind(QStringLiteral(":section")); |
518 |
if (findIt != aliasedItem.constEnd()) { |
519 |
GMenuSection gmenuSection2 = qdbus_cast<GMenuSection>(findIt->value<QDBusArgument>()); |
520 |
items = m_currentMenu->getSection(gmenuSection2.subscription, gmenuSection2.menu).items; |
521 |
|
522 |
originalSubscription = gmenuSection2.subscription; |
523 |
originalMenu = gmenuSection2.menu; |
524 |
} |
525 |
} |
526 |
|
527 |
int aliasedCount = 0; |
528 |
for (const auto &aliasedItem : qAsConst(items)) { |
529 |
DBusMenuLayoutItem aliasedChild{ |
530 |
Utils::treeStructureToInt(originalSubscription, originalMenu, ++aliasedCount), |
531 |
gMenuToDBusMenuProperties(aliasedItem), |
532 |
{} // children |
533 |
}; |
534 |
dbusItem.children.append(aliasedChild); |
535 |
} |
536 |
} |
537 |
} |
538 |
|
539 |
// revision, unused in libdbusmenuqt |
540 |
return 1; |
541 |
} |
542 |
|
543 |
QDBusVariant Window::GetProperty(int id, const QString &property) |
544 |
{ |
545 |
Q_UNUSED(id); |
546 |
Q_UNUSED(property); |
547 |
QDBusVariant value; |
548 |
return value; |
549 |
} |
550 |
|
551 |
QString Window::status() const |
552 |
{ |
553 |
return QStringLiteral("normal"); |
554 |
} |
555 |
|
556 |
uint Window::version() const |
557 |
{ |
558 |
return 4; |
559 |
} |
560 |
|
561 |
QVariantMap Window::gMenuToDBusMenuProperties(const QVariantMap &source) const |
562 |
{ |
563 |
QVariantMap result; |
564 |
|
565 |
result.insert(QStringLiteral("label"), source.value(QStringLiteral("label")).toString()); |
566 |
|
567 |
if (source.contains(QStringLiteral(":section"))) { |
568 |
result.insert(QStringLiteral("type"), QStringLiteral("separator")); |
569 |
} |
570 |
|
571 |
const bool isMenu = source.contains(QStringLiteral(":submenu")); |
572 |
if (isMenu) { |
573 |
result.insert(QStringLiteral("children-display"), QStringLiteral("submenu")); |
574 |
} |
575 |
|
576 |
QString accel = source.value(QStringLiteral("accel")).toString(); |
577 |
if (!accel.isEmpty()) { |
578 |
QStringList shortcut; |
579 |
|
580 |
// TODO use regexp or something |
581 |
if (accel.contains(QLatin1String("<Primary>")) || accel.contains(QLatin1String("<Control>"))) { |
582 |
shortcut.append(QStringLiteral("Control")); |
583 |
accel.remove(QLatin1String("<Primary>")); |
584 |
accel.remove(QLatin1String("<Control>")); |
585 |
} |
586 |
|
587 |
if (accel.contains(QLatin1String("<Shift>"))) { |
588 |
shortcut.append(QStringLiteral("Shift")); |
589 |
accel.remove(QLatin1String("<Shift>")); |
590 |
} |
591 |
|
592 |
if (accel.contains(QLatin1String("<Alt>"))) { |
593 |
shortcut.append(QStringLiteral("Alt")); |
594 |
accel.remove(QLatin1String("<Alt>")); |
595 |
} |
596 |
|
597 |
if (accel.contains(QLatin1String("<Super>"))) { |
598 |
shortcut.append(QStringLiteral("Super")); |
599 |
accel.remove(QLatin1String("<Super>")); |
600 |
} |
601 |
|
602 |
if (!accel.isEmpty()) { |
603 |
// TODO replace "+" by "plus" and "-" by "minus" |
604 |
shortcut.append(accel); |
605 |
|
606 |
// TODO does gmenu support multiple? |
607 |
DBusMenuShortcut dbusShortcut; |
608 |
dbusShortcut.append(shortcut); // don't let it unwrap the list we append |
609 |
|
610 |
result.insert(QStringLiteral("shortcut"), QVariant::fromValue(dbusShortcut)); |
611 |
} |
612 |
} |
613 |
|
614 |
bool enabled = true; |
615 |
|
616 |
const QString actionName = Utils::itemActionName(source); |
617 |
|
618 |
GMenuAction action; |
619 |
// if no action is specified this is fine but if there is an action we don't have |
620 |
// disable the menu entry |
621 |
bool actionOk = true; |
622 |
if (!actionName.isEmpty()) { |
623 |
actionOk = getAction(actionName, action); |
624 |
enabled = actionOk && action.enabled; |
625 |
} |
626 |
|
627 |
// we used to only send this if not enabled but then dbusmenuimporter does not |
628 |
// update the enabled state when it changes from disabled to enabled |
629 |
result.insert(QStringLiteral("enabled"), enabled); |
630 |
|
631 |
bool visible = true; |
632 |
const QString hiddenWhen = source.value(QStringLiteral("hidden-when")).toString(); |
633 |
if (hiddenWhen == QLatin1String("action-disabled") && (!actionOk || !enabled)) { |
634 |
visible = false; |
635 |
} else if (hiddenWhen == QLatin1String("action-missing") && !actionOk) { |
636 |
visible = false; |
637 |
// While we have Global Menu we don't have macOS menu (where Quit, Help, etc is separate) |
638 |
} else if (hiddenWhen == QLatin1String("macos-menubar")) { |
639 |
visible = true; |
640 |
} |
641 |
|
642 |
result.insert(QStringLiteral("visible"), visible); |
643 |
|
644 |
QString icon = source.value(QStringLiteral("icon")).toString(); |
645 |
if (icon.isEmpty()) { |
646 |
icon = source.value(QStringLiteral("verb-icon")).toString(); |
647 |
} |
648 |
|
649 |
icon = Icons::actionIcon(actionName); |
650 |
if (!icon.isEmpty()) { |
651 |
result.insert(QStringLiteral("icon-name"), icon); |
652 |
} |
653 |
|
654 |
if (actionOk) { |
655 |
const auto args = action.state; |
656 |
if (args.count() == 1) { |
657 |
const auto &firstArg = args.first(); |
658 |
// assume this is a checkbox |
659 |
if (!isMenu) { |
660 |
if (firstArg.type() == QVariant::Bool) { |
661 |
result.insert(QStringLiteral("toggle-type"), QStringLiteral("checkbox")); |
662 |
result.insert(QStringLiteral("toggle-state"), firstArg.toBool() ? 1 : 0); |
663 |
} else if (firstArg.type() == QVariant::String) { |
664 |
result.insert(QStringLiteral("toggle-type"), QStringLiteral("radio")); |
665 |
const QString checkedAction = firstArg.toString(); |
666 |
if (!checkedAction.isEmpty() && actionName.endsWith(checkedAction)) { |
667 |
result.insert(QStringLiteral("toggle-state"), 1); |
668 |
} else { |
669 |
result.insert(QStringLiteral("toggle-state"), 0); |
670 |
} |
671 |
} |
672 |
} |
673 |
} |
674 |
} |
675 |
|
676 |
return result; |
677 |
} |