diff options
author | Michele Calgaro <[email protected]> | 2020-03-31 22:41:41 +0900 |
---|---|---|
committer | Michele Calgaro <[email protected]> | 2020-03-31 22:41:41 +0900 |
commit | 6fcc661ff72af5a957a1a8d2737d2327dba8841e (patch) | |
tree | 284b56632512e37875a8a19a5792551e147402d1 /src/tqtraylabel.cpp | |
parent | 05add324c22f8fb2d1824a8a6145b2b8821e95bb (diff) | |
download | tdedocker-6fcc661ff72af5a957a1a8d2737d2327dba8841e.tar.gz tdedocker-6fcc661ff72af5a957a1a8d2737d2327dba8841e.zip |
1) fixed bug when invoking tdedocker with options, which were not passed
to an existing instance
2) code restructure for tray label object
3) dockWhenLostFocus option is now saved/restored correctly
4) fixed issues with popup menu actions status
Signed-off-by: Michele Calgaro <[email protected]>
Diffstat (limited to 'src/tqtraylabel.cpp')
-rw-r--r-- | src/tqtraylabel.cpp | 1086 |
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" |