theshell/shell/systrayicons.cpp
2019-01-01 21:52:05 +11:00

233 lines
9.9 KiB
C++

/****************************************
*
* theShell - Desktop Environment
* Copyright (C) 2019 Victor Tran
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* *************************************/
#include "systrayicons.h"
#define None 0L
extern NativeEventFilter* NativeFilter;
SysTrayIcons::SysTrayIcons(QWidget *parent) : QFrame(parent)
{
//Prepare a layout for the system tray
QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight);
layout->setSpacing(6);
this->setLayout(layout);
//Connect the signal to dock a system tray
connect(NativeFilter, SIGNAL(SysTrayEvent(long,long,long,long)), this, SLOT(SysTrayEvent(long,long,long,long)));
//Get the correct manager selection
unsigned long selection = 0;
QString atomName = QString("_NET_SYSTEM_TRAY_S").append(QString::number(XScreenNumberOfScreen(XDefaultScreenOfDisplay(QX11Info::display()))));
selection = XInternAtom(QX11Info::display(), atomName.toLocal8Bit(), False);
if (selection == None) { //Manager selection wasn't found
QLabel* errorLabel = new QLabel();
errorLabel->setText(tr("System Tray Unavailable."));
this->layout()->addWidget(errorLabel);
} else {
if (XGetSelectionOwner(QX11Info::display(), selection) == None) {
XSetSelectionOwner(QX11Info::display(), selection, this->winId(), CurrentTime);
if (XGetSelectionOwner(QX11Info::display(), selection) != this->winId()) {
QLabel* errorLabel = new QLabel();
errorLabel->setText(tr("System Tray Unavailable."));
this->layout()->addWidget(errorLabel);
} else { //System tray available. Send a ClientMessage event to tell everyone that a system tray is available.
XEvent event;
event.xclient.type = ClientMessage;
event.xclient.message_type = XInternAtom(QX11Info::display(), "MANAGER", False);
event.xclient.format = 32;
event.xclient.data.l[0] = CurrentTime;
event.xclient.data.l[1] = selection;
event.xclient.data.l[2] = this->winId();
int retval = XSendEvent(QX11Info::display(), DefaultRootWindow(QX11Info::display()), False, StructureNotifyMask, &event);
qDebug() << retval;
}
} else { //Systray is already handled.
QLabel* errorLabel = new QLabel();
errorLabel->setText(tr("System Tray Unavailable."));
this->layout()->addWidget(errorLabel);
}
}
//Register a new DBus service
QString service = "org.freedesktop.StatusNotifierHost-" + QString::number(QApplication::applicationPid());
QDBusConnection::sessionBus().registerService(service);
//Connect to SNI signals
QDBusConnection::sessionBus().connect("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "StatusNotifierItemRegistered", this, SLOT(SniItemRegistered(QString)));
QDBusConnection::sessionBus().connect("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "StatusNotifierItemUnregistered", this, SLOT(SniItemUnregistered(QString)));
//Tell SNI about a new system tray host
QDBusMessage message = QDBusMessage::createMethodCall("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "RegisterStatusNotifierHost");
QVariantList messageArgs;
messageArgs.append(service);
message.setArguments(messageArgs);
QDBusConnection::sessionBus().call(message);
//Get all current SNI items
QDBusInterface interface("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher");
for (QString service : interface.property("RegisteredStatusNotifierItems").toStringList()) {
SniItemRegistered(service);
}
}
void SysTrayIcons::SysTrayEvent(long opcode, long data2, long data3, long data4) {
Q_UNUSED(data3)
Q_UNUSED(data4)
if (opcode == SYSTEM_TRAY_REQUEST_DOCK) { //Check that the system tray wants to be docked
//Create a XEmbed window for the system tray and dock it in the layout
QWindow* window = QWindow::fromWinId(data2);
window->resize(16, 16);
QWidget* widget = QWidget::createWindowContainer(window);
widget->setFixedSize(16, 16);
this->layout()->addWidget(widget);
connect(window, &QWindow::destroyed, [=]() {
widget->deleteLater();
});
connect(window, &QWindow::visibleChanged, [=](bool visible) {
widget->setVisible(visible);
});
}
}
void SysTrayIcons::SniItemRegistered(QString service) {
if (!availableSniServices.contains(service)) {
availableSniServices.append(service);
SniIcon* icon = new SniIcon(service);
this->layout()->addWidget(icon);
}
}
void SysTrayIcons::SniItemUnregistered(QString service) {
if (availableSniServices.contains(service)) {
availableSniServices.removeAll(service);
}
}
SniIcon::SniIcon(QString service, QWidget *parent) : QLabel(parent) {
this->service = service;
QStringList pathParts = service.split("/");
service = pathParts.first();
pathParts.removeFirst();
QString path = pathParts.join("/");
path.insert(0, "/");
interface = new QDBusInterface(service, path, "org.kde.StatusNotifierItem");
this->title = interface->property("Title").toString();
this->setContextMenuPolicy(Qt::CustomContextMenu);
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewTitle", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewIcon", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewAttentionIcon", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewOverlayIcon", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewToolTip", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect(service, path, "org.kde.StatusNotifierItem", "NewStatus", this, SLOT(ReloadIcon()));
QDBusConnection::sessionBus().connect("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "StatusNotifierItemUnregistered", this, SLOT(SniItemUnregistered(QString)));
ReloadIcon();
}
void SniIcon::SniItemUnregistered(QString service) {
if (service == this->service) {
this->deleteLater();
}
}
void SniIcon::ReloadIcon() {
if (this->title != "discord" && this->title != "discord-canary") {
if (interface->property("IconName").toString() != "") {
this->setPixmap(QIcon::fromTheme(interface->property("IconName").toString(), QIcon::fromTheme("dialog-warning")).pixmap(24, 24));
} else {
//TODO: Load other image data
QDBusMessage message = QDBusMessage::createMethodCall(interface->service(), interface->path(), "org.freedesktop.DBus.Properties", "Get");
QList<QVariant> messageArguments;
messageArguments.append("org.kde.StatusNotifierItem");
messageArguments.append("IconPixmap");
message.setArguments(messageArguments);
QDBusReply<QDBusVariant> reply = QDBusConnection::sessionBus().call(message);
QDBusVariant pixmapsVar = reply.value();
QDBusArgument pixmaps = pixmapsVar.variant().value<QDBusArgument>();
QDBusVariant firstPixmapVar;
pixmaps >> firstPixmapVar;
pixmaps.endArray();
QDBusArgument firstPixmap = firstPixmapVar.variant().value<QDBusArgument>();
firstPixmap.beginArray();
int width, height;
QByteArray data;
firstPixmap >> width >> height >> data;
firstPixmap.endArray();
QImage image(width, height, QImage::Format_ARGB32);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width * 4; x = x + 4) {
//char dat = data.at(y * width + x);
unsigned char a, r, g, b;
b = data.at(y * width * 4 + x + 3);
g = data.at(y * width * 4 + x + 2);
r = data.at(y * width * 4 + x + 1);
a = data.at(y * width * 4 + x);
QColor col = QColor(r, g, b, a);
image.setPixelColor(x / 4, y, col);
}
}
this->setPixmap(QPixmap::fromImage(image.scaledToHeight(24, Qt::SmoothTransformation)));
}
this->setToolTip(interface->property("Title").toString());
}
}
void SniIcon::mouseReleaseEvent(QMouseEvent *event) {
QPoint pos = this->mapToGlobal(event->pos());
if (event->button() == Qt::LeftButton) {
interface->call("Activate", pos.x(), pos.y());
} else if (event->button() == Qt::RightButton) {
interface->call("ContextMenu", pos.x(), pos.y());
} else if (event->button() == Qt::MiddleButton) {
interface->call("SecondaryActivate", pos.x(), pos.y());
}
}
void SniIcon::wheelEvent(QWheelEvent *event) {
if (event->orientation() == Qt::Vertical) {
interface->call("Scroll", event->delta(), "vertical");
} else {
interface->call("Scroll", event->delta(), "horizontal");
}
}