You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lxqt-panel-packaging/plugin-tray/lxqttray.cpp

401 lines
11 KiB

/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXDE-Qt - a lightweight, Qt based, desktop toolset
* http://razor-qt.org
*
* Copyright: 2011 Razor team
* Authors:
* Alexander Sokoloff <sokoloff.a@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
/********************************************************************
Inspired by freedesktops tint2 ;)
*********************************************************************/
#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QX11Info>
#include "trayicon.h"
#include "../panel/ilxqtpanel.h"
#include <LXQt/GridLayout>
#include "lxqttray.h"
#include "xfitman.h"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xdamage.h>
#include <xcb/xcb.h>
#include <xcb/damage.h>
#undef Bool // defined as int in X11/Xlib.h
#include "../panel/ilxqtpanelplugin.h"
#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
#define SYSTEM_TRAY_REQUEST_DOCK 0
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_MAPPED (1 << 0)
/************************************************
************************************************/
LXQtTray::LXQtTray(ILXQtPanelPlugin *plugin, QWidget *parent):
QFrame(parent),
mValid(false),
mTrayId(0),
mDamageEvent(0),
mDamageError(0),
mIconSize(TRAY_ICON_SIZE_DEFAULT, TRAY_ICON_SIZE_DEFAULT),
mPlugin(plugin),
mDisplay(QX11Info::display())
{
mLayout = new LXQt::GridLayout(this);
realign();
_NET_SYSTEM_TRAY_OPCODE = XfitMan::atom("_NET_SYSTEM_TRAY_OPCODE");
// Init the selection later just to ensure that no signals are sent until
// after construction is done and the creating object has a chance to connect.
QTimer::singleShot(0, this, SLOT(startTray()));
}
/************************************************
************************************************/
LXQtTray::~LXQtTray()
{
stopTray();
}
/************************************************
************************************************/
bool LXQtTray::nativeEventFilter(const QByteArray &eventType, void *message, long *)
{
if (eventType != "xcb_generic_event_t")
return false;
xcb_generic_event_t* event = static_cast<xcb_generic_event_t *>(message);
TrayIcon* icon;
int event_type = event->response_type & ~0x80;
switch (event_type)
{
case ClientMessage:
clientMessageEvent(event);
break;
// case ConfigureNotify:
// icon = findIcon(event->xconfigure.window);
// if (icon)
// icon->configureEvent(&(event->xconfigure));
// break;
case DestroyNotify: {
unsigned long event_window;
event_window = reinterpret_cast<xcb_destroy_notify_event_t*>(event)->window;
icon = findIcon(event_window);
if (icon)
{
icon->windowDestroyed(event_window);
mIcons.removeAll(icon);
delete icon;
}
break;
}
default:
if (event_type == mDamageEvent + XDamageNotify)
{
xcb_damage_notify_event_t* dmg = reinterpret_cast<xcb_damage_notify_event_t*>(event);
icon = findIcon(dmg->drawable);
if (icon)
icon->update();
}
break;
}
return false;
}
/************************************************
************************************************/
void LXQtTray::realign()
{
mLayout->setEnabled(false);
ILXQtPanel *panel = mPlugin->panel();
if (panel->isHorizontal())
{
mLayout->setRowCount(panel->lineCount());
mLayout->setColumnCount(0);
}
else
{
mLayout->setColumnCount(panel->lineCount());
mLayout->setRowCount(0);
}
mLayout->setEnabled(true);
}
/************************************************
************************************************/
void LXQtTray::clientMessageEvent(xcb_generic_event_t *e)
{
unsigned long opcode;
unsigned long message_type;
Window id;
xcb_client_message_event_t* event = reinterpret_cast<xcb_client_message_event_t*>(e);
uint32_t* data32 = event->data.data32;
message_type = event->type;
opcode = data32[1];
if(message_type != _NET_SYSTEM_TRAY_OPCODE)
return;
switch (opcode)
{
case SYSTEM_TRAY_REQUEST_DOCK:
id = data32[2];
if (id)
addIcon(id);
break;
case SYSTEM_TRAY_BEGIN_MESSAGE:
case SYSTEM_TRAY_CANCEL_MESSAGE:
qDebug() << "we don't show balloon messages.";
break;
default:
// if (opcode == xfitMan().atom("_NET_SYSTEM_TRAY_MESSAGE_DATA"))
// qDebug() << "message from dockapp:" << e->data.b;
// else
// qDebug() << "SYSTEM_TRAY : unknown message type" << opcode;
break;
}
}
/************************************************
************************************************/
TrayIcon* LXQtTray::findIcon(Window id)
{
foreach(TrayIcon* icon, mIcons)
{
if (icon->iconId() == id || icon->windowId() == id)
return icon;
}
return 0;
}
/************************************************
************************************************/
void LXQtTray::setIconSize(QSize iconSize)
{
mIconSize = iconSize;
unsigned long size = qMin(mIconSize.width(), mIconSize.height());
XChangeProperty(mDisplay,
mTrayId,
XfitMan::atom("_NET_SYSTEM_TRAY_ICON_SIZE"),
XA_CARDINAL,
32,
PropModeReplace,
(unsigned char*)&size,
1);
}
/************************************************
************************************************/
VisualID LXQtTray::getVisual()
{
VisualID visualId = 0;
Display* dsp = mDisplay;
XVisualInfo templ;
templ.screen=QX11Info::appScreen();
templ.depth=32;
templ.c_class=TrueColor;
int nvi;
XVisualInfo* xvi = XGetVisualInfo(dsp, VisualScreenMask|VisualDepthMask|VisualClassMask, &templ, &nvi);
if (xvi)
{
int i;
XRenderPictFormat* format;
for (i = 0; i < nvi; i++)
{
format = XRenderFindVisualFormat(dsp, xvi[i].visual);
if (format &&
format->type == PictTypeDirect &&
format->direct.alphaMask)
{
visualId = xvi[i].visualid;
break;
}
}
XFree(xvi);
}
return visualId;
}
/************************************************
freedesktop systray specification
************************************************/
void LXQtTray::startTray()
{
Display* dsp = mDisplay;
Window root = QX11Info::appRootWindow();
QString s = QString("_NET_SYSTEM_TRAY_S%1").arg(DefaultScreen(dsp));
Atom _NET_SYSTEM_TRAY_S = XfitMan::atom(s.toLatin1());
if (XGetSelectionOwner(dsp, _NET_SYSTEM_TRAY_S) != None)
{
qWarning() << "Another systray is running";
mValid = false;
return;
}
// init systray protocol
mTrayId = XCreateSimpleWindow(dsp, root, -1, -1, 1, 1, 0, 0, 0);
XSetSelectionOwner(dsp, _NET_SYSTEM_TRAY_S, mTrayId, CurrentTime);
if (XGetSelectionOwner(dsp, _NET_SYSTEM_TRAY_S) != mTrayId)
{
qWarning() << "Can't get systray manager";
stopTray();
mValid = false;
return;
}
int orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
XChangeProperty(dsp,
mTrayId,
XfitMan::atom("_NET_SYSTEM_TRAY_ORIENTATION"),
XA_CARDINAL,
32,
PropModeReplace,
(unsigned char *) &orientation,
1);
// ** Visual ********************************
VisualID visualId = getVisual();
if (visualId)
{
XChangeProperty(mDisplay,
mTrayId,
XfitMan::atom("_NET_SYSTEM_TRAY_VISUAL"),
XA_VISUALID,
32,
PropModeReplace,
(unsigned char*)&visualId,
1);
}
// ******************************************
setIconSize(mIconSize);
XClientMessageEvent ev;
ev.type = ClientMessage;
ev.window = root;
ev.message_type = XfitMan::atom("MANAGER");
ev.format = 32;
ev.data.l[0] = CurrentTime;
ev.data.l[1] = _NET_SYSTEM_TRAY_S;
ev.data.l[2] = mTrayId;
ev.data.l[3] = 0;
ev.data.l[4] = 0;
XSendEvent(dsp, root, False, StructureNotifyMask, (XEvent*)&ev);
XDamageQueryExtension(mDisplay, &mDamageEvent, &mDamageError);
qDebug() << "Systray started";
mValid = true;
qApp->installNativeEventFilter(this);
}
/************************************************
************************************************/
void LXQtTray::stopTray()
{
for (auto & icon : mIcons)
disconnect(icon, &QObject::destroyed, this, &LXQtTray::onIconDestroyed);
qDeleteAll(mIcons);
if (mTrayId)
{
XDestroyWindow(mDisplay, mTrayId);
mTrayId = 0;
}
mValid = false;
}
/************************************************
************************************************/
void LXQtTray::onIconDestroyed(QObject * icon)
{
//in the time QOjbect::destroyed is emitted, the child destructor
//is already finished, so the qobject_cast to child will return nullptr in all cases
mIcons.removeAll(static_cast<TrayIcon *>(icon));
}
/************************************************
************************************************/
void LXQtTray::addIcon(Window winId)
{
// decline to add an icon for a window we already manage
TrayIcon *icon = findIcon(winId);
if(icon)
return;
icon = new TrayIcon(winId, mIconSize, this);
mIcons.append(icon);
mLayout->addWidget(icon);
connect(icon, &QObject::destroyed, this, &LXQtTray::onIconDestroyed);
}