mirror of https://github.com/cutefishos/statusbar
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
7.4 KiB
C++
250 lines
7.4 KiB
C++
/******************************************************************
|
|
* 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());
|
|
}
|
|
});
|
|
}
|