diff options
Diffstat (limited to 'src/qtraylabel.cpp')
-rw-r--r-- | src/qtraylabel.cpp | 809 |
1 files changed, 809 insertions, 0 deletions
diff --git a/src/qtraylabel.cpp b/src/qtraylabel.cpp new file mode 100644 index 0000000..c3daaa3 --- /dev/null +++ b/src/qtraylabel.cpp @@ -0,0 +1,809 @@ +/* + * Copyright (C) 2004 Girish Ramakrishnan All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// $Id: qtraylabel.cpp,v 1.31 2005/06/21 10:04:36 cs19713 Exp $ + +// Include all Qt includes before X +#include <qstring.h> +#include <qevent.h> +#include <qpoint.h> +#include <qtooltip.h> +#include <qtimer.h> +#include <qimage.h> +#include <qpixmap.h> +#include <qfileinfo.h> +#include <qapplication.h> +#include "trace.h" +#include "qtraylabel.h" + +#include <X11/cursorfont.h> +#include <X11/xpm.h> +#include <Xmu/WinUtil.h> + +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "util.h" + +void QTrayLabel::initialize(void) +{ + mDocked = false; + mWithdrawn = true; + mBalloonTimeout = 4000; + mSkippingTaskbar = false; + mDockWhenMinimized = true; + mDesktop = 666; // setDockedWindow would set it a saner value + + // Balloon's properties are set to match a Qt tool tip (see Qt source) + mBalloon = new QLabel(0, "balloon", WType_TopLevel | WStyle_StaysOnTop | + WStyle_Customize | WStyle_NoBorder | + WStyle_Tool | WX11BypassWM); + mBalloon->setFont(QToolTip::font()); + mBalloon->setPalette(QToolTip::palette()); + mBalloon->setAlignment(Qt::AlignLeft | Qt::AlignTop); + mBalloon->setAutoMask(FALSE); + mBalloon->setAutoResize(true); + setAlignment(Qt::AlignCenter); + setBackgroundMode(X11ParentRelative); + + connect(&mRealityMonitor, SIGNAL(timeout()), this, SLOT(realityCheck())); + setDockedWindow(mDockedWindow); + + sysTrayStatus(QPaintDevice::x11AppDisplay(), &mSysTray); + // Subscribe to system tray window notifications + if (mSysTray != None) + subscribe(QPaintDevice::x11AppDisplay(), mSysTray, + StructureNotifyMask, true); +} + +// Describe ourselves in a few words +const char *QTrayLabel::me(void) const +{ + static char temp[100]; + snprintf(temp, sizeof(temp), "(%s,PID=%i,WID=0x%x)", + mProgName[0].latin1(), mPid, (unsigned) mDockedWindow); + return temp; +} + +QTrayLabel::QTrayLabel(Window w, QWidget* parent, const QString& text) + :QLabel(parent, text, WStyle_Customize | WStyle_NoBorder | WStyle_Tool), + mDockedWindow(w), mPid(0) +{ + initialize(); +} + +QTrayLabel::QTrayLabel(const QStringList& pname, pid_t pid, QWidget* parent) + :QLabel(parent, "TrayLabel", WStyle_Customize | WStyle_NoBorder | WStyle_Tool), + mDockedWindow(None), mProgName(pname), mPid(pid) +{ + if (pname[0].at(0) != '/' && pname[0].find('/', 1) > 0) + mProgName[0] = QFileInfo(pname[0]).absFilePath(); // convert to absolute + initialize(); +} + +QTrayLabel::~QTrayLabel() +{ + TRACE("%s Goodbye", me()); + if (mDockedWindow == None) return; + // Leave the docked window is some sane state + mSkippingTaskbar = false; + skipTaskbar(); + map(); +} + +/* + * Scans the windows in the desktop and checks if a window exists that we might + * be interested in + */ +void QTrayLabel::scanClients() +{ + Window r, parent, *children; + unsigned nchildren = 0; + Display *display = QPaintDevice::x11AppDisplay(); + QString ename = QFileInfo(mProgName[0]).fileName(); // strip out the path + + XQueryTree(display, qt_xrootwin(), &r, &parent, &children, &nchildren); + TRACE("%s nchildren=%i", me(), nchildren); + for(unsigned i=0; i<nchildren; i++) + { + Window w = XmuClientWindow(display, children[i]); + TRACE("\t%s checking 0x%x", me(), (unsigned) w); + if (!isNormalWindow(display, w)) continue; + if (analyzeWindow(display, w, mPid, ename.latin1())) + { + TRACE("\t%s SOULMATE FOUND", me()); + setDockedWindow(w); + break; + } + } +} + +/* + * Do a reality check :). Note that this timer runs only when required. Does 3 + * things, + * 1) If the system tray had disappeared, checks for arrival of new system tray + * 2) Check root window subscription since it is overwritten by Qt (see below) + * 3) Checks health of the process whose windows we are docking + */ +void QTrayLabel::realityCheck(void) +{ + if (mSysTray == None) + { + // Check the system tray status if we were docked + if (sysTrayStatus(QPaintDevice::x11AppDisplay(), &mSysTray) + != SysTrayPresent) return; // no luck + + TRACE("%s System tray present", me()); + dock(); + subscribe(QPaintDevice::x11AppDisplay(), mSysTray, + StructureNotifyMask, true); + mRealityMonitor.stop(); + return; + } + + /* + * I am not sure when, but Qt at some point in time overwrites our + * subscription (SubstructureNotifyMask) on the root window. So, we check + * the status of root window subscription periodically. Now, from the time + * Qt overwrote our subscription to the time we discovered it, the + * window we are looking for could have been mapped and we would have never + * been informed (since Qt overrwrote the subscription). So we have to + * scan existing client list and dock. I have never seen this happen + * but I see it likely to happen during session restoration + */ + Display *display = QPaintDevice::x11AppDisplay(); + XWindowAttributes attr; + XGetWindowAttributes(display, qt_xrootwin(), &attr); + + if (!(attr.your_event_mask & SubstructureNotifyMask)) + { + subscribe(display, None, SubstructureNotifyMask, true); + TRACE("%s rescanning clients since qt overrode mask", me()); + scanClients(); + } + + if (mPid) + { + // Check process health + int status; + if (waitpid(mPid, &status, WNOHANG) == 0) return; // still running + TRACE("%s processDead", me()); + mPid = 0; + processDead(); + } +} + +/* + * Sends a message to the WM to show this window on all the desktops + */ +void QTrayLabel::showOnAllDesktops(void) +{ + TRACE("Showing on all desktops"); + Display *d = QPaintDevice::x11AppDisplay(); + long l[5] = { -1, 0, 0, 0, 0 }; // -1 = all, 0 = Desktop1, 1 = Desktop2 ... + sendMessage(d, qt_xrootwin(), mDockedWindow, "_NET_WM_DESKTOP", 32, + SubstructureNotifyMask | SubstructureRedirectMask, l, sizeof(l)); +} + +// System tray messages +const long SYSTEM_TRAY_REQUEST_DOCK = 0; +const long SYSTEM_TRAY_BEGIN_MESSAGE = 1; +const long SYSTEM_TRAY_CANCEL_MESSAGE = 2; + +/* + * Add the window to the system tray. Different WM require different hints to be + * set. We support the following (Google for more information), + * 1. GNOME - SYSTEM_TRAY_REQUEST_DOCK (freedesktop.org) + * 2. KDE 3.x and above - _KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR + * 3. Older KDE - KWM_DOCKWINDOW (Untested) + */ +void QTrayLabel::dock(void) +{ + TRACE("%s", me()); + mDocked = true; + if (mDockedWindow == None) return; // nothing to add + + if (mSysTray == None) // no system tray yet + { + TRACE("%s starting reality monitor", me()); + mRealityMonitor.start(500); + return; + } + + Display *display = QPaintDevice::x11AppDisplay(); + Window wid = winId(); + + // 1. GNOME and NET WM Specification + long l[5] = { CurrentTime, SYSTEM_TRAY_REQUEST_DOCK, wid, 0, 0 }; + sendMessage(display, mSysTray, mSysTray, "_NET_SYSTEM_TRAY_OPCODE", + 32, 0L, l, sizeof(l)); + + // 2. KDE 3.x and above + Atom tray_atom = + XInternAtom(display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False); + XChangeProperty(display, wid, tray_atom, XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wid, 1); + + // 3. All other KDEs + tray_atom = XInternAtom(display, "KWM_DOCKWINDOW", False); + XChangeProperty(display, wid, tray_atom, XA_WINDOW, 32, + PropModeReplace, (unsigned char *) &wid, 1); + + TRACE("%s ", me()); + + handleTitleChange(); + handleIconChange(); + + if (mProgName.count() == 0) setAppName(mClass); + + /* + * For Gnome, a delay is required before we do a show (dont ask me why) + * If I do a show() without any delay, sometimes the icon has width=1 pixel + * even though the minimumSizeHint = 24, 24. I have successfully got it + * working with with a delay of as little as 50ms. But since I + * dont understand why this delay is required, I am justifiably paranoid + */ + QTimer::singleShot(500, this, SLOT(show())); + + // let the world know + emit docked(this); + emit docked(); +} + +/* + * Undocks. Removes us from the system tray. The spec doesnt say how an icon + * can be removed from the tray. KDE Spec says XUnmapWindow or XWithdraw should + * be used. It works but the system tray does not fill the void that we left + * in the tray. Looks like the system tray will resize only for DestroyEvents + */ +void QTrayLabel::undock(void) +{ + TRACE("%s stopping reality monitor", me()); + mRealityMonitor.stop(); + XUnmapWindow(QPaintDevice::x11AppDisplay(), winId()); + emit undocked(this); + emit undocked(); +} + +/* + * Maps the window from the same place it was withdrawn from + */ +void QTrayLabel::map(void) +{ + TRACE("%s", me()); + mWithdrawn = false; + if (mDockedWindow == None) return; + + Display *display = QPaintDevice::x11AppDisplay(); + + if (mDesktop == -1) + { + /* + * We track _NET_WM_DESKTOP changes in the x11EventFilter. Its used here. + * _NET_WM_DESKTOP is set by the WM to the active desktop for newly + * mapped windows (like this one) at some point in time. We give + * the WM 200ms to do that. We will override that value to -1 (all + * desktops) on showOnAllDesktops(). + */ + QTimer::singleShot(200, this, SLOT(showOnAllDesktops())); + } + + /* + * A simple XMapWindow would not do. Some applications like xmms wont + * redisplay its other windows (like the playlist, equalizer) since the + * Withdrawn->Normal state change code does not map them. So we make the + * window go through Withdrawn->Iconify->Normal state. + */ + XWMHints *wm_hint = XGetWMHints(display, mDockedWindow); + if (wm_hint) + { + wm_hint->initial_state = IconicState; + XSetWMHints(display, mDockedWindow, wm_hint); + XFree(wm_hint); + } + + XMapWindow(display, mDockedWindow); + mSizeHint.flags = USPosition; // Obsolete ? + XSetWMNormalHints(display, mDockedWindow, &mSizeHint); + // make it the active window + long l[5] = { None, CurrentTime, None, 0, 0 }; + sendMessage(display, qt_xrootwin(), mDockedWindow, "_NET_ACTIVE_WINDOW", 32, + SubstructureNotifyMask | SubstructureRedirectMask, l, sizeof(l)); + // skipTaskbar modifies _NET_WM_STATE. Make sure we dont override WMs value + QTimer::singleShot(230, this, SLOT(skipTaskbar())); + // disable docking when minized for some time (since we went to Iconic state) + mDockWhenMinimized = !mDockWhenMinimized; + QTimer::singleShot(230, this, SLOT(toggleDockWhenMinimized())); +} + +void QTrayLabel::withdraw(void) +{ + TRACE("%s", me()); + mWithdrawn = true; + if (mDockedWindow == None) return; + + Display *display = QPaintDevice::x11AppDisplay(); + int screen = DefaultScreen(display); + long dummy; + + XGetWMNormalHints(display, mDockedWindow, &mSizeHint, &dummy); + + /* + * A simple call to XWithdrawWindow wont do. Here is what we do: + * 1. Iconify. This will make the application hide all its other windows. For + * example, xmms would take off the playlist and equalizer window. + * 2. Next tell the WM, that we would like to go to withdrawn state. Withdrawn + * state will remove us from the taskbar. + * Reference: ICCCM 4.1.4 Changing Window State + */ + XIconifyWindow(display, mDockedWindow, screen); // good for effects too + XUnmapWindow(display, mDockedWindow); + XUnmapEvent ev; + memset(&ev, 0, sizeof(ev)); + ev.type = UnmapNotify; + ev.display = display; + ev.event = qt_xrootwin(); + ev.window = mDockedWindow; + ev.from_configure = false; + XSendEvent(display, qt_xrootwin(), False, + SubstructureRedirectMask|SubstructureNotifyMask, (XEvent *)&ev); + XSync(display, False); +} + +/* + * Skipping the taskbar is a bit painful. Basically, NET_WM_STATE needs to + * have _NET_WM_STATE_SKIP_TASKBAR. NET_WM_STATE needs to be updated + * carefully since it is a set of states. + */ +void QTrayLabel::skipTaskbar(void) +{ + Atom __attribute__ ((unused)) type; + int __attribute__ ((unused)) format; + unsigned long __attribute__ ((unused)) left; + Atom *data = NULL; + unsigned long nitems = 0, num_states = 0; + Display *display = QPaintDevice::x11AppDisplay(); + + TRACE("%s", me()); + Atom _NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", True); + Atom skip_atom = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False); + int ret = XGetWindowProperty(display, mDockedWindow, _NET_WM_STATE, 0, + 20, False, AnyPropertyType, &type, &format, + &nitems, &left, (unsigned char **) &data); + Atom *old_states = (Atom *) data; + bool append = true, replace = false; + + if ((ret == Success) && data) + { + // Search for the skip_atom. Stop when found + for (num_states = 0; num_states < nitems; num_states++) + if (old_states[num_states] == skip_atom) break; + + if (mSkippingTaskbar) + append = (num_states >= nitems); + else + { + if (num_states < nitems) + { + replace = true; // need to remove skip_atom + for (; num_states < nitems - 1; num_states++) + old_states[num_states] = old_states[num_states + 1]; + } + } + XFree(data); + } + + TRACE("%s SkippingTaskar=%i append=%i replace=%i", me(), mSkippingTaskbar, + append, replace); + + if (mSkippingTaskbar) + { + if (append) + XChangeProperty(display, mDockedWindow, _NET_WM_STATE, XA_ATOM, 32, + PropModeAppend, (unsigned char *) &skip_atom, 1); + } + else if (replace) + XChangeProperty(display, mDockedWindow, _NET_WM_STATE, XA_ATOM, 32, + PropModeReplace, (unsigned char *) &old_states, nitems - 1); +} + +void QTrayLabel::setSkipTaskbar(bool skip) +{ + TRACE("%s Skip=%i", me(), skip); + mSkippingTaskbar = skip; + if (mDockedWindow != None && !mWithdrawn) skipTaskbar(); +} + +/* + * Closes a window by sending _NET_CLOSE_WINDOW. For reasons best unknown we + * need to first map and then send the request. + */ +void QTrayLabel::close(void) +{ + TRACE("%s", me()); + Display *display = QPaintDevice::x11AppDisplay(); + long l[5] = { 0, 0, 0, 0, 0 }; + map(); + sendMessage(display, qt_xrootwin(), mDockedWindow, "_NET_CLOSE_WINDOW", 32, + SubstructureNotifyMask | SubstructureRedirectMask, + l, sizeof(l)); +} + +/* + * Sets the tray icon. If the icon failed to load, we revert to application icon + */ +void QTrayLabel::setTrayIcon(const QString& icon) +{ + mCustomIcon = icon; + if (QPixmap(mCustomIcon).isNull()) mCustomIcon = QString::null; + TRACE("%s mCustomIcon=%s", me(), mCustomIcon.latin1()); + updateIcon(); +} + +/* + * Sets the docked window to w. + * A) Start/stop reality timer. + * B) Subscribe/Unsubscribe for root/w notifications as appropriate + * C) And of course, dock the window and apply some settings + */ +void QTrayLabel::setDockedWindow(Window w) +{ + TRACE("%s %s reality monitor", me(), + mDockedWindow==None ? "Starting" : "Stopping"); + + // Check if we are allowed to dock this window (allows custom rules) + if (w != None) mDockedWindow = canDockWindow(w) ? w : None; + else mDockedWindow = None; + + if (mDockedWindow == None) mRealityMonitor.start(500); + else mRealityMonitor.stop(); + + Display *d = QPaintDevice::x11AppDisplay(); + + // Subscribe for window or root window events + if (w == None) subscribe(d, None, SubstructureNotifyMask, true); + else + { + if (canUnsubscribeFromRoot()) + subscribe(d, None, ~SubstructureNotifyMask, false); + else subscribe(d, None, SubstructureNotifyMask, true); + + subscribe(d, w, + StructureNotifyMask | PropertyChangeMask | + VisibilityChangeMask | FocusChangeMask, + true); + } + + if (mDocked && w!=None) + { + // store the desktop on which the window is being shown + getCardinalProperty(d, mDockedWindow, + XInternAtom(d, "_NET_WM_DESKTOP", True), &mDesktop); + + if (mWithdrawn) + // show the window for sometime before docking + QTimer::singleShot(1000, this, SLOT(withdraw())); + else map(); + dock(); + } +} + +/* + * Balloon text. Overload this if you dont like the way things are ballooned + */ +void QTrayLabel::balloonText() +{ + TRACE("%s BalloonText=%s ToolTipText=%s", me(), + mBalloon->text().latin1(), QToolTip::textFor(this).latin1()); + + if (mBalloon->text() == QToolTip::textFor(this)) return; +#if 0 // I_GOT_NETWM_BALLOONING_TO_WORK + // if you can get NET WM ballooning to work let me know + static int id = 1; + long l[5] = { CurrentTime, SYSTEM_TRAY_BEGIN_MESSAGE, 2000, + mTitle.length(), id++ + }; + sendMessage(display, mSystemTray, winId(), "_NET_SYSTEM_TRAY_OPCODE", 32, + SubstructureNotifyMask | SubstructureRedirectMask, + l, sizeof(l)); + int length = mTitle.length(); + const char *data = mTitle.latin1(); + while (length > 0) + { + sendMessage(display, mSystemTray, winId(), "_NET_SYSTEM_TRAY_MESSAGE_DATA", 8, + SubstructureNotifyMask | SubstructureRedirectMask, + (void *) data, length > 20 ? 20 : length); + length -= 20; + data += 20; + } +#else + // Manually do ballooning. See the Qt ToolTip code + QString oldText = mBalloon->text(); + mBalloon->setText(QToolTip::textFor(this)); + if (oldText.isEmpty()) return; // dont tool tip the first time + QPoint p = mapToGlobal(QPoint(0, -1 - mBalloon->height())); + if (p.x() + mBalloon->width() > QApplication::desktop()->width()) + p.setX(p.x() + width() - mBalloon->width()); + + if (p.y() < 0) p.setY(height() + 1); + + mBalloon->move(p); + mBalloon->show(); + QTimer::singleShot(mBalloonTimeout, mBalloon, SLOT(hide())); +#endif +} + +/* + * Update the title in the menu. Balloon the title change if necessary + */ +void QTrayLabel::handleTitleChange(void) +{ + Display *display = QPaintDevice::x11AppDisplay(); + char *window_name = NULL; + + XFetchName(display, mDockedWindow, &window_name); + mTitle = window_name; + TRACE("%s has title [%s]", me(), mTitle.latin1()); + if (window_name) XFree(window_name); + + XClassHint ch; + if (XGetClassHint(display, mDockedWindow, &ch)) + { + if (ch.res_class) mClass = QString(ch.res_class); + else if (ch.res_name) mClass = QString(ch.res_name); + + if (ch.res_class) XFree(ch.res_class); + if (ch.res_name) XFree(ch.res_name); + } + + updateTitle(); + if (mBalloonTimeout) balloonText(); +} + +/* + * Overload this if you want a tool tip format that is different from the one + * below i.e "Title [Class]". + */ +void QTrayLabel::updateTitle() +{ + TRACE("%s", me()); + QString text = mTitle + " [" + mClass + "]"; + QToolTip::remove(this); + QToolTip::add(this, text); + + if (mBalloonTimeout) balloonText(); +} + +void QTrayLabel::handleIconChange(void) +{ + char **window_icon = NULL; + + TRACE("%s", me()); + if (mDockedWindow == None) return; + + Display *display = QPaintDevice::x11AppDisplay(); + XWMHints *wm_hints = XGetWMHints(display, mDockedWindow); + if (wm_hints != NULL) + { + if (!(wm_hints->flags & IconMaskHint)) + wm_hints->icon_mask = None; + /* + * We act paranoid here. Progams like KSnake has a bug where + * IconPixmapHint is set but no pixmap (Actually this happens with + * quite a few KDE programs) X-( + */ + if ((wm_hints->flags & IconPixmapHint) && (wm_hints->icon_pixmap)) + XpmCreateDataFromPixmap(display, &window_icon, wm_hints->icon_pixmap, + wm_hints->icon_mask, NULL); + XFree(wm_hints); + } + QImage image; + if (!window_icon) + { + if (!image.load(QString(ICONS_PATH) + "/question.png")) + image.load(qApp->applicationDirPath() + "/icons/question.png"); + } + else image = QPixmap((const char **) window_icon).convertToImage(); + if (window_icon) XpmFree(window_icon); + mAppIcon = image.smoothScale(24, 24); // why? + setMinimumSize(mAppIcon.size()); + setMaximumSize(mAppIcon.size()); + + updateIcon(); +} + +/* + * Overload this to possibly do operations on the pixmap before it is set + */ +void QTrayLabel::updateIcon() +{ + TRACE("%s", me()); + setPixmap(mCustomIcon.isEmpty() ? mAppIcon : mCustomIcon); + erase(); + QPaintEvent pe(rect()); + paintEvent(&pe); +} + +/* + * Mouse activity on our label. RightClick = Menu. LeftClick = Toggle Map + */ +void QTrayLabel::mouseReleaseEvent(QMouseEvent * ev) +{ + emit clicked(ev->button(), ev->globalPos()); +} + +/* + * Track drag event + */ +void QTrayLabel::dragEnterEvent(QDragEnterEvent *ev) +{ + ev->accept(); + map(); +} + +/* + * Event dispatcher + */ +bool QTrayLabel::x11EventFilter(XEvent *ev) +{ + XAnyEvent *event = (XAnyEvent *)ev; + + if (event->window == mSysTray) + { + if (event->type != DestroyNotify) return FALSE; // not interested in others + emit sysTrayDestroyed(); + mSysTray = None; + TRACE("%s SystemTray disappeared. Starting timer", me()); + mRealityMonitor.start(500); + return true; + } + else if (event->window == mDockedWindow) + { + if (event->type == DestroyNotify) + destroyEvent(); + else if (event->type == PropertyNotify) + propertyChangeEvent(((XPropertyEvent *)event)->atom); + else if (event->type == VisibilityNotify) + { + if (((XVisibilityEvent *) event)->state == VisibilityFullyObscured) + obscureEvent(); + } + else if (event->type == MapNotify) + { + mWithdrawn = false; + mapEvent(); + } + else if (event->type == UnmapNotify) + { + mWithdrawn = true; + unmapEvent(); + } + else if (event->type == FocusOut) + { + focusLostEvent(); + } + return true; // Dont process this again + } + + if (mDockedWindow != None || event->type != MapNotify) return FALSE; + + TRACE("%s Will analyze window 0x%x", me(), (int)((XMapEvent *)event)->window); + // Check if this window is the soulmate we are looking for + Display *display = QPaintDevice::x11AppDisplay(); + Window w = XmuClientWindow(display, ((XMapEvent *) event)->window); + if (!isNormalWindow(display, w)) return FALSE; + if (!analyzeWindow(display, w, mPid, + QFileInfo(mProgName[0]).fileName().latin1())) return FALSE; + // All right. Lets dock this baby + setDockedWindow(w); + return true; +} + +void QTrayLabel::minimizeEvent(void) +{ + TRACE("minimizeEvent"); + if (mDockWhenMinimized) withdraw(); +} + +void QTrayLabel::destroyEvent(void) +{ + TRACE("%s destroyEvent", me()); + setDockedWindow(None); + if (!mPid) undock(); +} + +void QTrayLabel::propertyChangeEvent(Atom property) +{ + Display *display = QPaintDevice::x11AppDisplay(); + static Atom WM_NAME = XInternAtom(display, "WM_NAME", True); + static Atom WM_ICON = XInternAtom(display, "WM_ICON", True); + static Atom WM_STATE = XInternAtom(display, "WM_STATE", True); + static Atom _NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", True); + static Atom _NET_WM_DESKTOP = XInternAtom(display, "_NET_WM_DESKTOP", True); + + if (property == WM_NAME) + handleTitleChange(); + else if (property == WM_ICON) + handleIconChange(); + else if (property == _NET_WM_STATE) + ; // skipTaskbar(); + else if (property == _NET_WM_DESKTOP) + { + TRACE("_NET_WM_DESKTOP changed"); + getCardinalProperty(display, mDockedWindow, _NET_WM_DESKTOP, &mDesktop); + } + else if (property == WM_STATE) + { + Atom type = None; + int format; + unsigned long nitems, after; + unsigned char *data = NULL; + int r = XGetWindowProperty(display, mDockedWindow, WM_STATE, + 0, 1, False, AnyPropertyType, &type, + &format, &nitems, &after, &data); + + if ((r == Success) && data && (*(long *) data == IconicState)) + { + minimizeEvent(); + XFree(data); + } + } +} + +// Session Management +bool QTrayLabel::saveState(QSettings &settings) +{ + TRACE("%s saving state", me()); + settings.writeEntry("/Application", mProgName.join(" ")); + settings.writeEntry("/CustomIcon", mCustomIcon); + settings.writeEntry("/BalloonTimeout", mBalloonTimeout); + settings.writeEntry("/DockWhenMinimized", mDockWhenMinimized); + settings.writeEntry("/SkipTaskbar", mSkippingTaskbar); + settings.writeEntry("/Withdraw", mWithdrawn); + return true; +} + +bool QTrayLabel::restoreState(QSettings &settings) +{ + TRACE("%s restoring state", me()); + mCustomIcon = settings.readEntry("/CustomIcon"); + setBalloonTimeout(settings.readNumEntry("/BalloonTimeout")); + setDockWhenMinimized(settings.readBoolEntry("/DockWhenMinimized")); + setSkipTaskbar(settings.readBoolEntry("/SkipTaskbar")); + mWithdrawn = settings.readBoolEntry("/Withdraw"); + + dock(); + + /* + * Since we are getting restored, it is likely that the application that we + * are interested in has already been started (if we didnt launch it). + * So we scan the list of windows and grab the first one that satisfies us + * This implicitly assumes that if mPid!=0 then we launched it. Wait till + * the application really shows itself up before we do a scan (the reason + * why we have 2s + */ + if (!mPid) QTimer::singleShot(2000, this, SLOT(scanClients())); + + return true; +} + +// End kicking butt + |