aboutsummaryrefslogtreecommitdiff
path: root/lumina-desktop/panel-plugins/systemtray
diff options
context:
space:
mode:
Diffstat (limited to 'lumina-desktop/panel-plugins/systemtray')
-rw-r--r--lumina-desktop/panel-plugins/systemtray/LSysTray.cpp249
-rw-r--r--lumina-desktop/panel-plugins/systemtray/LSysTray.h76
-rw-r--r--lumina-desktop/panel-plugins/systemtray/TrayIcon.cpp142
-rw-r--r--lumina-desktop/panel-plugins/systemtray/TrayIcon.h60
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
bgstack15