summaryrefslogtreecommitdiffstats
path: root/src/tqtraylabel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tqtraylabel.cpp')
-rw-r--r--src/tqtraylabel.cpp1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/src/tqtraylabel.cpp b/src/tqtraylabel.cpp
new file mode 100644
index 0000000..61ace00
--- /dev/null
+++ b/src/tqtraylabel.cpp
@@ -0,0 +1,1086 @@
+/*
+ * 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 TQt includes before X
+#include <tqstring.h>
+#include <tqevent.h>
+#include <tqfiledialog.h>
+#include <tqfileinfo.h>
+#include <tqimage.h>
+#include <tqinputdialog.h>
+#include <tqpixmap.h>
+#include <tqpoint.h>
+#include <tqtooltip.h>
+#include <tqtimer.h>
+
+#include <khelpmenu.h>
+#include <kstdaction.h>
+#include <kiconloader.h>
+#include <kstdguiitem.h>
+#include <tqapplication.h>
+#include <tdeconfig.h>
+#include <tdeglobal.h>
+#include <tdelocale.h>
+#include <tdemessagebox.h>
+#include <tdepopupmenu.h>
+
+#include <X11/cursorfont.h>
+#include <X11/xpm.h>
+#include <X11/Xmu/WinUtil.h>
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "util.h"
+
+#include "trace.h"
+#include "traylabelmgr.h"
+#include "tqtraylabel.h"
+
+void TQTrayLabel::initialize(void)
+{
+ mDocked = false;
+ mWithdrawn = true;
+ mBalloonTimeout = 4000;
+ mUndockWhenDead = false;
+ mDesktop = 666; // setDockedWindow would set it a saner value
+
+ // Balloon's properties are set to match a TQt tool tip (see TQt source)
+ mBalloon = new TQLabel(0, "balloon", WType_TopLevel | WStyle_StaysOnTop |
+ WStyle_Customize | WStyle_NoBorder |
+ WStyle_Tool | WX11BypassWM);
+ mBalloon->setFont(TQToolTip::font());
+ mBalloon->setPalette(TQToolTip::palette());
+ mBalloon->setAlignment(TQt::AlignLeft | TQt::AlignTop);
+ mBalloon->setAutoMask(FALSE);
+ mBalloon->setAutoResize(true);
+ setAlignment(TQt::AlignCenter);
+ setBackgroundMode(X11ParentRelative);
+
+ connect(&mRealityMonitor, SIGNAL(timeout()), this, SLOT(realityCheck()));
+ setDockedWindow(mDockedWindow);
+
+ sysTrayStatus(TQPaintDevice::x11AppDisplay(), &mSysTray);
+ // Subscribe to system tray window notifications
+ if (mSysTray != None)
+ subscribe(TQPaintDevice::x11AppDisplay(), mSysTray,
+ StructureNotifyMask, true);
+
+ installMenu();
+}
+
+// Describe ourselves in a few words
+const char *TQTrayLabel::me(void) const
+{
+ static char temp[100];
+ snprintf(temp, sizeof(temp), "(%s,PID=%i,WID=0x%x)",
+ appName().local8Bit().data(), mPid, (unsigned) mDockedWindow);
+ return temp;
+}
+
+TQTrayLabel::TQTrayLabel(Window w, TQWidget *parent, const TQString &text)
+ : TQLabel(parent, text.utf8(), WStyle_Customize | WStyle_NoBorder | WStyle_Tool),
+ mDockedWindow(w), mPid(0)
+{
+ initialize();
+}
+
+TQTrayLabel::TQTrayLabel(const TQStringList &pname, pid_t pid, TQWidget *parent)
+ : TQLabel(parent, "TrayLabel", WStyle_Customize | WStyle_NoBorder | WStyle_Tool),
+ mDockedWindow(None), mProgName(pname), mPid(pid)
+{
+ initialize();
+}
+
+TQTrayLabel::~TQTrayLabel()
+{
+ TRACE("%s Goodbye", me());
+ if (mDockedWindow == None) return;
+ // Leave the docked window is some sane state
+ mSkipTaskbar->setChecked(false);
+ skipTaskbar();
+ map();
+}
+
+void TQTrayLabel::setAppName(const TQString &prog)
+{
+ // FIXME HACK
+ // using "prog.lower()" relies on window and application name being the same.
+ if (mProgName.count() == 0)
+ {
+ mProgName.push_front(prog.lower());
+ }
+}
+
+/*
+ * Scans the windows in the desktop and checks if a window exists that we might
+ * be interested in
+ */
+void TQTrayLabel::scanClients()
+{
+ Window r, parent, *children;
+ unsigned nchildren = 0;
+ Display *display = TQPaintDevice::x11AppDisplay();
+ TQString ename = TQFileInfo(appName()).fileName(); // strip out the path
+
+ XQueryTree(display, tqt_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(1) 0x%x", me(), (unsigned) w);
+ if (!isNormalWindow(display, w)) continue;
+ if (analyzeWindow(display, w, mPid, ename.local8Bit()))
+ {
+ TRACE("\t%s SOULMATE FOUND (1)", me());
+ setDockedWindow(w);
+ return;
+ }
+ }
+}
+
+/*
+ * 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 TQt (see below)
+ * 3) Checks health of the process whose windows we are docking
+ */
+void TQTrayLabel::realityCheck(void)
+{
+ if (mSysTray == None)
+ {
+ // Check the system tray status if we were docked
+ if (sysTrayStatus(TQPaintDevice::x11AppDisplay(), &mSysTray)
+ != SysTrayPresent) return; // no luck
+
+ TRACE("%s System tray present", me());
+ dock();
+ subscribe(TQPaintDevice::x11AppDisplay(), mSysTray,
+ StructureNotifyMask, true);
+ mRealityMonitor.stop();
+ return;
+ }
+
+ /*
+ * I am not sure when, but TQt 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
+ * TQt 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 TQt 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 = TQPaintDevice::x11AppDisplay();
+ XWindowAttributes attr;
+ XGetWindowAttributes(display, tqt_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 TQTrayLabel::showOnAllDesktops(void)
+{
+ TRACE("Showing on all desktops");
+ Display *d = TQPaintDevice::x11AppDisplay();
+ long l[5] = { -1, 0, 0, 0, 0 }; // -1 = all, 0 = Desktop1, 1 = Desktop2 ...
+ sendMessage(d, tqt_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 TQTrayLabel::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 = TQPaintDevice::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);
+ }
+ mDockWhenRestored->setChecked(true);
+ setDockWhenRestored(true);
+
+ /*
+ * 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
+ */
+ TQTimer::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 TQTrayLabel::undock(void)
+{
+ TRACE("%s stopping reality monitor", me());
+ mRealityMonitor.stop();
+ XUnmapWindow(TQPaintDevice::x11AppDisplay(), winId());
+ emit undocked(this);
+ emit undocked();
+}
+
+/*
+ * Maps the window from the same place it was withdrawn from
+ */
+void TQTrayLabel::map(void)
+{
+ TRACE("%s", me());
+ mWithdrawn = false;
+ if (mDockedWindow == None) return;
+
+ Display *display = TQPaintDevice::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().
+ */
+ TQTimer::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, tqt_xrootwin(), mDockedWindow, "_NET_ACTIVE_WINDOW", 32,
+ SubstructureNotifyMask | SubstructureRedirectMask, l, sizeof(l));
+ // skipTaskbar modifies _NET_WM_STATE. Make sure we dont override WMs value
+ TQTimer::singleShot(230, this, SLOT(skipTaskbar()));
+ // disable "dock when minized" for a short while since we went to Iconic state
+ // (when the window is mapped, often an IconicState WM_STATE message is sent too
+ // just before the NormalState)
+ toggleDockWhenMinimized();
+ TQTimer::singleShot(500, this, SLOT(toggleDockWhenMinimized()));
+}
+
+void TQTrayLabel::withdraw(void)
+{
+ TRACE("%s", me());
+ mWithdrawn = true;
+ if (mDockedWindow == None) return;
+
+ Display *display = TQPaintDevice::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 = tqt_xrootwin();
+ ev.window = mDockedWindow;
+ ev.from_configure = false;
+ XSendEvent(display, tqt_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 TQTrayLabel::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 = TQPaintDevice::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 (mSkipTaskbar->isChecked())
+ {
+ 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(),
+ mSkipTaskbar->isChecked(), append, replace);
+
+ if (mSkipTaskbar->isChecked())
+ {
+ 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 TQTrayLabel::setSkipTaskbar(bool skip)
+{
+ TRACE("%s skip=%i", me(), skip);
+ if (skip != mSkipTaskbar->isChecked())
+ {
+ // Make sure the toggle action state is updated in case this function
+ // is called directly from code.
+ mSkipTaskbar->setChecked(skip);
+ return;
+ }
+ if (mDockedWindow != None && !mWithdrawn)
+ {
+ skipTaskbar();
+ }
+}
+
+void TQTrayLabel::toggleShow(void)
+{
+ if (mWithdrawn)
+ {
+ map();
+ }
+ else
+ {
+ withdraw();
+ }
+}
+
+/*
+ * Closes a window by sending _NET_CLOSE_WINDOW. For reasons best unknown we
+ * need to first map and then send the request.
+ */
+void TQTrayLabel::close(void)
+{
+ TRACE("%s", me());
+ undock();
+ Display *display = TQPaintDevice::x11AppDisplay();
+ long l[5] = { 0, 0, 0, 0, 0 };
+ map();
+ sendMessage(display, tqt_xrootwin(), mDockedWindow, "_NET_CLOSE_WINDOW", 32,
+ SubstructureNotifyMask | SubstructureRedirectMask,
+ l, sizeof(l));
+}
+
+/*
+ * This function is called when TQTrayLabel wants to know whether it can
+ * unsubscribe from the root window. This is because it doesn't know if someone
+ * else is interested in root window events
+ */
+bool TQTrayLabel::canUnsubscribeFromRoot(void)
+{
+ return (TrayLabelMgr::instance())->hiddenLabelsCount() == 0;
+}
+
+/*
+ * Sets the tray icon. If the icon failed to load, we revert to application icon
+ */
+void TQTrayLabel::setTrayIcon(const TQString& icon)
+{
+ mCustomIcon = icon;
+ if (TQPixmap(mCustomIcon).isNull()) mCustomIcon = TQString::null;
+ TRACE("%s mCustomIcon=%s", me(), mCustomIcon.local8Bit());
+ 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 TQTrayLabel::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)
+ mDockedWindow = None;
+ if (w != None)
+ {
+ if (!(TrayLabelMgr::instance()->isWindowDocked(w)))
+ {
+ mDockedWindow = w;
+ }
+ }
+
+ if (mDockedWindow == None) mRealityMonitor.start(500); else mRealityMonitor.stop();
+
+ Display *d = TQPaintDevice::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
+ TQTimer::singleShot(500, this, SLOT(withdraw()));
+ }
+ else map();
+ dock();
+ }
+}
+
+/*
+ * Balloon text. Overload this if you dont like the way things are ballooned
+ */
+void TQTrayLabel::balloonText()
+{
+ TRACE("%s BalloonText=%s ToolTipText=%s", me(),
+ mBalloon->text().local8Bit(), TQToolTip::textFor(this).local8Bit());
+
+ if (mBalloon->text() == TQToolTip::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.local8Bit();
+ 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 TQt ToolTip code
+ TQString oldText = mBalloon->text();
+ mBalloon->setText(TQToolTip::textFor(this));
+ if (oldText.isEmpty()) return; // dont tool tip the first time
+ TQPoint p = mapToGlobal(TQPoint(0, -1 - mBalloon->height()));
+ if (p.x() + mBalloon->width() > TQApplication::desktop()->width())
+ p.setX(p.x() + width() - mBalloon->width());
+
+ if (p.y() < 0) p.setY(height() + 1);
+
+ mBalloon->move(p);
+ mBalloon->show();
+ TQTimer::singleShot(mBalloonTimeout, mBalloon, SLOT(hide()));
+#endif
+}
+
+/*
+ * Update the title in the menu. Balloon the title change if necessary
+ */
+void TQTrayLabel::handleTitleChange(void)
+{
+ Display *display = TQPaintDevice::x11AppDisplay();
+ char *window_name = NULL;
+
+ XFetchName(display, mDockedWindow, &window_name);
+ mTitle = window_name;
+ TRACE("%s has title [%s]", me(), mTitle.local8Bit());
+ if (window_name) XFree(window_name);
+
+ XClassHint ch;
+ if (XGetClassHint(display, mDockedWindow, &ch))
+ {
+ if (ch.res_class) mClass = TQString(ch.res_class);
+ else if (ch.res_name) mClass = TQString(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 TQTrayLabel::updateTitle()
+{
+ TRACE("%s", me());
+ TQString text = mTitle + " [" + mClass + "]";
+ TQToolTip::remove(this);
+ TQToolTip::add(this, text);
+
+ if (mBalloonTimeout) balloonText();
+}
+
+void TQTrayLabel::handleIconChange(void)
+{
+ char **window_icon = NULL;
+
+ TRACE("%s", me());
+ if (mDockedWindow == None) return;
+
+ Display *display = TQPaintDevice::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);
+ }
+ TQImage image;
+ if (!window_icon)
+ {
+ image = TDEGlobal::iconLoader()->loadIcon("question", TDEIcon::NoGroup, TDEIcon::SizeMedium);
+ }
+ else image = TQPixmap((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 TQTrayLabel::updateIcon()
+{
+ TRACE("%s", me());
+ setPixmap(mCustomIcon.isEmpty() ? mAppIcon : mCustomIcon);
+ erase();
+ TQPaintEvent pe(rect());
+ paintEvent(&pe);
+}
+
+/*
+ * Mouse activity on our label. RightClick = Menu. LeftClick = Toggle Map
+ */
+void TQTrayLabel::mouseReleaseEvent(TQMouseEvent *ev)
+{
+ if (ev->button() == TQt::RightButton)
+ {
+ mMainMenu->popup(ev->globalPos());
+/* contextMenuAboutToShow(contextMenu());
+ contextMenu()->popup(e->globalPos());
+ e->accept();
+ return;*/
+ }
+ else
+ toggleShow();
+}
+
+/*
+ * Track drag event
+ */
+void TQTrayLabel::dragEnterEvent(TQDragEnterEvent *ev)
+{
+ ev->accept();
+ map();
+}
+
+void TQTrayLabel::dropEvent(TQDropEvent *)
+{
+ KMessageBox::error(NULL, i18n("You cannot drop an item into the tray icon. Drop it on the window\n"
+ "that is brought in front when you hover the item over the tray icon"), i18n("TDEDocker"));
+}
+
+/*
+ * Event dispatcher
+ */
+bool TQTrayLabel::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 = TQPaintDevice::x11AppDisplay();
+ Window w = XmuClientWindow(display, ((XMapEvent *) event)->window);
+ if (!isNormalWindow(display, w)) return FALSE;
+ if (!analyzeWindow(display, w, mPid, TQFileInfo(appName()).fileName().local8Bit()))
+ {
+ return false;
+ }
+ // All right. Lets dock this baby
+ setDockedWindow(w);
+ return true;
+}
+
+void TQTrayLabel::destroyEvent(void)
+{
+ TRACE("%s destroyEvent", me());
+ mUndockWhenDead = true;
+ setDockedWindow(None);
+ if (!mPid)
+ {
+ undock();
+ }
+}
+
+void TQTrayLabel::focusLostEvent()
+{
+ if (mDockWhenFocusLost->isChecked())
+ {
+ withdraw();
+ }
+}
+
+void TQTrayLabel::mapEvent(void)
+{
+ TRACE("mapEvent");
+ if (mDockWhenObscured->isChecked())
+ {
+ /*
+ * We get a obscured event for the time between the map and focus in of
+ * the window. So we disable it for sometime and reanable.
+ */
+ mDockWhenObscured->setChecked(false);
+ TQTimer::singleShot(800, mDockWhenObscured, SLOT(toggle()));
+ TRACE("Turning off DWO for some time");
+ }
+}
+
+void TQTrayLabel::minimizeEvent(void)
+{
+ TRACE("minimizeEvent");
+ if (mDockWhenMinimized->isChecked())
+ {
+ withdraw();
+ }
+}
+
+void TQTrayLabel::obscureEvent(void)
+{
+ TRACE("obscureEvent");
+ if (mDockWhenObscured->isChecked() && !mWithdrawn)
+ {
+ withdraw();
+ }
+}
+
+void TQTrayLabel::unmapEvent(void)
+{
+ // NO OP
+}
+
+void TQTrayLabel::propertyChangeEvent(Atom property)
+{
+ Display *display = TQPaintDevice::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);
+ }
+ }
+}
+
+void TQTrayLabel::processDead(void)
+{
+ /*
+ * This is a ugly hack but worth every but of ugliness IMO ;).
+ * Lets say, an instance of xmms, already exists. You type tdedocker xmms.
+ * TDEDocker launches xmms. xmms cowardly exists seeing its previous instance.
+ * Wouldnt it be nice now to dock the previous instance of xmms automatically.
+ * This is more common than you think (think of session restoration)
+ */
+
+ if (!mUndockWhenDead)
+ {
+ scanClients();
+ if (dockedWindow() != None) return;
+ }
+ undock();
+}
+
+void TQTrayLabel::setDockWhenRestored(bool dwr)
+{
+ if (dwr && !mSessionManaged)
+ {
+ // Make sure the TDE action is off if session management was initially disabled by command line
+ mSessionManaged = true;
+ mDockWhenRestored->setChecked(false);
+ return;
+ }
+
+ if (dwr && appName().isEmpty())
+ {
+ KMessageBox::error(NULL, i18n("No valid application executable file known. \"Dock When Restore\" is not possible."),
+ i18n("TDEDocker"));
+ mDockWhenRestored->setChecked(false);
+ }
+}
+
+// Get icon from user, load it and if successful load it.
+void TQTrayLabel::setCustomIcon(void)
+{
+ TQString icon;
+
+ while (true)
+ {
+ // Nag the user to give us a valid icon or press cancel
+ icon = TQFileDialog::getOpenFileName();
+ if (icon.isNull()) return; // user cancelled
+ if (!TQPixmap(icon).isNull()) break;
+ TRACE("Attempting to set icon to %s", icon.local8Bit());
+ KMessageBox::error(this, i18n("%1 is not a valid icon").arg(icon), i18n("TDEDocker"));
+ }
+
+ setTrayIcon(icon);
+}
+
+// Get balloon timeout from the user
+void TQTrayLabel::slotSetBalloonTimeout(void)
+{
+ bool ok;
+ int timeout = TQInputDialog::getInteger(i18n("TDEDocker"),
+ i18n("Enter balloon timeout (secs). 0 to disable ballooning"),
+ balloonTimeout()/1000, 0, 60, 1, &ok);
+
+ if (!ok) return;
+ setBalloonTimeout(timeout * 1000);
+}
+
+// Installs a popup menu on the tray label
+void TQTrayLabel::installMenu()
+{
+ TQPixmap tdedocker_png(TDEGlobal::iconLoader()->loadIcon("tdedocker", TDEIcon::NoGroup, TDEIcon::SizeSmall));
+ setIcon(tdedocker_png);
+ TrayLabelMgr *tlMgr = TrayLabelMgr::instance();
+
+ mOptionsMenu = new TDEPopupMenu(this);
+ mDockWhenRestored = new TDEToggleAction(i18n("Dock when session restored"), 0, this);
+ connect(mDockWhenRestored, SIGNAL(toggled(bool)), this, SLOT(setDockWhenRestored(bool)));
+ mDockWhenRestored->plug(mOptionsMenu);
+
+ mOptionsMenu->insertItem(i18n("Set Icon"), this, SLOT(setCustomIcon()));
+
+ mBalloonTimeoutAction = new TDEAction(i18n("Set balloon timeout"), 0, this);
+ connect(mBalloonTimeoutAction, SIGNAL(activated()), this, SLOT(slotSetBalloonTimeout()));
+ mBalloonTimeoutAction->plug(mOptionsMenu);
+
+ mDockWhenObscured = new TDEToggleAction(i18n("Dock when obscured"), 0, this);
+ mDockWhenObscured->plug(mOptionsMenu);
+
+ mDockWhenMinimized = new TDEToggleAction(i18n("Dock when minimized"), 0, this);
+ mDockWhenMinimized->plug(mOptionsMenu);
+
+ mDockWhenFocusLost = new TDEToggleAction(i18n("Dock when focus lost"), 0, this);
+ mDockWhenFocusLost->plug(mOptionsMenu);
+
+ mSkipTaskbar = new TDEToggleAction(i18n("Skip taskbar"), 0, this);
+ connect(mSkipTaskbar, SIGNAL(toggled(bool)), this, SLOT(setSkipTaskbar(bool)));
+ mSkipTaskbar->plug(mOptionsMenu);
+
+ mMainMenu = new TDEPopupMenu(this);
+ mMainMenu->insertItem(i18n("Options"), mOptionsMenu);
+ mMainMenu->insertItem(i18n("Dock Another"), tlMgr, SLOT(dockAnother()));
+ mMainMenu->insertItem(i18n("Undock All"), tlMgr, SLOT(undockAll()));
+ mMainMenu->insertItem(i18n("Quit All"), tlMgr, SLOT(quitAll()));
+ mMainMenu->insertSeparator();
+
+ mShowId = mMainMenu->insertItem(TQString("Show/Hide [untitled]"), this, SLOT(toggleShow()));
+ mMainMenu->insertItem(TQString(i18n("Undock")), this, SLOT(undock()));
+ mMainMenu->insertSeparator();
+
+ mMainMenu->insertItem(SmallIcon("help"),KStdGuiItem::help().text(), (new KHelpMenu(this, TDEGlobal::instance()->aboutData()))->menu(), false);
+ TDEAction *quitAction = KStdAction::quit(this, SLOT(close()), NULL);
+ quitAction->plug(mMainMenu);
+
+ connect(mMainMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu()));
+
+ // Apply defaults here
+ mDockWhenObscured->setChecked(false);
+ mSessionManaged = true;
+ mDockWhenMinimized->setChecked(true);
+ mSkipTaskbar->setChecked(false);
+ setAcceptDrops(true); // and you thought this function only installs the menu
+}
+
+// Called when we are just about to display the menu
+void TQTrayLabel::updateMenu(void)
+{
+ TQString title = mClass; // + "(" + mTitle + ")";
+ mMainMenu->changeItem(mShowId, TQIconSet(*pixmap()),
+ TQString((mWithdrawn ? i18n("Show %1") : i18n("Hide %1")).arg(title)));
+}
+
+// Session Management
+bool TQTrayLabel::saveState(TDEConfig *config)
+{
+ TRACE("%s saving state", me());
+
+ if (!mDockWhenRestored->isChecked())
+ {
+ return false;
+ }
+
+ config->writeEntry("Application", mProgName.join(" "));
+ config->writeEntry("BalloonTimeout", mBalloonTimeout);
+ config->writeEntry("CustomIcon", mCustomIcon);
+ config->writeEntry("DockWhenFocusLost", mDockWhenFocusLost->isChecked());
+ config->writeEntry("DockWhenMinimized", mDockWhenMinimized->isChecked());
+ config->writeEntry("DockWhenObscured", mDockWhenObscured->isChecked());
+ config->writeEntry("SkipTaskbar", mSkipTaskbar->isChecked());
+ config->writeEntry("Withdraw", mWithdrawn);
+ return true;
+}
+
+bool TQTrayLabel::restoreState(TDEConfig *config)
+{
+ TRACE("%s restoring state", me());
+ setBalloonTimeout(config->readNumEntry("BalloonTimeout", 4000));
+ mCustomIcon = config->readEntry("CustomIcon", TQString::null);
+ mDockWhenFocusLost->setChecked(config->readBoolEntry("DockWhenFocusLost", false));
+ mDockWhenMinimized->setChecked(config->readBoolEntry("DockWhenMinimized", true));
+ mDockWhenObscured->setChecked(config->readBoolEntry("DockWhenObscured", false));
+ mSkipTaskbar->setChecked(config->readBoolEntry("SkipTaskbar", false));
+ mWithdrawn = config->readBoolEntry("Withdraw", false);
+
+ dock();
+ scanClients(); // Grab window
+ if (mWithdrawn)
+ {
+ withdraw();
+ }
+ else
+ {
+ map();
+ }
+ return true;
+}
+
+// End kicking butt
+
+
+#include "tqtraylabel.moc"