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.
348 lines
12 KiB
C++
348 lines
12 KiB
C++
/***************************************************************************
|
|
* *
|
|
* Copyright (C) 2021 Reion Wong <aj@cutefishos.com> *
|
|
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
|
|
* Copyright (C) 2009 Matthieu Gallien <matthieu_gallien@yahoo.fr> *
|
|
* *
|
|
* 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) any later version. *
|
|
* *
|
|
* 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, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
|
|
***************************************************************************/
|
|
|
|
#include "statusnotifieritemsource.h"
|
|
#include "systemtraytypes.h"
|
|
|
|
#include "../libdbusmenuqt/dbusmenuimporter.h"
|
|
|
|
#include <QDebug>
|
|
#include <netinet/in.h>
|
|
|
|
class TrayMenuImporter : public DBusMenuImporter
|
|
{
|
|
public:
|
|
using DBusMenuImporter::DBusMenuImporter;
|
|
|
|
protected:
|
|
QIcon iconForName(const QString & name) override {
|
|
return QIcon::fromTheme(name);
|
|
}
|
|
};
|
|
|
|
StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent)
|
|
: QObject(parent)
|
|
, m_menuImporter(nullptr)
|
|
, m_refreshing(false)
|
|
, m_needsReRefreshing(false)
|
|
, m_titleUpdate(true)
|
|
, m_iconUpdate(true)
|
|
, m_tooltipUpdate(true)
|
|
, m_statusUpdate(true)
|
|
, m_id(notifierItemId)
|
|
{
|
|
setObjectName(notifierItemId);
|
|
|
|
qDBusRegisterMetaType<KDbusImageStruct>();
|
|
qDBusRegisterMetaType<KDbusImageVector>();
|
|
qDBusRegisterMetaType<KDbusToolTipStruct>();
|
|
|
|
m_name = notifierItemId;
|
|
|
|
int slash = notifierItemId.indexOf('/');
|
|
if (slash == -1) {
|
|
qWarning() << "Invalid notifierItemId:" << notifierItemId;
|
|
m_valid = false;
|
|
m_statusNotifierItemInterface = nullptr;
|
|
return;
|
|
}
|
|
|
|
QString service = notifierItemId.left(slash);
|
|
QString path = notifierItemId.mid(slash);
|
|
|
|
m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this);
|
|
|
|
m_refreshTimer.setSingleShot(true);
|
|
m_refreshTimer.setInterval(10);
|
|
connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh);
|
|
|
|
m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid();
|
|
|
|
if (m_valid) {
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle);
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons);
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons);
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons);
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip);
|
|
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus);
|
|
refresh();
|
|
}
|
|
}
|
|
|
|
StatusNotifierItemSource::~StatusNotifierItemSource()
|
|
{
|
|
if (m_statusNotifierItemInterface)
|
|
delete m_statusNotifierItemInterface;
|
|
}
|
|
|
|
QString StatusNotifierItemSource::id() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
QString StatusNotifierItemSource::title() const
|
|
{
|
|
return m_title;
|
|
}
|
|
|
|
QString StatusNotifierItemSource::tooltip() const
|
|
{
|
|
return m_tooltip;
|
|
}
|
|
|
|
QString StatusNotifierItemSource::subtitle() const
|
|
{
|
|
return m_subTitle;
|
|
}
|
|
|
|
QString StatusNotifierItemSource::iconName() const
|
|
{
|
|
return m_iconName;
|
|
}
|
|
|
|
QIcon StatusNotifierItemSource::icon() const
|
|
{
|
|
return m_icon;
|
|
}
|
|
|
|
void StatusNotifierItemSource::activate(int x, int y)
|
|
{
|
|
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
|
|
m_statusNotifierItemInterface->path(),
|
|
m_statusNotifierItemInterface->interface(),
|
|
QStringLiteral("Activate"));
|
|
|
|
message << x << y;
|
|
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::activateCallback);
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::secondaryActivate(int x, int y)
|
|
{
|
|
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
|
|
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y);
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::scroll(int delta, const QString &direction)
|
|
{
|
|
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
|
|
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction);
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::contextMenu(int x, int y)
|
|
{
|
|
if (m_menuImporter) {
|
|
// Popup menu
|
|
if (m_menuImporter->menu()) {
|
|
m_menuImporter->updateMenu();
|
|
m_menuImporter->menu()->popup(QPoint(x, y));
|
|
}
|
|
} else {
|
|
qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()";
|
|
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
|
|
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::contextMenuReady()
|
|
{
|
|
if (m_menuImporter && m_menuImporter->menu()) {
|
|
emit contextMenuReady(m_menuImporter->menu());
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::refreshTitle()
|
|
{
|
|
m_titleUpdate = true;
|
|
refresh();
|
|
}
|
|
|
|
void StatusNotifierItemSource::refreshIcons()
|
|
{
|
|
m_iconUpdate = true;
|
|
refresh();
|
|
}
|
|
|
|
void StatusNotifierItemSource::refreshToolTip()
|
|
{
|
|
m_tooltipUpdate = true;
|
|
refresh();
|
|
}
|
|
|
|
void StatusNotifierItemSource::refresh()
|
|
{
|
|
if (!m_refreshTimer.isActive()) {
|
|
m_refreshTimer.start();
|
|
}
|
|
}
|
|
|
|
void StatusNotifierItemSource::performRefresh()
|
|
{
|
|
if (m_refreshing) {
|
|
m_needsReRefreshing = true;
|
|
return;
|
|
}
|
|
|
|
m_refreshing = true;
|
|
QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
|
|
m_statusNotifierItemInterface->path(),
|
|
QStringLiteral("org.freedesktop.DBus.Properties"),
|
|
QStringLiteral("GetAll"));
|
|
|
|
message << m_statusNotifierItemInterface->interface();
|
|
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback);
|
|
}
|
|
|
|
void StatusNotifierItemSource::syncStatus(QString)
|
|
{
|
|
|
|
}
|
|
|
|
void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call)
|
|
{
|
|
m_refreshing = false;
|
|
if (m_needsReRefreshing) {
|
|
m_needsReRefreshing = false;
|
|
performRefresh();
|
|
call->deleteLater();
|
|
return;
|
|
}
|
|
|
|
QDBusPendingReply<QVariantMap> reply = *call;
|
|
if (reply.isError()) {
|
|
m_valid = false;
|
|
} else {
|
|
QVariantMap properties = reply.argumentAt<0>();
|
|
QString path = properties[QStringLiteral("IconThemePath")].toString();
|
|
|
|
m_title = properties[QStringLiteral("Title")].toString();
|
|
m_iconName = properties[QStringLiteral("IconName")].toString();
|
|
|
|
// Reion: For icon theme path
|
|
QString iconThemePath = properties[QStringLiteral("IconThemePath")].toString();
|
|
if (!iconThemePath.isEmpty()) {
|
|
QIcon::setFallbackSearchPaths(QStringList() << iconThemePath);
|
|
}
|
|
|
|
// ToolTip
|
|
KDbusToolTipStruct toolTip;
|
|
properties[QStringLiteral("ToolTip")].value<QDBusArgument>() >> toolTip;
|
|
m_tooltip = toolTip.title;
|
|
|
|
// Use ID as an alternative :)
|
|
if (m_tooltip.isEmpty() && m_title.isEmpty()) {
|
|
m_tooltip = properties[QStringLiteral("Id")].toString();
|
|
}
|
|
|
|
// Icon
|
|
KDbusImageVector image;
|
|
properties[QStringLiteral("IconPixmap")].value<QDBusArgument>() >> image;
|
|
if (!image.isEmpty()) {
|
|
m_icon = imageVectorToPixmap(image);
|
|
}
|
|
|
|
// Menu
|
|
if (!m_menuImporter) {
|
|
QString menuObjectPath = properties[QStringLiteral("Menu")].value<QDBusObjectPath>().path();
|
|
if (!menuObjectPath.isEmpty()) {
|
|
if (menuObjectPath.startsWith(QLatin1String("/NO_DBUSMENU"))) {
|
|
// This is a hack to make it possible to disable DBusMenu in an
|
|
// application. The string "/NO_DBUSMENU" must be the same as in
|
|
// KStatusNotifierItem::setContextMenu().
|
|
qWarning() << "DBusMenu disabled for this application";
|
|
} else {
|
|
m_menuImporter = new TrayMenuImporter(m_statusNotifierItemInterface->service(),
|
|
menuObjectPath, this);
|
|
connect(m_menuImporter, &TrayMenuImporter::menuUpdated, this, [this](QMenu *menu) {
|
|
if (menu == m_menuImporter->menu()) {
|
|
contextMenuReady();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// qDebug() << newTitle << newIconName << newToolTip << image.isEmpty();
|
|
|
|
emit updated(this);
|
|
}
|
|
|
|
call->deleteLater();
|
|
}
|
|
|
|
void StatusNotifierItemSource::activateCallback(QDBusPendingCallWatcher *call)
|
|
{
|
|
QDBusPendingReply<void> reply = *call;
|
|
emit activateResult(!reply.isError());
|
|
call->deleteLater();
|
|
}
|
|
|
|
QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const
|
|
{
|
|
// swap from network byte order if we are little endian
|
|
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
|
|
uint *uintBuf = (uint *)image.data.data();
|
|
for (uint i = 0; i < image.data.size() / sizeof(uint); ++i) {
|
|
*uintBuf = ntohl(*uintBuf);
|
|
++uintBuf;
|
|
}
|
|
}
|
|
if (image.width == 0 || image.height == 0) {
|
|
return QPixmap();
|
|
}
|
|
|
|
// avoid a deep copy of the image data
|
|
// we need to keep a reference to the image.data alive for the lifespan of the image, even if the image is copied
|
|
// we create a new QByteArray with a shallow copy of the original data on the heap, then delete this in the QImage cleanup
|
|
auto dataRef = new QByteArray(image.data);
|
|
|
|
QImage iconImage(
|
|
reinterpret_cast<const uchar *>(dataRef->data()),
|
|
image.width,
|
|
image.height,
|
|
QImage::Format_ARGB32,
|
|
[](void *ptr) {
|
|
delete static_cast<QByteArray *>(ptr);
|
|
},
|
|
dataRef);
|
|
return QPixmap::fromImage(iconImage);
|
|
}
|
|
|
|
QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const
|
|
{
|
|
QIcon icon;
|
|
|
|
for (int i = 0; i < vector.size(); ++i) {
|
|
icon.addPixmap(KDbusImageStructToPixmap(vector[i]));
|
|
}
|
|
|
|
return icon;
|
|
}
|