appmenu.cpp 8.58 KB
Newer Older
1
2
3
4
5
/*
  This file is part of the KDE project.

  Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
  Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
6
  Copyright (c) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

  Permission is hereby granted, free of charge, to any person obtaining a
  copy of this software and associated documentation files (the "Software"),
  to deal in the Software without restriction, including without limitation
  the rights to use, copy, modify, merge, publish, distribute, sublicense,
  and/or sell copies of the Software, and to permit persons to whom the
  Software is furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  DEALINGS IN THE SOFTWARE.
*/

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
27
28
#include <config-X11.h>

29
#include "appmenu.h"
Alexander Lohnau's avatar
Alexander Lohnau committed
30
31
#include "appmenu_dbus.h"
#include "appmenuadaptor.h"
32
33
34
35
#include "kdbusimporter.h"
#include "menuimporteradaptor.h"
#include "verticalmenu.h"

36
#include <QApplication>
37
38
39
40
41
#include <QDBusInterface>
#include <QMenu>

#include <kpluginfactory.h>
#include <kpluginloader.h>
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
42
43
44
45
46
47
48
49

#if HAVE_X11
#include <QX11Info>
#include <xcb/xcb.h>
#endif

static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
50

Alexander Lohnau's avatar
Alexander Lohnau committed
51
K_PLUGIN_FACTORY_WITH_JSON(AppMenuFactory, "appmenu.json", registerPlugin<AppMenuModule>();)
52

Alexander Lohnau's avatar
Alexander Lohnau committed
53
54
55
AppMenuModule::AppMenuModule(QObject *parent, const QList<QVariant> &)
    : KDEDModule(parent)
    , m_appmenuDBus(new AppmenuDBus(this))
56
57
58
59
60
{
    reconfigure();

    m_appmenuDBus->connectToBus();

61
    connect(m_appmenuDBus, &AppmenuDBus::appShowMenu, this, &AppMenuModule::slotShowMenu);
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
62
    connect(m_appmenuDBus, &AppmenuDBus::reconfigured, this, &AppMenuModule::reconfigure);
63
64

    // transfer our signals to dbus
65
66
    connect(this, &AppMenuModule::showRequest, m_appmenuDBus, &AppmenuDBus::showRequest);
    connect(this, &AppMenuModule::menuHidden, m_appmenuDBus, &AppmenuDBus::menuHidden);
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
67
    connect(this, &AppMenuModule::menuShown, m_appmenuDBus, &AppmenuDBus::menuShown);
68

Alexander Lohnau's avatar
Alexander Lohnau committed
69
70
71
72
    m_menuViewWatcher = new QDBusServiceWatcher(QStringLiteral("org.kde.kappmenuview"),
                                                QDBusConnection::sessionBus(),
                                                QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
                                                this);
73
74

    auto setupMenuImporter = [this]() {
Alexander Lohnau's avatar
Alexander Lohnau committed
75
76
77
78
79
80
        QDBusConnection::sessionBus().connect({},
                                              {},
                                              QStringLiteral("com.canonical.dbusmenu"),
                                              QStringLiteral("ItemActivationRequested"),
                                              this,
                                              SLOT(itemActivationRequested(int, uint)));
81
82
83
84
85
86
87
88
89
90
91

        // Setup a menu importer if needed
        if (!m_menuImporter) {
            m_menuImporter = new MenuImporter(this);
            connect(m_menuImporter, &MenuImporter::WindowRegistered, this, &AppMenuModule::slotWindowRegistered);
            m_menuImporter->connectToBus();
        }
    };
    connect(m_menuViewWatcher, &QDBusServiceWatcher::serviceRegistered, this, setupMenuImporter);
    connect(m_menuViewWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &service) {
        Q_UNUSED(service)
Alexander Lohnau's avatar
Alexander Lohnau committed
92
93
94
95
96
97
        QDBusConnection::sessionBus().disconnect({},
                                                 {},
                                                 QStringLiteral("com.canonical.dbusmenu"),
                                                 QStringLiteral("ItemActivationRequested"),
                                                 this,
                                                 SLOT(itemActivationRequested(int, uint)));
98
99
100
101
102
103
104
        delete m_menuImporter;
        m_menuImporter = nullptr;
    });

    if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kappmenuview"))) {
        setupMenuImporter();
    }
105
106
107
108
109
110

#if HAVE_X11
    if (!QX11Info::connection()) {
        m_xcbConn = xcb_connect(nullptr, nullptr);
    }
#endif
111
112
}

113
114
115
116
117
118
119
120
AppMenuModule::~AppMenuModule()
{
#if HAVE_X11
    if (m_xcbConn) {
        xcb_disconnect(m_xcbConn);
    }
#endif
}
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
121
122

