/* 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 * * 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 #include #include #include #include "trayicon.h" #include "../panel/ilxqtpanel.h" #include #include "lxqttray.h" #include "xfitman.h" #include #include #include #include #include #include #include #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(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(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(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(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(icon)); } /************************************************ ************************************************/ void LXQtTray::addIcon(Window winId) { TrayIcon* icon = new TrayIcon(winId, mIconSize, this); mIcons.append(icon); mLayout->addWidget(icon); connect(icon, &QObject::destroyed, this, &LXQtTray::onIconDestroyed); }