mirror of https://github.com/cutefishos/statusbar
Add the global menu
parent
4a635c6acc
commit
bb2e092579
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
Copyright (c) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "appmenu.h"
|
||||||
|
#include "appmenudbus.h"
|
||||||
|
#include "appmenuadaptor.h"
|
||||||
|
#include "kdbusimporter.h"
|
||||||
|
#include "menuimporteradaptor.h"
|
||||||
|
#include "verticalmenu.h"
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
// X11
|
||||||
|
#include <QX11Info>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
|
||||||
|
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
|
||||||
|
|
||||||
|
AppMenu::AppMenu(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_appmenuDBus(new AppmenuDBus(this))
|
||||||
|
{
|
||||||
|
reconfigure();
|
||||||
|
|
||||||
|
m_appmenuDBus->connectToBus();
|
||||||
|
|
||||||
|
connect(m_appmenuDBus, &AppmenuDBus::appShowMenu, this, &AppMenu::slotShowMenu);
|
||||||
|
connect(m_appmenuDBus, &AppmenuDBus::reconfigured, this, &AppMenu::reconfigure);
|
||||||
|
|
||||||
|
// transfer our signals to dbus
|
||||||
|
connect(this, &AppMenu::showRequest, m_appmenuDBus, &AppmenuDBus::showRequest);
|
||||||
|
connect(this, &AppMenu::menuHidden, m_appmenuDBus, &AppmenuDBus::menuHidden);
|
||||||
|
connect(this, &AppMenu::menuShown, m_appmenuDBus, &AppmenuDBus::menuShown);
|
||||||
|
|
||||||
|
m_menuViewWatcher = new QDBusServiceWatcher(QStringLiteral("org.cutefish.cappmenuview"),
|
||||||
|
QDBusConnection::sessionBus(),
|
||||||
|
QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
|
||||||
|
this);
|
||||||
|
|
||||||
|
auto setupMenuImporter = [this]() {
|
||||||
|
QDBusConnection::sessionBus().connect({},
|
||||||
|
{},
|
||||||
|
QStringLiteral("com.canonical.dbusmenu"),
|
||||||
|
QStringLiteral("ItemActivationRequested"),
|
||||||
|
this,
|
||||||
|
SLOT(itemActivationRequested(int, uint)));
|
||||||
|
|
||||||
|
// Setup a menu importer if needed
|
||||||
|
if (!m_menuImporter) {
|
||||||
|
m_menuImporter = new MenuImporter(this);
|
||||||
|
connect(m_menuImporter, &MenuImporter::WindowRegistered, this, &AppMenu::slotWindowRegistered);
|
||||||
|
m_menuImporter->connectToBus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
connect(m_menuViewWatcher, &QDBusServiceWatcher::serviceRegistered, this, setupMenuImporter);
|
||||||
|
connect(m_menuViewWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &service) {
|
||||||
|
Q_UNUSED(service)
|
||||||
|
QDBusConnection::sessionBus().disconnect({},
|
||||||
|
{},
|
||||||
|
QStringLiteral("com.canonical.dbusmenu"),
|
||||||
|
QStringLiteral("ItemActivationRequested"),
|
||||||
|
this,
|
||||||
|
SLOT(itemActivationRequested(int, uint)));
|
||||||
|
delete m_menuImporter;
|
||||||
|
m_menuImporter = nullptr;
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kappmenuview"))) {
|
||||||
|
setupMenuImporter();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!QX11Info::connection()) {
|
||||||
|
m_xcbConn = xcb_connect(nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppMenu::~AppMenu()
|
||||||
|
{
|
||||||
|
if (m_xcbConn) {
|
||||||
|
xcb_disconnect(m_xcbConn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppMenu::eventFilter(QObject *object, QEvent *event)
|
||||||
|
{
|
||||||
|
// HACK we need an input serial to create popups but Qt only sets them on click
|
||||||
|
// if (object == m_menu && event->type() == QEvent::Enter && m_plasmashell) {
|
||||||
|
// auto waylandWindow = dynamic_cast<QtWaylandClient::QWaylandWindow *>(m_menu->windowHandle()->handle());
|
||||||
|
// if (waylandWindow) {
|
||||||
|
// const auto device = waylandWindow->display()->currentInputDevice();
|
||||||
|
// waylandWindow->display()->setLastInputDevice(device, device->pointer()->mEnterSerial, waylandWindow);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return AppMenu::eventFilter(object, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::slotWindowRegistered(WId id, const QString &serviceName, const QDBusObjectPath &menuObjectPath)
|
||||||
|
{
|
||||||
|
auto *c = QX11Info::connection();
|
||||||
|
if (!c) {
|
||||||
|
c = m_xcbConn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c) {
|
||||||
|
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());
|
||||||
|
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> reply(xcb_intern_atom_reply(c, cookie, nullptr));
|
||||||
|
if (reply.isNull()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
atom = reply->atom;
|
||||||
|
if (atom == XCB_ATOM_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cookie = xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, id, atom, XCB_ATOM_STRING, 8, value.length(), value.constData());
|
||||||
|
xcb_generic_error_t *error;
|
||||||
|
if ((error = xcb_request_check(c, cookie))) {
|
||||||
|
qWarning() << "Got an error";
|
||||||
|
free(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::slotShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId)
|
||||||
|
{
|
||||||
|
if (!m_menuImporter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If menu visible, hide it
|
||||||
|
if (m_menu && m_menu.data()->isVisible()) {
|
||||||
|
m_menu.data()->hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dbus call by user (for khotkey shortcut)
|
||||||
|
if (x == -1 || y == -1) {
|
||||||
|
// We do not know kwin button position, so tell kwin to show menu
|
||||||
|
emit showRequest(serviceName, menuObjectPath, actionId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *importer = new KDBusMenuImporter(serviceName, menuObjectPath.path(), this);
|
||||||
|
QMetaObject::invokeMethod(importer, "updateMenu", Qt::QueuedConnection);
|
||||||
|
disconnect(importer, nullptr, this, nullptr); // ensure we don't popup multiple times in case the menu updates again later
|
||||||
|
|
||||||
|
connect(importer, &KDBusMenuImporter::menuUpdated, this, [=](QMenu *m) {
|
||||||
|
QMenu *menu = importer->menu();
|
||||||
|
if (!menu || menu != m) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_menu = qobject_cast<VerticalMenu *>(menu);
|
||||||
|
|
||||||
|
m_menu.data()->setServiceName(serviceName);
|
||||||
|
m_menu.data()->setMenuObjectPath(menuObjectPath);
|
||||||
|
|
||||||
|
connect(m_menu.data(), &QMenu::aboutToHide, this, [this, importer] {
|
||||||
|
hideMenu();
|
||||||
|
importer->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
// if (m_plasmashell) {
|
||||||
|
// connect(m_menu.data(), &QMenu::aboutToShow, this, &AppMenuModule::initMenuWayland, Qt::UniqueConnection);
|
||||||
|
// m_menu.data()->popup(QPoint(x, y));
|
||||||
|
// } else {
|
||||||
|
m_menu.data()->popup(QPoint(x, y) / qApp->devicePixelRatio());
|
||||||
|
// }
|
||||||
|
|
||||||
|
QAction *actiontoActivate = importer->actionForId(actionId);
|
||||||
|
|
||||||
|
emit menuShown(serviceName, menuObjectPath);
|
||||||
|
|
||||||
|
if (actiontoActivate) {
|
||||||
|
m_menu.data()->setActiveAction(actiontoActivate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::reconfigure()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::itemActivationRequested(int actionId, uint timeStamp)
|
||||||
|
{
|
||||||
|
Q_UNUSED(timeStamp);
|
||||||
|
emit showRequest(message().service(), QDBusObjectPath(message().path()), actionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::hideMenu()
|
||||||
|
{
|
||||||
|
if (m_menu) {
|
||||||
|
emit menuHidden(m_menu.data()->serviceName(), m_menu->menuObjectPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenu::fakeUnityAboutToShow(const QString &service, const QDBusObjectPath &menuObjectPath)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
KDBusMenuImporter *AppMenu::getImporter(const QString &service, const QString &path)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
Copyright (c) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPMENU_H
|
||||||
|
#define APPMENU_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#include "menuimporter.h"
|
||||||
|
#include <QPointer>
|
||||||
|
|
||||||
|
class QDBusServiceWatcher;
|
||||||
|
class KDBusMenuImporter;
|
||||||
|
class AppmenuDBus;
|
||||||
|
class VerticalMenu;
|
||||||
|
|
||||||
|
class AppMenu : public QObject, protected QDBusContext
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AppMenu(QObject *parent = nullptr);
|
||||||
|
~AppMenu() override;
|
||||||
|
|
||||||
|
bool eventFilter(QObject *object, QEvent *event) override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
/**
|
||||||
|
* We do not know where is menu decoration button, so tell kwin to show menu
|
||||||
|
*/
|
||||||
|
void showRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
|
||||||
|
/**
|
||||||
|
* This signal is emitted whenever popup menu/menubar is shown
|
||||||
|
* Useful for decorations to know if menu button should look pressed
|
||||||
|
*/
|
||||||
|
void menuShown(const QString &service, const QDBusObjectPath &objectPath);
|
||||||
|
/**
|
||||||
|
* This signal is emitted whenever popup menu/menubar is hidden
|
||||||
|
* Useful for decorations to know if menu button should be release
|
||||||
|
*/
|
||||||
|
void menuHidden(const QString &service, const QDBusObjectPath &objectPath);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
/**
|
||||||
|
* A new window was registered to AppMenu
|
||||||
|
*
|
||||||
|
* For compatibility this will set the DBus service name and menu object path as properties
|
||||||
|
* on the window so we keep working with clients that use the DBusMenu "properly".
|
||||||
|
*/
|
||||||
|
void slotWindowRegistered(WId id, const QString &serviceName, const QDBusObjectPath &menuObjectPath);
|
||||||
|
/**
|
||||||
|
* Show menu at QPoint(x,y) for DBus serviceName and menuObjectPath
|
||||||
|
* if x or y == -1, show in application window
|
||||||
|
*/
|
||||||
|
void slotShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
|
||||||
|
/**
|
||||||
|
* Reconfigure module
|
||||||
|
*/
|
||||||
|
void reconfigure();
|
||||||
|
|
||||||
|
void itemActivationRequested(int actionId, uint timeStamp);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hideMenu();
|
||||||
|
|
||||||
|
void fakeUnityAboutToShow(const QString &service, const QDBusObjectPath &menuObjectPath);
|
||||||
|
|
||||||
|
KDBusMenuImporter *getImporter(const QString &service, const QString &path);
|
||||||
|
|
||||||
|
MenuImporter *m_menuImporter = nullptr;
|
||||||
|
AppmenuDBus *m_appmenuDBus;
|
||||||
|
QDBusServiceWatcher *m_menuViewWatcher;
|
||||||
|
QPointer<VerticalMenu> m_menu;
|
||||||
|
xcb_connection_t *m_xcbConn = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPMENU_H
|
||||||
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Reion Wong <aj@cutefishos.com>
|
||||||
|
* Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "appmenuapplet.h"
|
||||||
|
#include <QAction>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusConnectionInterface>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
int AppMenuApplet::s_refs = 0;
|
||||||
|
|
||||||
|
static QString viewService()
|
||||||
|
{
|
||||||
|
return QStringLiteral("org.cutefish.cappmenuview");
|
||||||
|
}
|
||||||
|
|
||||||
|
AppMenuApplet::AppMenuApplet(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
++s_refs;
|
||||||
|
// if we're the first, register the service
|
||||||
|
if (s_refs == 1) {
|
||||||
|
QDBusConnection::sessionBus().interface()->registerService(viewService(),
|
||||||
|
QDBusConnectionInterface::QueueService,
|
||||||
|
QDBusConnectionInterface::DontAllowReplacement);
|
||||||
|
}
|
||||||
|
/*it registers or unregisters the service when the destroyed value of the applet change,
|
||||||
|
and not in the dtor, because:
|
||||||
|
when we "delete" an applet, it just hides it for about a minute setting its status
|
||||||
|
to destroyed, in order to be able to do a clean undo: if we undo, there will be
|
||||||
|
another destroyedchanged and destroyed will be false.
|
||||||
|
When this happens, if we are the only appmenu applet existing, the dbus interface
|
||||||
|
will have to be registered again*/
|
||||||
|
// connect(this, &Applet::destroyedChanged, this, [](bool destroyed) {
|
||||||
|
// if (destroyed) {
|
||||||
|
// // if we were the last, unregister
|
||||||
|
// if (--s_refs == 0) {
|
||||||
|
// QDBusConnection::sessionBus().interface()->unregisterService(viewService());
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// // if we're the first, register the service
|
||||||
|
// if (++s_refs == 1) {
|
||||||
|
// QDBusConnection::sessionBus().interface()->registerService(viewService(),
|
||||||
|
// QDBusConnectionInterface::QueueService,
|
||||||
|
// QDBusConnectionInterface::DontAllowReplacement);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppMenuApplet::currentIndex() const
|
||||||
|
{
|
||||||
|
return m_currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickItem *AppMenuApplet::buttonGrid() const
|
||||||
|
{
|
||||||
|
return m_buttonGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuApplet::setButtonGrid(QQuickItem *buttonGrid)
|
||||||
|
{
|
||||||
|
if (m_buttonGrid != buttonGrid) {
|
||||||
|
m_buttonGrid = buttonGrid;
|
||||||
|
emit buttonGridChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AppMenuModel *AppMenuApplet::model() const
|
||||||
|
{
|
||||||
|
return m_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuApplet::setModel(AppMenuModel *model)
|
||||||
|
{
|
||||||
|
if (m_model != model) {
|
||||||
|
m_model = model;
|
||||||
|
emit modelChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuApplet::trigger(QQuickItem *ctx, int idx)
|
||||||
|
{
|
||||||
|
if (m_currentIndex == idx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx || !ctx->window() || !ctx->window()->screen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu *actionMenu = createMenu(idx);
|
||||||
|
if (actionMenu) {
|
||||||
|
// this is a workaround where Qt will fail to realize a mouse has been released
|
||||||
|
// this happens if a window which does not accept focus spawns a new window that takes focus and X grab
|
||||||
|
// whilst the mouse is depressed
|
||||||
|
// https://bugreports.qt.io/browse/QTBUG-59044
|
||||||
|
// this causes the next click to go missing
|
||||||
|
|
||||||
|
// by releasing manually we avoid that situation
|
||||||
|
auto ungrabMouseHack = [ctx]() {
|
||||||
|
if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) {
|
||||||
|
// FIXME event forge thing enters press and hold move mode :/
|
||||||
|
ctx->window()->mouseGrabberItem()->ungrabMouse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTimer::singleShot(0, ctx, ungrabMouseHack);
|
||||||
|
// end workaround
|
||||||
|
|
||||||
|
const auto &geo = ctx->window()->screen()->availableVirtualGeometry();
|
||||||
|
|
||||||
|
QPoint pos = ctx->window()->mapToGlobal(ctx->mapToScene(QPointF()).toPoint());
|
||||||
|
// Top
|
||||||
|
pos.setY(pos.y() + ctx->height() + 6);
|
||||||
|
|
||||||
|
actionMenu->adjustSize();
|
||||||
|
|
||||||
|
pos = QPoint(qBound(geo.x(), pos.x(), geo.x() + geo.width() - actionMenu->width()),
|
||||||
|
qBound(geo.y(), pos.y(), geo.y() + geo.height() - actionMenu->height()));
|
||||||
|
|
||||||
|
actionMenu->installEventFilter(this);
|
||||||
|
|
||||||
|
actionMenu->winId(); // create window handle
|
||||||
|
actionMenu->windowHandle()->setTransientParent(ctx->window());
|
||||||
|
|
||||||
|
// hide the old menu only after showing the new one to avoid brief focus flickering on X11.
|
||||||
|
// on wayland, you can't have more than one grabbing popup at a time so we show it after
|
||||||
|
// the menu has hidden. thankfully, wayland doesn't have this flickering.
|
||||||
|
if (!KWindowSystem::isPlatformWayland()) {
|
||||||
|
actionMenu->popup(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu *oldMenu = m_currentMenu;
|
||||||
|
m_currentMenu = actionMenu;
|
||||||
|
if (oldMenu && oldMenu != actionMenu) {
|
||||||
|
// don't initialize the currentIndex when another menu is already shown
|
||||||
|
disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide);
|
||||||
|
oldMenu->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (KWindowSystem::isPlatformWayland()) {
|
||||||
|
actionMenu->popup(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentIndex(idx);
|
||||||
|
|
||||||
|
// FIXME TODO connect only once
|
||||||
|
connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection);
|
||||||
|
} else { // is it just an action without a menu?
|
||||||
|
const QVariant data = m_model->index(idx, 0).data(AppMenuModel::ActionRole);
|
||||||
|
QAction *action = static_cast<QAction *>(data.value<void *>());
|
||||||
|
if (action) {
|
||||||
|
Q_ASSERT(!action->menu());
|
||||||
|
action->trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppMenuApplet::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
auto *menu = qobject_cast<QMenu *>(watched);
|
||||||
|
if (!menu) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event->type() == QEvent::KeyPress) {
|
||||||
|
auto *e = static_cast<QKeyEvent *>(event);
|
||||||
|
|
||||||
|
// TODO right to left languages
|
||||||
|
if (e->key() == Qt::Key_Left) {
|
||||||
|
int desiredIndex = m_currentIndex - 1;
|
||||||
|
emit requestActivateIndex(desiredIndex);
|
||||||
|
return true;
|
||||||
|
} else if (e->key() == Qt::Key_Right) {
|
||||||
|
if (menu->activeAction() && menu->activeAction()->menu()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int desiredIndex = m_currentIndex + 1;
|
||||||
|
emit requestActivateIndex(desiredIndex);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (event->type() == QEvent::MouseMove) {
|
||||||
|
auto *e = static_cast<QMouseEvent *>(event);
|
||||||
|
|
||||||
|
if (!m_buttonGrid || !m_buttonGrid->window()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME the panel margin breaks Fitt's law :(
|
||||||
|
const QPointF &windowLocalPos = m_buttonGrid->window()->mapFromGlobal(e->globalPos());
|
||||||
|
const QPointF &buttonGridLocalPos = m_buttonGrid->mapFromScene(windowLocalPos);
|
||||||
|
auto *item = m_buttonGrid->childAt(buttonGridLocalPos.x(), buttonGridLocalPos.y());
|
||||||
|
if (!item) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
const int buttonIndex = item->property("buttonIndex").toInt(&ok);
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit requestActivateIndex(buttonIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu *AppMenuApplet::createMenu(int idx) const
|
||||||
|
{
|
||||||
|
QMenu *menu = nullptr;
|
||||||
|
QAction *action = nullptr;
|
||||||
|
|
||||||
|
const QModelIndex index = m_model->index(idx, 0);
|
||||||
|
const QVariant data = m_model->data(index, AppMenuModel::ActionRole);
|
||||||
|
action = (QAction *)data.value<void *>();
|
||||||
|
if (action) {
|
||||||
|
menu = action->menu();
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuApplet::setCurrentIndex(int currentIndex)
|
||||||
|
{
|
||||||
|
if (m_currentIndex != currentIndex) {
|
||||||
|
m_currentIndex = currentIndex;
|
||||||
|
emit currentIndexChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuApplet::onMenuAboutToHide()
|
||||||
|
{
|
||||||
|
setCurrentIndex(-1);
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Reion Wong <aj@cutefishos.com>
|
||||||
|
* Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPMENUAPPLET_H
|
||||||
|
#define APPMENUAPPLET_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQuickItem>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
#include "appmenumodel.h"
|
||||||
|
|
||||||
|
class AppMenuApplet : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(AppMenuModel *model READ model WRITE setModel NOTIFY modelChanged)
|
||||||
|
Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged)
|
||||||
|
Q_PROPERTY(QQuickItem *buttonGrid READ buttonGrid WRITE setButtonGrid NOTIFY buttonGridChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AppMenuApplet(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
int currentIndex() const;
|
||||||
|
|
||||||
|
QQuickItem *buttonGrid() const;
|
||||||
|
void setButtonGrid(QQuickItem *buttonGrid);
|
||||||
|
|
||||||
|
AppMenuModel *model() const;
|
||||||
|
void setModel(AppMenuModel *model);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void modelChanged();
|
||||||
|
void viewChanged();
|
||||||
|
void currentIndexChanged();
|
||||||
|
void buttonGridChanged();
|
||||||
|
void requestActivateIndex(int index);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
Q_INVOKABLE void trigger(QQuickItem *ctx, int idx);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMenu *createMenu(int idx) const;
|
||||||
|
void setCurrentIndex(int currentIndex);
|
||||||
|
void onMenuAboutToHide();
|
||||||
|
|
||||||
|
int m_currentIndex = -1;
|
||||||
|
QPointer<QMenu> m_currentMenu;
|
||||||
|
QPointer<QQuickItem> m_buttonGrid;
|
||||||
|
QPointer<AppMenuModel> m_model;
|
||||||
|
static int s_refs;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPMENUAPPLET_H
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "appmenudbus.h"
|
||||||
|
#include "appmenuadaptor.h"
|
||||||
|
#include "kdbusimporter.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#include <QDBusServiceWatcher>
|
||||||
|
|
||||||
|
static const char *DBUS_SERVICE = "org.cutefish.cappmenu";
|
||||||
|
static const char *DBUS_OBJECT_PATH = "/CAppMenu";
|
||||||
|
|
||||||
|
AppmenuDBus::AppmenuDBus(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
AppmenuDBus::~AppmenuDBus()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppmenuDBus::connectToBus(const QString &service, const QString &path)
|
||||||
|
{
|
||||||
|
m_service = service.isEmpty() ? DBUS_SERVICE : service;
|
||||||
|
QString newPath = path.isEmpty() ? DBUS_OBJECT_PATH : path;
|
||||||
|
|
||||||
|
if (!QDBusConnection::sessionBus().registerService(m_service)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
new AppmenuAdaptor(this);
|
||||||
|
QDBusConnection::sessionBus().registerObject(newPath, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppmenuDBus::showMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId)
|
||||||
|
{
|
||||||
|
emit appShowMenu(x, y, serviceName, menuObjectPath, actionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppmenuDBus::reconfigure()
|
||||||
|
{
|
||||||
|
emit reconfigured();
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPMENU_DBUS_H
|
||||||
|
#define APPMENU_DBUS_H
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QDBusContext>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QObject>
|
||||||
|
#include <qwindowdefs.h>
|
||||||
|
|
||||||
|
class AppmenuDBus : public QObject, protected QDBusContext
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AppmenuDBus(QObject *);
|
||||||
|
~AppmenuDBus() override;
|
||||||
|
|
||||||
|
bool connectToBus(const QString &service = QString(), const QString &path = QString());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DBus method showing menu at QPoint(x,y) for given DBus service name and menuObjectPath
|
||||||
|
* if x or y == -1, show in application window
|
||||||
|
*/
|
||||||
|
void showMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
|
||||||
|
/**
|
||||||
|
* DBus method reconfiguring kded module
|
||||||
|
*/
|
||||||
|
void reconfigure();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
/**
|
||||||
|
* This signal is emitted on showMenu() request
|
||||||
|
*/
|
||||||
|
void appShowMenu(int x, int y, const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
|
||||||
|
/**
|
||||||
|
* This signal is emitted on reconfigure() request
|
||||||
|
*/
|
||||||
|
void reconfigured();
|
||||||
|
|
||||||
|
// Dbus signals
|
||||||
|
/**
|
||||||
|
* This signal is emitted whenever kded want to show menu
|
||||||
|
* We do not know where is menu decoration button, so tell kwin to show menu
|
||||||
|
*/
|
||||||
|
void showRequest(const QString &serviceName, const QDBusObjectPath &menuObjectPath, int actionId);
|
||||||
|
/**
|
||||||
|
* This signal is emitted whenever popup menu/menubar is shown
|
||||||
|
* Useful for decorations to know if menu button should look pressed
|
||||||
|
*/
|
||||||
|
void menuShown(const QString &serviceName, const QDBusObjectPath &menuObjectPath);
|
||||||
|
/**
|
||||||
|
* This signal is emitted whenever popup menu/menubar is hidden
|
||||||
|
* Useful for decorations to know if menu button should be release
|
||||||
|
*/
|
||||||
|
void menuHidden(const QString &serviceName, const QDBusObjectPath &menuObjectPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_service;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // APPMENU_DBUS_H
|
||||||
@ -0,0 +1,249 @@
|
|||||||
|
/******************************************************************
|
||||||
|
* Copyright 2021 Reion Wong <aj@cutefishos.com>
|
||||||
|
* Copyright 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
* Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
******************************************************************/
|
||||||
|
|
||||||
|
#include "appmenumodel.h"
|
||||||
|
|
||||||
|
#include <QAction>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusConnectionInterface>
|
||||||
|
#include <QDBusServiceWatcher>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
#include <KWindowSystem>
|
||||||
|
|
||||||
|
#include "../libdbusmenuqt/dbusmenuimporter.h"
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
class CDBusMenuImporter : public DBusMenuImporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CDBusMenuImporter(const QString &service, const QString &path, QObject *parent)
|
||||||
|
: DBusMenuImporter(service, path, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QIcon iconForName(const QString &name) override
|
||||||
|
{
|
||||||
|
return QIcon::fromTheme(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AppMenuModel::AppMenuModel(QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
, m_serviceWatcher(new QDBusServiceWatcher(this))
|
||||||
|
{
|
||||||
|
connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] {
|
||||||
|
if (!m_updatePending) {
|
||||||
|
m_updatePending = true;
|
||||||
|
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Active window
|
||||||
|
connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged, Qt::QueuedConnection);
|
||||||
|
connect(KWindowSystem::self(), static_cast<void (KWindowSystem::*)(WId)>(&KWindowSystem::windowChanged), this, &AppMenuModel::onActiveWindowChanged, Qt::QueuedConnection);
|
||||||
|
onActiveWindowChanged();
|
||||||
|
|
||||||
|
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
|
||||||
|
// if our current DBus connection gets lost, close the menu
|
||||||
|
// we'll select the new menu when the focus changes
|
||||||
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) {
|
||||||
|
if (serviceName == m_serviceName) {
|
||||||
|
setMenuAvailable(false);
|
||||||
|
emit modelNeedsUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AppMenuModel::~AppMenuModel() = default;
|
||||||
|
|
||||||
|
bool AppMenuModel::menuAvailable() const
|
||||||
|
{
|
||||||
|
return m_menuAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::setMenuAvailable(bool set)
|
||||||
|
{
|
||||||
|
if (m_menuAvailable != set) {
|
||||||
|
m_menuAvailable = set;
|
||||||
|
setVisible(true);
|
||||||
|
emit menuAvailableChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AppMenuModel::visible() const
|
||||||
|
{
|
||||||
|
return m_visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::setVisible(bool visible)
|
||||||
|
{
|
||||||
|
if (m_visible != visible) {
|
||||||
|
m_visible = visible;
|
||||||
|
emit visibleChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect AppMenuModel::screenGeometry() const
|
||||||
|
{
|
||||||
|
// return m_tasksModel->screenGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::setScreenGeometry(QRect geometry)
|
||||||
|
{
|
||||||
|
// m_tasksModel->setScreenGeometry(geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
int AppMenuModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
if (!m_menuAvailable || !m_menu) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_menu->actions().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::update()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
endResetModel();
|
||||||
|
m_updatePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::onActiveWindowChanged()
|
||||||
|
{
|
||||||
|
KWindowInfo info(KWindowSystem::activeWindow(),
|
||||||
|
NET::WMState | NET::WMVisibleName,
|
||||||
|
NET::WM2AppMenuObjectPath | NET::WM2AppMenuServiceName);
|
||||||
|
const QString objectPath = info.applicationMenuObjectPath();
|
||||||
|
const QString serviceName = info.applicationMenuServiceName();
|
||||||
|
|
||||||
|
if (!objectPath.isEmpty() && !serviceName.isEmpty()) {
|
||||||
|
setMenuAvailable(true);
|
||||||
|
updateApplicationMenu(serviceName, objectPath);
|
||||||
|
setVisible(true);
|
||||||
|
emit modelNeedsUpdate();
|
||||||
|
} else {
|
||||||
|
setMenuAvailable(false);
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> AppMenuModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roleNames;
|
||||||
|
roleNames[MenuRole] = QByteArrayLiteral("activeMenu");
|
||||||
|
roleNames[ActionRole] = QByteArrayLiteral("activeActions");
|
||||||
|
return roleNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant AppMenuModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
const int row = index.row();
|
||||||
|
if (row < 0 || !m_menuAvailable || !m_menu) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto actions = m_menu->actions();
|
||||||
|
if (row >= actions.count()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == MenuRole) { // TODO this should be Qt::DisplayRole
|
||||||
|
return actions.at(row)->text();
|
||||||
|
} else if (role == ActionRole) {
|
||||||
|
return QVariant::fromValue((void *)actions.at(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath)
|
||||||
|
{
|
||||||
|
if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) {
|
||||||
|
if (m_importer) {
|
||||||
|
QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_serviceName = serviceName;
|
||||||
|
m_serviceWatcher->setWatchedServices(QStringList({m_serviceName}));
|
||||||
|
|
||||||
|
m_menuObjectPath = menuObjectPath;
|
||||||
|
|
||||||
|
if (m_importer) {
|
||||||
|
m_importer->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_importer = new CDBusMenuImporter(serviceName, menuObjectPath, this);
|
||||||
|
QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection);
|
||||||
|
|
||||||
|
connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) {
|
||||||
|
m_menu = m_importer->menu();
|
||||||
|
if (m_menu.isNull() || menu != m_menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cache first layer of sub menus, which we'll be popping up
|
||||||
|
const auto actions = m_menu->actions();
|
||||||
|
for (QAction *a : actions) {
|
||||||
|
// signal dataChanged when the action changes
|
||||||
|
connect(a, &QAction::changed, this, [this, a] {
|
||||||
|
if (m_menuAvailable && m_menu) {
|
||||||
|
const int actionIdx = m_menu->actions().indexOf(a);
|
||||||
|
if (actionIdx > -1) {
|
||||||
|
const QModelIndex modelIdx = index(actionIdx, 0);
|
||||||
|
emit dataChanged(modelIdx, modelIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate);
|
||||||
|
|
||||||
|
if (a->menu()) {
|
||||||
|
m_importer->updateMenu(a->menu());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenuAvailable(true);
|
||||||
|
emit modelNeedsUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) {
|
||||||
|
// TODO submenus
|
||||||
|
if (!m_menuAvailable || !m_menu) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto actions = m_menu->actions();
|
||||||
|
auto it = std::find(actions.begin(), actions.end(), action);
|
||||||
|
if (it != actions.end()) {
|
||||||
|
Q_EMIT requestActivateIndex(it - actions.begin());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
/******************************************************************
|
||||||
|
* Copyright 2021 Reion Wong <aj@cutefishos.com>
|
||||||
|
* Copyright 2016 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
******************************************************************/
|
||||||
|
|
||||||
|
#ifndef APPMENUMODEL_H
|
||||||
|
#define APPMENUMODEL_H
|
||||||
|
|
||||||
|
#include <KWindowSystem>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QRect>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class QMenu;
|
||||||
|
class QModelIndex;
|
||||||
|
class QDBusServiceWatcher;
|
||||||
|
class CDBusMenuImporter;
|
||||||
|
|
||||||
|
class AppMenuModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged)
|
||||||
|
Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged)
|
||||||
|
|
||||||
|
Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AppMenuModel(QObject *parent = nullptr);
|
||||||
|
~AppMenuModel() override;
|
||||||
|
|
||||||
|
enum AppMenuRole {
|
||||||
|
MenuRole = Qt::UserRole + 1, // TODO this should be Qt::DisplayRole
|
||||||
|
ActionRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath);
|
||||||
|
|
||||||
|
bool menuAvailable() const;
|
||||||
|
void setMenuAvailable(bool set);
|
||||||
|
|
||||||
|
bool visible() const;
|
||||||
|
|
||||||
|
QRect screenGeometry() const;
|
||||||
|
void setScreenGeometry(QRect geometry);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void requestActivateIndex(int index);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onActiveWindowChanged();
|
||||||
|
void setVisible(bool visible);
|
||||||
|
void update();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void menuAvailableChanged();
|
||||||
|
void modelNeedsUpdate();
|
||||||
|
void screenGeometryChanged();
|
||||||
|
void visibleChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_menuAvailable;
|
||||||
|
bool m_updatePending = false;
|
||||||
|
bool m_visible = true;
|
||||||
|
|
||||||
|
//! current active window used
|
||||||
|
WId m_currentWindowId = 0;
|
||||||
|
//! window that its menu initialization may be delayed
|
||||||
|
WId m_delayedMenuWindowId = 0;
|
||||||
|
|
||||||
|
QPointer<QMenu> m_menu;
|
||||||
|
|
||||||
|
QDBusServiceWatcher *m_serviceWatcher;
|
||||||
|
QString m_serviceName;
|
||||||
|
QString m_menuObjectPath;
|
||||||
|
|
||||||
|
QPointer<CDBusMenuImporter> m_importer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
<node xmlns:dox="http://www.ayatana.org/dbus/dox.dtd">
|
||||||
|
<dox:d><![CDATA[
|
||||||
|
@mainpage
|
||||||
|
|
||||||
|
An interface to register menus that are associated with a window in an application. The
|
||||||
|
main interface is docuemented here: @ref com::canonical::AppMenu::Registrar.
|
||||||
|
|
||||||
|
The actual menus are transported using the dbusmenu protocol which is available
|
||||||
|
here: @ref com::canonical::dbusmenu.
|
||||||
|
]]></dox:d>
|
||||||
|
<interface name="com.canonical.AppMenu.Registrar" xmlns:dox="http://www.ayatana.org/dbus/dox.dtd">
|
||||||
|
<dox:d>
|
||||||
|
An interface to register a menu from an application's window to be displayed in another
|
||||||
|
window. This manages that association between XWindow Window IDs and the dbus
|
||||||
|
address and object that provides the menu using the dbusmenu dbus interface.
|
||||||
|
</dox:d>
|
||||||
|
<method name="RegisterWindow">
|
||||||
|
<dox:d><![CDATA[
|
||||||
|
Associates a dbusmenu with a window
|
||||||
|
|
||||||
|
/note this method assumes that the connection from the caller is the DBus connection
|
||||||
|
to use for the object. Applications that use multiple DBus connections will need to
|
||||||
|
ensure this method is called with the same connection that implmenets the object.
|
||||||
|
]]></dox:d>
|
||||||
|
<arg name="windowId" type="u" direction="in">
|
||||||
|
<dox:d>The XWindow ID of the window</dox:d>
|
||||||
|
</arg>
|
||||||
|
<arg name="menuObjectPath" type="o" direction="in">
|
||||||
|
<dox:d>The object on the dbus interface implementing the dbusmenu interface</dox:d>
|
||||||
|
</arg>
|
||||||
|
</method>
|
||||||
|
<method name="UnregisterWindow">
|
||||||
|
<dox:d>
|
||||||
|
A method to allow removing a window from the database. Windows will also be removed
|
||||||
|
when the client drops off DBus so this is not required. It is polite though. And
|
||||||
|
important for testing.
|
||||||
|
</dox:d>
|
||||||
|
<arg name="windowId" type="u" direction="in">
|
||||||
|
<dox:d>The XWindow ID of the window</dox:d>
|
||||||
|
</arg>
|
||||||
|
</method>
|
||||||
|
<method name="GetMenuForWindow">
|
||||||
|
<dox:d>Gets the registered menu for a given window ID.</dox:d>
|
||||||
|
<arg name="windowId" type="u" direction="in">
|
||||||
|
<dox:d>The XWindow ID of the window to get</dox:d>
|
||||||
|
</arg>
|
||||||
|
<arg name="service" type="s" direction="out">
|
||||||
|
<dox:d>The address of the connection on DBus (e.g. :1.23 or org.example.service)</dox:d>
|
||||||
|
</arg>
|
||||||
|
<arg name="menuObjectPath" type="o" direction="out">
|
||||||
|
<dox:d>The path to the object which implements the com.canonical.dbusmenu interface.</dox:d>
|
||||||
|
</arg>
|
||||||
|
</method>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KDBUSIMPORTER_H
|
||||||
|
#define KDBUSIMPORTER_H
|
||||||
|
|
||||||
|
#include "../libdbusmenuqt/dbusmenuimporter.h"
|
||||||
|
|
||||||
|
#include "verticalmenu.h"
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
class KDBusMenuImporter : public DBusMenuImporter
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KDBusMenuImporter(const QString &service, const QString &path, QObject *parent)
|
||||||
|
: DBusMenuImporter(service, path, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QIcon iconForName(const QString &name) override
|
||||||
|
{
|
||||||
|
return QIcon::fromTheme(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu *createMenu(QWidget *parent) override
|
||||||
|
{
|
||||||
|
return new VerticalMenu(parent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KDBUSIMPORTER_H
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
Copyright (c) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "menuimporter.h"
|
||||||
|
#include "../libdbusmenuqt/dbusmenutypes_p.h"
|
||||||
|
#include "menuimporteradaptor.h"
|
||||||
|
|
||||||
|
#include <QDBusMessage>
|
||||||
|
#include <QDBusServiceWatcher>
|
||||||
|
|
||||||
|
#include <KWindowInfo>
|
||||||
|
#include <KWindowSystem>
|
||||||
|
|
||||||
|
static const char *DBUS_SERVICE = "com.canonical.AppMenu.Registrar";
|
||||||
|
static const char *DBUS_OBJECT_PATH = "/com/canonical/AppMenu/Registrar";
|
||||||
|
|
||||||
|
MenuImporter::MenuImporter(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_serviceWatcher(new QDBusServiceWatcher(this))
|
||||||
|
{
|
||||||
|
qDBusRegisterMetaType<DBusMenuLayoutItem>();
|
||||||
|
m_serviceWatcher->setConnection(QDBusConnection::sessionBus());
|
||||||
|
m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
|
||||||
|
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &MenuImporter::slotServiceUnregistered);
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuImporter::~MenuImporter()
|
||||||
|
{
|
||||||
|
QDBusConnection::sessionBus().unregisterService(DBUS_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MenuImporter::connectToBus()
|
||||||
|
{
|
||||||
|
if (!QDBusConnection::sessionBus().registerService(DBUS_SERVICE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
new MenuImporterAdaptor(this);
|
||||||
|
QDBusConnection::sessionBus().registerObject(DBUS_OBJECT_PATH, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuImporter::RegisterWindow(WId id, const QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
KWindowInfo info(id, NET::WMWindowType, NET::WM2WindowClass);
|
||||||
|
NET::WindowTypes mask = NET::AllTypesMask;
|
||||||
|
auto type = info.windowType(mask);
|
||||||
|
|
||||||
|
// Menu can try to register, right click in gimp for example
|
||||||
|
if (type != NET::Unknown && (type & (NET::Menu | NET::DropdownMenu | NET::PopupMenu))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.path().isEmpty()) // prevent bad dbusmenu usage
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString service = message().service();
|
||||||
|
|
||||||
|
QString classClass = info.windowClassClass();
|
||||||
|
m_windowClasses.insert(id, classClass);
|
||||||
|
m_menuServices.insert(id, service);
|
||||||
|
m_menuPaths.insert(id, path);
|
||||||
|
|
||||||
|
if (!m_serviceWatcher->watchedServices().contains(service)) {
|
||||||
|
m_serviceWatcher->addWatchedService(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit WindowRegistered(id, service, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuImporter::UnregisterWindow(WId id)
|
||||||
|
{
|
||||||
|
m_menuServices.remove(id);
|
||||||
|
m_menuPaths.remove(id);
|
||||||
|
m_windowClasses.remove(id);
|
||||||
|
|
||||||
|
emit WindowUnregistered(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString MenuImporter::GetMenuForWindow(WId id, QDBusObjectPath &path)
|
||||||
|
{
|
||||||
|
path = m_menuPaths.value(id);
|
||||||
|
return m_menuServices.value(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuImporter::slotServiceUnregistered(const QString &service)
|
||||||
|
{
|
||||||
|
WId id = m_menuServices.key(service);
|
||||||
|
m_menuServices.remove(id);
|
||||||
|
m_menuPaths.remove(id);
|
||||||
|
m_windowClasses.remove(id);
|
||||||
|
emit WindowUnregistered(id);
|
||||||
|
m_serviceWatcher->removeWatchedService(service);
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
Copyright (c) 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MENUIMPORTER_H
|
||||||
|
#define MENUIMPORTER_H
|
||||||
|
|
||||||
|
// Qt
|
||||||
|
#include <QDBusArgument>
|
||||||
|
#include <QDBusContext>
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QWidget> // For WId
|
||||||
|
|
||||||
|
class QDBusObjectPath;
|
||||||
|
class QDBusServiceWatcher;
|
||||||
|
|
||||||
|
class MenuImporter : public QObject, protected QDBusContext
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MenuImporter(QObject *);
|
||||||
|
~MenuImporter() override;
|
||||||
|
|
||||||
|
bool connectToBus();
|
||||||
|
|
||||||
|
bool serviceExist(WId id)
|
||||||
|
{
|
||||||
|
return m_menuServices.contains(id);
|
||||||
|
}
|
||||||
|
QString serviceForWindow(WId id)
|
||||||
|
{
|
||||||
|
return m_menuServices.value(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pathExist(WId id)
|
||||||
|
{
|
||||||
|
return m_menuPaths.contains(id);
|
||||||
|
}
|
||||||
|
QString pathForWindow(WId id)
|
||||||
|
{
|
||||||
|
return m_menuPaths.value(id).path();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<WId> ids()
|
||||||
|
{
|
||||||
|
return m_menuServices.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void WindowRegistered(WId id, const QString &service, const QDBusObjectPath &);
|
||||||
|
void WindowUnregistered(WId id);
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
Q_NOREPLY void RegisterWindow(WId id, const QDBusObjectPath &path);
|
||||||
|
Q_NOREPLY void UnregisterWindow(WId id);
|
||||||
|
QString GetMenuForWindow(WId id, QDBusObjectPath &path);
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void slotServiceUnregistered(const QString &service);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QDBusServiceWatcher *m_serviceWatcher;
|
||||||
|
QHash<WId, QString> m_menuServices;
|
||||||
|
QHash<WId, QDBusObjectPath> m_menuPaths;
|
||||||
|
QHash<WId, QString> m_windowClasses;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* MENUIMPORTER_H */
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
<node>
|
||||||
|
<interface name="org.kde.cappmenu">
|
||||||
|
<method name="showMenu">
|
||||||
|
<arg name="x" type="i" direction="in"/>
|
||||||
|
<arg name="y" type="i" direction="in"/>
|
||||||
|
<arg name="service" type="s" direction="in"/>
|
||||||
|
<arg name="objectPath" type="o" direction="in"/>
|
||||||
|
<arg name="actionId" type="i" direction="in"/>
|
||||||
|
</method>
|
||||||
|
<method name="reconfigure">
|
||||||
|
</method>
|
||||||
|
<signal name="reconfigured" />
|
||||||
|
<signal name="showRequest">
|
||||||
|
<arg name="service" type="s" direction="out"/>
|
||||||
|
<arg name="objectPath" type="o" direction="out"/>
|
||||||
|
<arg name="actionId" type="i" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="menuShown">
|
||||||
|
<arg name="service" type="s" direction="out"/>
|
||||||
|
<arg name="objectPath" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="menuHidden">
|
||||||
|
<arg name="service" type="s" direction="out"/>
|
||||||
|
<arg name="objectPath" type="o" direction="out"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "verticalmenu.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QKeyEvent>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
|
||||||
|
VerticalMenu::VerticalMenu(QWidget *parent)
|
||||||
|
: QMenu(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticalMenu::~VerticalMenu()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu *VerticalMenu::leafMenu()
|
||||||
|
{
|
||||||
|
QMenu *leaf = this;
|
||||||
|
while (true) {
|
||||||
|
QAction *act = leaf->activeAction();
|
||||||
|
if (act && act->menu() && act->menu()->isVisible()) {
|
||||||
|
leaf = act->menu();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return leaf == this ? nullptr : leaf;
|
||||||
|
}
|
||||||
|
return nullptr; // make gcc happy
|
||||||
|
}
|
||||||
|
|
||||||
|
void VerticalMenu::paintEvent(QPaintEvent *pe)
|
||||||
|
{
|
||||||
|
QMenu::paintEvent(pe);
|
||||||
|
if (QWidget::mouseGrabber() == this)
|
||||||
|
return;
|
||||||
|
if (QWidget::mouseGrabber())
|
||||||
|
QWidget::mouseGrabber()->releaseMouse();
|
||||||
|
grabMouse();
|
||||||
|
grabKeyboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FORWARD(_EVENT_, _TYPE_) \
|
||||||
|
void VerticalMenu::_EVENT_##Event(Q##_TYPE_##Event *e) \
|
||||||
|
{ \
|
||||||
|
if (QMenu *leaf = leafMenu()) \
|
||||||
|
QCoreApplication::sendEvent(leaf, e); \
|
||||||
|
else \
|
||||||
|
QMenu::_EVENT_##Event(e); \
|
||||||
|
}
|
||||||
|
|
||||||
|
FORWARD(keyPress, Key)
|
||||||
|
FORWARD(keyRelease, Key)
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (c) 2011 Lionel Chauvin <megabigbug@yahoo.fr>
|
||||||
|
Copyright (c) 2011,2012 Cédric Bellegarde <gnumdk@gmail.com>
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VERTICALMENU_H
|
||||||
|
#define VERTICALMENU_H
|
||||||
|
|
||||||
|
#include <QDBusObjectPath>
|
||||||
|
#include <QMenu>
|
||||||
|
|
||||||
|
class VerticalMenu : public QMenu
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit VerticalMenu(QWidget *parent = nullptr);
|
||||||
|
~VerticalMenu() override;
|
||||||
|
|
||||||
|
QString serviceName() const
|
||||||
|
{
|
||||||
|
return m_serviceName;
|
||||||
|
}
|
||||||
|
void setServiceName(const QString &serviceName)
|
||||||
|
{
|
||||||
|
m_serviceName = serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusObjectPath menuObjectPath() const
|
||||||
|
{
|
||||||
|
return m_menuObjectPath;
|
||||||
|
}
|
||||||
|
void setMenuObjectPath(const QDBusObjectPath &menuObjectPath)
|
||||||
|
{
|
||||||
|
m_menuObjectPath = menuObjectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void keyPressEvent(QKeyEvent *) override;
|
||||||
|
void keyReleaseEvent(QKeyEvent *) override;
|
||||||
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMenu *leafMenu();
|
||||||
|
|
||||||
|
QString m_serviceName;
|
||||||
|
QDBusObjectPath m_menuObjectPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VERTICALMENU_H
|
||||||
Loading…
Reference in New Issue