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.
401 lines
11 KiB
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);
|
|
}
|
|
|