void AppMenuModule::slotWindowRegistered(WId id, const QString &serviceName, const QDBusObjectPath &menuObjectPath)
123
{
124
#if HAVE_X11
125
126
127
128
    auto *c = QX11Info::connection();
    if (!c) {
        c = m_xcbConn;
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
129

Alexander Lohnau's avatar
Alexander Lohnau committed
130
    if (c) {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
131
132
133
134
135
136
        static xcb_atom_t s_serviceNameAtom = XCB_ATOM_NONE;
        static xcb_atom_t s_objectPathAtom = XCB_ATOM_NONE;

        auto setWindowProperty = [c](WId id, xcb_atom_t &atom, const QByteArray &name, const QByteArray &value) {
            if (atom == XCB_ATOM_NONE) {
                const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, false, name.length(), name.constData());
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
137
                QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> reply(xcb_intern_atom_reply(c, cookie, nullptr));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
138
139
140
141
142
143
144
145
146
                if (reply.isNull()) {
                    return;
                }
                atom = reply->atom;
                if (atom == XCB_ATOM_NONE) {
                    return;
                }
            }

Alexander Lohnau's avatar
Alexander Lohnau committed
147
            auto cookie = xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING, 8, value.length(), value.constData());
148
149
150
151
152
153
            xcb_generic_error_t *error;
            if ((error = xcb_request_check(c, cookie))) {
                qWarning() << "Got an error";
                free(error);
                return;
            }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
154
155
156
157
158
159
160
        };

        // TODO only set the property if it doesn't already exist

        setWindowProperty(id, s_serviceNameAtom, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8());
        setWindowProperty(id, s_objectPathAtom, s_x11AppMenuObjectPathPropertyName, menuObjectPath.path().toUtf8());
    }
161
#endif
162
163
}

164
void AppMenuModule::slotShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId)
165
166
167
168
169
170
{
    if (!m_menuImporter) {
        return;
    }

    // If menu visible, hide it
171
172
    if (m_menu && m_menu.data()->isVisible()) {
        m_menu.data()->hide();
173
174
175
        return;
    }

Alexander Lohnau's avatar
Alexander Lohnau committed
176
    // dbus call by user (for khotkey shortcut)
177
178
    if (x == -1 || y == -1) {
        // We do not know kwin button position, so tell kwin to show menu
179
        emit showRequest(serviceName, menuObjectPath, actionId);
180
181
182
        return;
    }

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
183
184
    auto *importer = new KDBusMenuImporter(serviceName, menuObjectPath.path(), this);
    QMetaObject::invokeMethod(importer, "updateMenu", Qt::QueuedConnection);
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
185
    disconnect(importer, nullptr, this, nullptr); // ensure we don't popup multiple times in case the menu updates again later
186

187
    connect(importer, &KDBusMenuImporter::menuUpdated, this, [=](QMenu *m) {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
188
        QMenu *menu = importer->menu();
189
        if (!menu || menu != m) {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
190
191
            return;
        }
Alexander Lohnau's avatar
Alexander Lohnau committed
192
        m_menu = qobject_cast<VerticalMenu *>(menu);
193

194
195
        m_menu.data()->setServiceName(serviceName);
        m_menu.data()->setMenuObjectPath(menuObjectPath);
196

197
        connect(m_menu.data(), &QMenu::aboutToHide, this, [this, importer] {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
198
199
200
            hideMenu();
            importer->deleteLater();
        });
201

Alexander Lohnau's avatar
Alexander Lohnau committed
202
        // m_menuImporter->fakeUnityAboutToShow(serviceName, menuObjectPath);
203

204
        m_menu.data()->popup(QPoint(x, y) / qApp->devicePixelRatio());
205

206
207
        QAction *actiontoActivate = importer->actionForId(actionId);

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
208
        emit menuShown(serviceName, menuObjectPath);
209

210
211
        if (actiontoActivate) {
            m_menu.data()->setActiveAction(actiontoActivate);
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
212
213
        }
    });
214
215
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
216
void AppMenuModule::hideMenu()
217
{
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
218
    if (m_menu) {
219
        emit menuHidden(m_menu.data()->serviceName(), m_menu->menuObjectPath());
220
221
222
    }
}

223
void AppMenuModule::itemActivationRequested(int actionId, uint timeStamp)
224
{
225
226
    Q_UNUSED(timeStamp);
    emit showRequest(message().service(), QDBusObjectPath(message().path()), actionId);
227
228
}

229
// this method is not really used anymore but has to be kept for DBus compatibility
230
231
232
233
234
void AppMenuModule::reconfigure()
{
}

#include "appmenu.moc"