diff options
Diffstat (limited to 'lumina-desktop/panel-plugins/systemtray')
-rw-r--r-- | lumina-desktop/panel-plugins/systemtray/LSysTray.cpp | 249 | ||||
-rw-r--r-- | lumina-desktop/panel-plugins/systemtray/LSysTray.h | 76 | ||||
-rw-r--r-- | lumina-desktop/panel-plugins/systemtray/TrayIcon.cpp | 142 | ||||
-rw-r--r-- | lumina-desktop/panel-plugins/systemtray/TrayIcon.h | 60 |
4 files changed, 527 insertions, 0 deletions
diff --git a/lumina-desktop/panel-plugins/systemtray/LSysTray.cpp b/lumina-desktop/panel-plugins/systemtray/LSysTray.cpp new file mode 100644 index 00000000..27dbaee5 --- /dev/null +++ b/lumina-desktop/panel-plugins/systemtray/LSysTray.cpp @@ -0,0 +1,249 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2012, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "LSysTray.h" +#include "../../LSession.h" + +#include <LuminaX11.h> +//X includes (these need to be last due to Qt compile issues) +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrender.h> +#include <X11/extensions/Xdamage.h> + +//Static variables for damage detection (tray update notifications) +static int dmgEvent = 0; +static int dmgError = 0; + +LSysTray::LSysTray(QWidget *parent, QString id, bool horizontal) : LPPlugin(parent, id, horizontal){ + frame = new QFrame(this); + frame->setContentsMargins(0,0,0,0); + //frame->setStyleSheet("QFrame{ background: transparent; border: 1px solid transparent; border-radius: 5px; }"); + LI = new QBoxLayout( this->layout()->direction()); + frame->setLayout(LI); + LI->setAlignment(Qt::AlignCenter); + LI->setSpacing(1); + LI->setContentsMargins(0,0,0,0); + this->layout()->addWidget(frame); + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + TrayID=0; + upTimer = new QTimer(this); + upTimer->setInterval(300000); //maximum time between refreshes is 5 minutes + connect(upTimer, SIGNAL(timeout()), this, SLOT(checkAll()) ); + isRunning = false; + start(); +} + +LSysTray::~LSysTray(){ + if(isRunning){ + this->stop(); + } +} + +void LSysTray::start(){ + if(TrayID!=0){ return; } //already running + //Make sure we catch all events right away + connect(LSession::instance(),SIGNAL(aboutToQuit()),this,SLOT(closeAll()) ); + connect(LSession::instance(),SIGNAL(TrayEvent(XEvent*)), this, SLOT(checkXEvent(XEvent*)) ); + isRunning = true; + TrayID = LX11::startSystemTray(0); //LSession::desktop()->screenNumber(this)); + if(TrayID!=0){ + XSelectInput(QX11Info::display(), TrayID, InputOutput); //make sure TrayID events get forwarded here + XDamageQueryExtension( QX11Info::display(), &dmgEvent, &dmgError); + //Now connect the session logout signal to the close function + qDebug() << "System Tray Started Successfully"; + upTimer->start(); + //QTimer::singleShot(100, this, SLOT(initialTrayIconDetect()) ); + }else{ + disconnect(this); + } + isRunning = (TrayID!=0); +} + +void LSysTray::stop(){ + if(!isRunning){ return; } + upTimer->stop(); + //Now close down the system tray registry + qDebug() << "Stop system Tray"; + LX11::closeSystemTray(TrayID); + TrayID = 0; + disconnect(this); //remove any signals/slots + isRunning = false; + //Release all the tray applications and delete the containers + qDebug() << " - Remove tray applications"; + for(int i=(trayIcons.length()-1); i>=0; i--){ + trayIcons[i]->detachApp(); + TrayIcon *cont = trayIcons.takeAt(i); + LI->removeWidget(cont); + delete cont; + } + qDebug() << "Done stopping system tray"; +} + +// ======================== +// PRIVATE FUNCTIONS +// ======================== +void LSysTray::checkXEvent(XEvent *event){ + if(!isRunning){ return; } + switch(event->type){ + // ------------------------- + case ClientMessage: + //Only check if the client is the system tray, otherwise ignore + if(event->xany.window == TrayID){ + //qDebug() << "SysTray: ClientMessage"; + switch(event->xclient.data.l[1]){ + case SYSTEM_TRAY_REQUEST_DOCK: + addTrayIcon(event->xclient.data.l[2]); //Window ID + break; + //case SYSTEM_TRAY_BEGIN_MESSAGE: + //Let the window manager handle the pop-up messages for now + //break; + //case SYSTEM_TRAY_CANCEL_MESSAGE: + //Let the window manager handle the pop-up messages for now + //break; + } + } + break; + case SelectionClear: + if(event->xany.window == TrayID){ + //qDebug() << "SysTray: Selection Clear"; + this->stop(); //de-activate this system tray (release all embeds) + } + break; + case DestroyNotify: + //qDebug() << "SysTray: DestroyNotify"; + removeTrayIcon(event->xany.window); //Check for removing an icon + break; + + case ConfigureNotify: + for(int i=0; i<trayIcons.length(); i++){ + if(event->xany.window==trayIcons[i]->appID()){ + //qDebug() << "SysTray: Configure Event" << trayIcons[i]->appID(); + trayIcons[i]->update(); //trigger a repaint event + break; + } + } + default: + if(event->type == dmgEvent+XDamageNotify){ + WId ID = reinterpret_cast<XDamageNotifyEvent*>(event)->drawable; + //qDebug() << "SysTray: Damage Event"; + for(int i=0; i<trayIcons.length(); i++){ + if(ID==trayIcons[i]->appID()){ + //qDebug() << "SysTray: Damage Event" << ID; + trayIcons[i]->update(); //trigger a repaint event + break; + } + } + } + + }//end of switch over event type +} + +void LSysTray::closeAll(){ + //Actually close all the tray apps (not just unembed) + //This is used when the desktop is shutting everything down + for(int i=0; i<trayIcons.length(); i++){ + LX11::CloseWindow(trayIcons[i]->appID()); + } + +} + +void LSysTray::checkAll(){ + for(int i=0; i<trayIcons.length(); i++){ + trayIcons[i]->update(); + } +} + +void LSysTray::initialTrayIconDetect(){ + // WARNING: This is still experimental and should be disabled by default!! + QList<WId> wins = LX11::findOrphanTrayWindows(); + for(int i=0; i<wins.length(); i++){ + //addTrayIcon(wins[i]); + qDebug() << "Initial Tray Window:" << wins[i] << LX11::WindowClass(wins[i]); + } +} + +void LSysTray::addTrayIcon(WId win){ + if(win == 0 || !isRunning){ return; } + //qDebug() << "System Tray: Add Tray Icon:" << win; + bool exists = false; + for(int i=0; i<trayIcons.length(); i++){ + if(trayIcons[i]->appID() == win){ exists=true; break; } + } + if(!exists){ + //qDebug() << " - New Icon Window:" << win; + TrayIcon *cont = new TrayIcon(this); + QCoreApplication::processEvents(); + connect(cont, SIGNAL(AppClosed()), this, SLOT(trayAppClosed()) ); + connect(cont, SIGNAL(AppAttached()), this, SLOT(updateStatus()) ); + trayIcons << cont; + LI->addWidget(cont); + //qDebug() << " - Update tray layout"; + if(this->layout()->direction()==QBoxLayout::LeftToRight){ + cont->setSizeSquare(this->height()-2*frame->frameWidth()); //horizontal tray + this->setMaximumSize( trayIcons.length()*this->height(), 10000); + }else{ + cont->setSizeSquare(this->width()-2*frame->frameWidth()); //vertical tray + this->setMaximumSize(10000, trayIcons.length()*this->width()); + } + LSession::processEvents(); + //qDebug() << " - Attach tray app"; + cont->attachApp(win); + LI->update(); //make sure there is no blank space + } +} + +void LSysTray::removeTrayIcon(WId win){ + if(win==0 || !isRunning){ return; } + for(int i=0; i<trayIcons.length(); i++){ + if(trayIcons[i]->appID()==win){ + //qDebug() << " - Remove Icon Window:" << win; + //Remove it from the layout and keep going + TrayIcon *cont = trayIcons.takeAt(i); + LI->removeWidget(cont); + delete cont; + i--; //make sure we don't miss an item when we continue + QCoreApplication::processEvents(); + } + } + //Re-adjust the maximum widget size to account for what is left + if(this->layout()->direction()==QBoxLayout::LeftToRight){ + this->setMaximumSize( trayIcons.length()*this->height(), 10000); + }else{ + this->setMaximumSize(10000, trayIcons.length()*this->width()); + } + LI->update(); //update the layout (no gaps) + this->update(); //update the main widget appearance +} + +void LSysTray::updateStatus(){ + qDebug() << "System Tray: Client Attached"; + LI->update(); //make sure there is no blank space + //qDebug() << " - Items:" << trayIcons.length(); +} + +void LSysTray::trayAppClosed(){ + if(!isRunning){ return; } + for(int i=0; i<trayIcons.length(); i++){ + if(trayIcons[i]->appID() == 0){ + qDebug() << "System Tray: Removing icon"; + TrayIcon *cont = trayIcons.takeAt(i); + LI->removeWidget(cont); + delete cont; + QCoreApplication::processEvents(); + } + } + //Re-adjust the maximum widget size + if(this->layout()->direction()==QBoxLayout::LeftToRight){ + this->setMaximumSize( trayIcons.length()*this->height(), 10000); + }else{ + this->setMaximumSize(10000, trayIcons.length()*this->width()); + } + LI->update(); //update the layout (no gaps) + this->update(); +} + diff --git a/lumina-desktop/panel-plugins/systemtray/LSysTray.h b/lumina-desktop/panel-plugins/systemtray/LSysTray.h new file mode 100644 index 00000000..13eb0df1 --- /dev/null +++ b/lumina-desktop/panel-plugins/systemtray/LSysTray.h @@ -0,0 +1,76 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2012, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef _LUMINA_DESKTOP_SYSTRAY_H +#define _LUMINA_DESKTOP_SYSTRAY_H + +//Qt includes +#include <QFrame> +#include <QHBoxLayout> +#include <QDebug> +#include <QX11Info> +#include <QX11EmbedContainer> +#include <QCoreApplication> + +//Local includes +#include "../LPPlugin.h" +#include "TrayIcon.h" + +//SYSTEM TRAY STANDARD DEFINITIONS +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + +class LSysTray : public LPPlugin{ + Q_OBJECT +public: + LSysTray(QWidget *parent = 0, QString id="systemtray", bool horizontal=true); + ~LSysTray(); + + void start(); + void stop(); + +private: + bool isRunning; + QList<TrayIcon*> trayIcons; + QFrame *frame; + QBoxLayout *LI; //layout items + WId TrayID; + QTimer *upTimer; //manual timer to force refresh of all items + +private slots: + void checkXEvent(XEvent *event); + void closeAll(); + void checkAll(); + + void initialTrayIconDetect(); //initial scan for previously running tray apps + void addTrayIcon(WId win); + void removeTrayIcon(WId win); + + void updateStatus(); + void trayAppClosed(); + +public slots: + virtual void OrientationChange(){ + //make sure the internal layout has the same orientation as the main widget + LI->setDirection( this->layout()->direction() ); + //Re-adjust the maximum widget size + int sz; + if(this->layout()->direction()==QBoxLayout::LeftToRight){ + this->setMaximumSize( trayIcons.length()*this->height(), 10000); + sz = this->height()-2*frame->frameWidth(); + }else{ + this->setMaximumSize(10000, trayIcons.length()*this->width()); + sz = this->width()-2*frame->frameWidth(); + } + for(int i=0; i<trayIcons.length(); i++){ + trayIcons[i]->setSizeSquare(sz); + trayIcons[i]->repaint(); + } + } +}; + +#endif diff --git a/lumina-desktop/panel-plugins/systemtray/TrayIcon.cpp b/lumina-desktop/panel-plugins/systemtray/TrayIcon.cpp new file mode 100644 index 00000000..37970051 --- /dev/null +++ b/lumina-desktop/panel-plugins/systemtray/TrayIcon.cpp @@ -0,0 +1,142 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "TrayIcon.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/Xdamage.h> + +static Damage dmgID = 0; + +TrayIcon::TrayIcon(QWidget *parent) : QWidget(parent){ + AID = 0; //nothing attached yet + IID = 0; +} + +TrayIcon::~TrayIcon(){ + if(AID!=0){ + detachApp(); + } +} + +WId TrayIcon::appID(){ + return AID; +} + +void TrayIcon::attachApp(WId id){ + if(id==0){ return; } //nothing to attach + else if(AID!=0){ qWarning() << "Tray Icon is already attached to a window!"; return; } + AID = id; + //qDebug() << "Container:" << this->winId(); + //qDebug() << " - Tray:" << AID; + QTimer::singleShot(0,this,SLOT(slotAttach()) ); +} + +void TrayIcon::setSizeSquare(int side){ + this->setFixedSize( QSize(side, side) ); +} + +// ============== +// PUBLIC SLOTS +// ============== +void TrayIcon::detachApp(){ + if(AID==0){ return; } //already detached + qDebug() << "Detach App:" << AID; + //Temporarily move the AID, so that internal slots do not auto-run + WId tmp = AID; + AID = 0; + //Now detach the application window and clean up + qDebug() << " - Unembed"; + LX11::UnembedWindow(tmp); + if(dmgID!=0){ + XDamageDestroy(QX11Info::display(), dmgID); + } + qDebug() << " - finished app:" << tmp; + //if(IID!=this->winId()){ LX11::DestroyWindow(IID); } + IID = 0; + emit AppClosed(); +} + +// ============== +// PRIVATE SLOTS +// ============== +void TrayIcon::slotAttach(){ + IID = this->winId(); //embed directly into this widget + //IID = LX11::CreateWindow( this->winId(), this->rect() ); //Create an intermediate window to be the parent + if( LX11::EmbedWindow(AID, IID) ){ + LX11::RestoreWindow(AID); //make it visible + //XSelectInput(QX11Info::display(), AID, StructureNotifyMask); + dmgID = XDamageCreate( QX11Info::display(), AID, XDamageReportRawRectangles ); + updateIcon(); + qDebug() << "New System Tray App:" << AID; + emit AppAttached(); + QTimer::singleShot(500, this, SLOT(updateIcon()) ); + }else{ + qWarning() << "Could not Embed Tray Application:" << AID; + //LX11::DestroyWindow(IID); + IID = 0; + AID = 0; + emit AppClosed(); + } +} + +void TrayIcon::updateIcon(){ + if(AID==0){ return; } + //Make sure the icon is square + QSize icosize = this->size(); + LX11::ResizeWindow(AID, icosize.width(), icosize.height()); +} + +// ============= +// PROTECTED +// ============= +void TrayIcon::paintEvent(QPaintEvent *event){ + QWidget::paintEvent(event); //make sure the background is already painted + if(AID!=0){ + //qDebug() << "Paint Tray:" << AID; + QPainter painter(this); + //Now paint the tray app on top of the background + //qDebug() << " - Draw tray:" << AID << IID << this->winId(); + //qDebug() << " - - " << event->rect().x() << event->rect().y() << event->rect().width() << event->rect().height(); + //qDebug() << " - Get image"; + QPixmap pix = LX11::WindowImage(AID, false); + if(pix.isNull()){ + //Try to grab the window directly with Qt + //qDebug() << " - Grab window directly"; + pix = QPixmap::grabWindow(AID); + } + //qDebug() << " - Pix size:" << pix.size().width() << pix.size().height(); + //qDebug() << " - Geom:" << this->geometry().x() << this->geometry().y() << this->geometry().width() << this->geometry().height(); + painter.drawPixmap(0,0,this->width(), this->height(), pix.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation) ); + //qDebug() << " - Done"; + } +} + +/*void TrayIcon::moveEvent(QMoveEvent *event){ + //Make sure the main Tray window is right underneath the widget + //qDebug() << "Move Event:" << event->pos().x() << event->pos().y(); + LX11::MoveResizeWindow(AID, QRect( this->mapToGlobal(event->pos()), this->size()) ); + QWidget::moveEvent(event); +}*/ + +void TrayIcon::resizeEvent(QResizeEvent *event){ + //qDebug() << "Resize Event:" << event->size().width() << event->size().height(); + if(AID!=0){ + LX11::ResizeWindow(AID, event->size().width(), event->size().height()); + } +} + +/*bool TrayIcon::x11Event(XEvent *event){ + qDebug() << "XEvent"; + if( event->xany.window==AID || event->type==( (int)dmgID+XDamageNotify) ){ + qDebug() << "Tray X Event:" << AID; + this->update(); //trigger a repaint + return true; + }else{ + return false; //no special handling + } +}*/ diff --git a/lumina-desktop/panel-plugins/systemtray/TrayIcon.h b/lumina-desktop/panel-plugins/systemtray/TrayIcon.h new file mode 100644 index 00000000..97f2fdf5 --- /dev/null +++ b/lumina-desktop/panel-plugins/systemtray/TrayIcon.h @@ -0,0 +1,60 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2014, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Note: The basic idea behind this class that that it puts the app window +// in the same spot as the tray icon (to directly pass mouse events and such), +// while keeping the tray icon visual in sync with the app window +//=========================================== +#ifndef _LUMINA_PANEL_PLUGIN_SYSTEM_TRAY_ICON_H +#define _LUMINA_PANEL_PLUGIN_SYSTEM_TRAY_ICON_H + +//Qt includes +#include <QWidget> +#include <QTimer> +#include <QPaintEvent> +#include <QMoveEvent> +#include <QResizeEvent> +#include <QPainter> +#include <QPixmap> +#include <QImage> + +// libLumina includes +#include <LuminaX11.h> + +//Local includes + +class TrayIcon : public QWidget{ + Q_OBJECT +public: + TrayIcon(QWidget* parent = 0); + ~TrayIcon(); + + WId appID(); //the ID for the attached application + void attachApp(WId id); + void setSizeSquare(int side); + +public slots: + void detachApp(); + void updateIcon(); + +private: + WId IID, AID; //icon ID and app ID + +private slots: + void slotAttach(); //so that the attachment can be done in a new thread + + +protected: + void paintEvent(QPaintEvent *event); + //void moveEvent(QMoveEvent *event); + void resizeEvent(QResizeEvent *event); + //bool x11Event(XEvent *event); + +signals: + void AppClosed(); + void AppAttached(); +}; +#endif
\ No newline at end of file |