summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/customtraylabel.cpp360
-rw-r--r--src/customtraylabel.h90
-rw-r--r--src/kdocker.cpp249
-rw-r--r--src/kdocker.h59
-rw-r--r--src/kdocker.xpm304
-rw-r--r--src/main.cpp56
-rw-r--r--src/qtraylabel.cpp809
-rw-r--r--src/qtraylabel.h161
-rw-r--r--src/question.xpm39
-rw-r--r--src/trace.cpp44
-rw-r--r--src/trace.h30
-rw-r--r--src/traylabelmgr.cpp523
-rw-r--r--src/traylabelmgr.h81
-rw-r--r--src/util.cpp324
-rw-r--r--src/util.h47
15 files changed, 3176 insertions, 0 deletions
diff --git a/src/customtraylabel.cpp b/src/customtraylabel.cpp
new file mode 100644
index 0000000..aca36e1
--- /dev/null
+++ b/src/customtraylabel.cpp
@@ -0,0 +1,360 @@
+/*
+ * 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: customtraylabel.cpp,v 1.14 2005/06/21 10:04:35 cs19713 Exp $
+
+#include <qsettings.h>
+#include <qpopupmenu.h>
+#include <qmessagebox.h>
+#include <qfiledialog.h>
+#include <qinputdialog.h>
+#include <qaction.h>
+#include <qtimer.h>
+#include <qsize.h>
+#include <stdlib.h>
+
+#include "trace.h"
+#include "customtraylabel.h"
+#include "traylabelmgr.h"
+#include "kdocker.h"
+
+CustomTrayLabel::CustomTrayLabel(Window w, QWidget* p, const QString& t)
+ : QTrayLabel(w, p, t), mUndockWhenDead(false)
+{
+ installMenu();
+}
+
+CustomTrayLabel::CustomTrayLabel(const QStringList& argv, pid_t pid,
+ QWidget* parent)
+ : QTrayLabel(argv, pid, parent), mUndockWhenDead(false)
+{
+ installMenu();
+}
+
+/*
+ * Installs a popup menu on the tray label
+ */
+void CustomTrayLabel::installMenu()
+{
+ QPixmap kdocker_png(QString(ICONS_PATH) + "/kdocker.png");
+ if (kdocker_png.isNull())
+ kdocker_png.load(qApp->applicationDirPath() + "/icons/kdocker.png");
+ setIcon(kdocker_png);
+ TrayLabelMgr *tlMgr = TrayLabelMgr::instance();
+
+ mOptionsMenu = new QPopupMenu(this);
+ mSessionManagement = new QAction(tr("Dock when session restored"), 0, this);
+ mSessionManagement->setToggleAction(true);
+ connect(mSessionManagement, SIGNAL(toggled(bool)),
+ this, SLOT(enableSessionManagement(bool)));
+ mSessionManagement->addTo(mOptionsMenu);
+
+ mAutoLaunch = new QAction(tr("Launch on startup"), 0, this);
+ mAutoLaunch->setToggleAction(true);
+ connect(mAutoLaunch, SIGNAL(activated()),
+ this, SLOT(slotSetLaunchOnStartup()));
+ mAutoLaunch->addTo(mOptionsMenu);
+
+ mOptionsMenu->insertItem(tr("Set Icon"), this, SLOT(setCustomIcon()));
+
+ mBalloonTimeout = new QAction(tr("Set balloon timeout"), 0, this);
+ connect(mBalloonTimeout, SIGNAL(activated()),
+ this, SLOT(slotSetBalloonTimeout()));
+ mBalloonTimeout->addTo(mOptionsMenu);
+
+ mDockWhenObscured = new QAction(tr("Dock when obscured"), 0, this);
+ mDockWhenObscured->setToggleAction(true);
+ connect(mDockWhenObscured, SIGNAL(toggled(bool)),
+ this, SLOT(setDockWhenObscured(bool)));
+ mDockWhenObscured->addTo(mOptionsMenu);
+
+ mDockWhenMinimized = new QAction(tr("Dock when minimized"), 0, this);
+ mDockWhenMinimized->setToggleAction(true);
+ connect(mDockWhenMinimized, SIGNAL(toggled(bool)),
+ this, SLOT(setDockWhenMinimized(bool)));
+ mDockWhenMinimized->addTo(mOptionsMenu);
+
+ mDockWhenFocusLost = new QAction(tr("Dock when focus lost"), 0, this);
+ mDockWhenFocusLost->setToggleAction(true);
+ connect(mDockWhenFocusLost, SIGNAL(toggled(bool)),
+ this, SLOT(setDockWhenFocusLost(bool)));
+ mDockWhenFocusLost->addTo(mOptionsMenu);
+
+ mSkipTaskbar = new QAction(tr("Skip taskbar"), 0, this);
+ mSkipTaskbar->setToggleAction(true);
+ connect(mSkipTaskbar, SIGNAL(toggled(bool)),
+ this, SLOT(setSkipTaskbar(bool)));
+ mSkipTaskbar->addTo(mOptionsMenu);
+
+ mMainMenu = new QPopupMenu(this);
+ mMainMenu->insertItem(QIconSet(kdocker_png),
+ tr("About KDocker"), tlMgr, SLOT(about()));
+ mMainMenu->insertSeparator();
+ mMainMenu->insertItem(tr("Options"), mOptionsMenu);
+ mMainMenu->insertItem(tr("Dock Another"), tlMgr, SLOT(dockAnother()));
+ mMainMenu->insertItem(tr("Undock All"), tlMgr, SLOT(undockAll()));
+ mMainMenu->insertSeparator();
+
+ mShowId = mMainMenu->insertItem(QString("Show/Hide [untitled]"),
+ this, SLOT(toggleShow()));
+ mMainMenu->insertItem(QString(tr("Undock")), this, SLOT(undock()));
+ mMainMenu->insertItem(QString(tr("Close")), this, SLOT(close()));
+
+ connect(mMainMenu, SIGNAL(aboutToShow()), this, SLOT(updateMenu()));
+
+ // Apply defaults here
+ setLaunchOnStartup(false);
+ setDockWhenObscured(false);
+ enableSessionManagement(true);
+ mDockWhenMinimized->setOn(isDockWhenMinimized());
+ mSkipTaskbar->setOn(isSkippingTaskbar());
+ setAcceptDrops(true); // and you thought this function only installs the menu
+}
+
+/*
+ * Session Management
+ */
+bool CustomTrayLabel::restoreState(QSettings& settings)
+{
+ mAutoLaunch->setOn(settings.readBoolEntry("/LaunchOnStartup"));
+ setDockWhenObscured(settings.readBoolEntry("/DockWhenObscured"));
+ TRACE("AutoLaunch=%i DWM=%i DWO=%i", isLaunchOnStartup(),
+ isDockWhenMinimized(), isDockWhenObscured());
+ return QTrayLabel::restoreState(settings);
+}
+
+bool CustomTrayLabel::saveState(QSettings& settings)
+{
+ if (!mSessionManagement->isOn()) return false;
+
+ QTrayLabel::saveState(settings);
+ settings.writeEntry("/LaunchOnStartup", isLaunchOnStartup());
+ settings.writeEntry("/DockWhenObscured", isDockWhenObscured());
+ TRACE("AutoLaunch=%i DWM=%i DWO=%i", isLaunchOnStartup(),
+ isDockWhenMinimized(), isDockWhenObscured());
+ return true;
+}
+
+static bool which(const char *app)
+{
+ if (access(app, X_OK) == 0) return true;
+
+ // Check if the program exist in the $PATH
+ char *path = strdup(getenv("PATH"));
+ char prog[300];
+ if (path == NULL) return false;
+ TRACE("PATH=%s", path);
+ char *p = strtok(path, ":");
+ while (p != NULL)
+ {
+ snprintf(prog, sizeof(prog), "%s/%s", p, app);
+ if (access(prog, X_OK) == 0) break;
+ p = strtok(NULL, ":");
+ }
+ free(path);
+ TRACE("Located at (%s)", p);
+ return p != NULL;
+}
+
+// Overridden to update our menu
+void CustomTrayLabel::setDockWhenMinimized(bool dwm)
+{
+ QTrayLabel::setDockWhenMinimized(dwm);
+ mDockWhenMinimized->setOn(isDockWhenMinimized());
+}
+
+void CustomTrayLabel::setSkipTaskbar(bool skip)
+{
+ QTrayLabel::setSkipTaskbar(skip);
+ mSkipTaskbar->setOn(isSkippingTaskbar());
+}
+
+void CustomTrayLabel::setAppName(const QString& name)
+{
+ QTrayLabel::setAppName(name.lower());
+}
+
+/*
+ * This function is called when QTrayLabel wants to know whether it can
+ * unsubscribe from root window. This is because it doesnt know if someone
+ * else is interested in root window events
+ */
+bool CustomTrayLabel::canUnsubscribeFromRoot(void)
+{
+ return (TrayLabelMgr::instance())->hiddenLabelsCount() == 0;
+}
+
+// Get icon from user, load it and if successful load it.
+void CustomTrayLabel::setCustomIcon(void)
+{
+ QString icon;
+
+ while (true)
+ {
+ // Nag the user to give us a valid icon or press cancel
+ icon = QFileDialog::getOpenFileName();
+ if (icon.isNull()) return; // user cancelled
+ if (!QPixmap(icon).isNull()) break;
+ TRACE("Attempting to set icon to %s", icon.latin1());
+ QMessageBox::critical(this, tr("KDocker"),
+ tr("%1 is not a valid icon").arg(icon));
+ }
+
+ setTrayIcon(icon);
+}
+
+// Get balloon timeout from the user
+void CustomTrayLabel::slotSetBalloonTimeout(void)
+{
+ bool ok;
+ int timeout = QInputDialog::getInteger(tr("KDocker"),
+ tr("Enter balloon timeout (secs). 0 to disable ballooning"),
+ balloonTimeout()/1000, 0, 60, 1, &ok);
+
+ if (!ok) return;
+ setBalloonTimeout(timeout * 1000);
+}
+
+void CustomTrayLabel::setLaunchOnStartup(bool launch)
+{
+ mAutoLaunch->setOn(launch);
+ slotSetLaunchOnStartup(); // fake an "activated" signal
+}
+
+void CustomTrayLabel::slotSetLaunchOnStartup()
+{
+ TRACE("%i", mAutoLaunch->isOn());
+ if (!mAutoLaunch->isOn()) return;
+ QString app = appName();
+
+ TRACE("Validating %s", app.latin1());
+
+ while (true)
+ {
+ if (which(app.latin1()))
+ {
+ TRACE("Autolaunch enabled to %s", app.latin1());
+ setAppName(app);
+ mAutoLaunch->setOn(true);
+ return;
+ }
+
+ // Request user to provide file name himself
+ if (QMessageBox::critical(NULL, tr("KDocker"),
+ tr("\"%1\" is not a valid executable "
+ "or was not found in your $PATH").arg(app),
+ tr("Select program"), tr("Cancel")) == 1)
+ {
+ mAutoLaunch->setOn(false);
+ return; // cancelled
+ }
+
+ app = QFileDialog::getOpenFileName();
+ if (app.isNull())
+ {
+ TRACE("Disabling auto launch");
+ mAutoLaunch->setOn(false);
+ return;
+ }
+ }
+}
+
+// Called when we are just about to display the menu
+void CustomTrayLabel::updateMenu(void)
+{
+ QString title = appClass(); // + "(" + appTitle() + ")";
+ mMainMenu->changeItem(mShowId, QIconSet(*pixmap()),
+ QString((isWithdrawn() ? tr("Show %1") : tr("Hide %1")).arg(title)));
+}
+
+void CustomTrayLabel::mapEvent(void)
+{
+ TRACE("mapEvent");
+ if (mDockWhenObscured->isOn())
+ {
+ /*
+ * 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->setOn(false);
+ QTimer::singleShot(800, mDockWhenObscured, SLOT(toggle()));
+ TRACE("Turning off DWO for some time");
+ }
+}
+
+void CustomTrayLabel::obscureEvent(void)
+{
+ TRACE("obscureEvent");
+ if (mDockWhenObscured->isOn() && !isWithdrawn())
+ withdraw();
+}
+
+void CustomTrayLabel::focusLostEvent()
+{
+ if (mDockWhenFocusLost->isOn()) withdraw();
+}
+
+void CustomTrayLabel::mouseReleaseEvent(QMouseEvent * ev)
+{
+ if (ev->button() == Qt::RightButton)
+ mMainMenu->popup(ev->globalPos());
+ else
+ toggleShow();
+}
+
+void CustomTrayLabel::destroyEvent(void)
+{
+ mUndockWhenDead = true;
+ QTrayLabel::destroyEvent();
+}
+
+void CustomTrayLabel::processDead(void)
+{
+ /*
+ * This is a ugly hack but worth every but of ugliness IMO ;).
+ * Lets say, an instance of xmms, already exists. You type kdocker xmms.
+ * KDocker 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();
+}
+
+/*
+ * Can dock this window iff not docked by another one tray label already
+ */
+bool CustomTrayLabel::canDockWindow(Window w)
+{
+ TRACE("Checking if 0x%x is already docked", (unsigned) w);
+ return !(TrayLabelMgr::instance()->isWindowDocked(w));
+}
+
+void CustomTrayLabel::dropEvent(QDropEvent *)
+{
+ QMessageBox::information(NULL, "KDocker",
+ tr("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"));
+}
+
diff --git a/src/customtraylabel.h b/src/customtraylabel.h
new file mode 100644
index 0000000..c12bcc2
--- /dev/null
+++ b/src/customtraylabel.h
@@ -0,0 +1,90 @@
+/*
+ * 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: customtraylabel.h,v 1.10 2007/01/02 21:48:37 cs19713 Exp $
+
+#ifndef _CUSTOMTRAYLABEL_H
+#define _CUSTOMTRAYLABEL_H
+
+#include <qobject.h>
+#include <qaction.h>
+#include "qtraylabel.h"
+
+class QStringList;
+class QPopupMenu;
+class QString;
+class QSettings;
+class QWidget;
+class QDropEvent;
+
+class CustomTrayLabel : public QTrayLabel
+{
+ Q_OBJECT
+
+public:
+ CustomTrayLabel(Window w, QWidget* p = 0, const QString& t = QString::null);
+ CustomTrayLabel(const QStringList& argv, pid_t pid, QWidget* parent = 0);
+
+ // Session management
+ bool saveState(QSettings& settings);
+ bool restoreState(QSettings& settings);
+
+ bool isLaunchOnStartup(void) const { return mAutoLaunch->isOn(); }
+ bool isDockWhenObscured(void) const { return mDockWhenObscured->isOn(); }
+
+ void setAppName(const QString& name);
+
+public slots:
+ // overridden to update our menu
+ void setDockWhenMinimized(bool dwm);
+ void setDockWhenFocusLost(bool dfl) { mDockWhenFocusLost->setOn(dfl); }
+ void setSkipTaskbar(bool skip);
+
+ void setLaunchOnStartup(bool launch);
+ void setDockWhenObscured(bool dock) { mDockWhenObscured->setOn(dock); }
+ void enableSessionManagement(bool sm) { mSessionManagement->setOn(sm); }
+
+protected:
+ void dropEvent(QDropEvent *ev);
+ bool canUnsubscribeFromRoot(void);
+ void mapEvent(void);
+ void focusLostEvent();
+ void obscureEvent(void);
+ void destroyEvent(void);
+ void mouseReleaseEvent(QMouseEvent * ev);
+ bool canDockWindow(Window w);
+ void processDead(void);
+
+private slots:
+ void setCustomIcon(void);
+ void updateMenu();
+ void slotSetBalloonTimeout(void);
+ void slotSetLaunchOnStartup(void);
+
+private:
+ void installMenu();
+ bool mUndockWhenDead;
+ QPopupMenu *mOptionsMenu, *mMainMenu;
+ QAction *mDockOnRestore, *mAutoLaunch, *mBalloonTimeout, *mSkipTaskbar,
+ *mDockWhenMinimized, *mDockWhenObscured, *mSessionManagement,
+ *mDockWhenFocusLost;
+ int mShowId;
+};
+
+#endif // _CUSTOMTRAYLABEL_H
diff --git a/src/kdocker.cpp b/src/kdocker.cpp
new file mode 100644
index 0000000..40cf00a
--- /dev/null
+++ b/src/kdocker.cpp
@@ -0,0 +1,249 @@
+/*
+ * 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: kdocker.cpp,v 1.24 2005/02/04 10:25:46 cs19713 Exp $
+
+#include <qsessionmanager.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qtranslator.h>
+#include <qtextcodec.h>
+#include <qtextstream.h>
+#include <qtimer.h>
+#include <qstring.h>
+
+#include "trace.h"
+#include "traylabelmgr.h"
+#include "kdocker.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// #define TMPFILE_PREFIX QString("/tmp/kdocker.")
+#define TMPFILE_PREFIX QDir::homeDirPath() + "/.kdocker."
+
+KDocker::KDocker(int& argc, char** argv)
+ :QApplication(argc, argv), mTrayLabelMgr(0)
+{
+ INIT_TRACE();
+
+ /*
+ * Load localisation strings. Most examples I have seen load QTranslator
+ * in main(). As a result the translator object lingers around till the end
+ * of the program. I tried the same thing here and all i got was translations
+ * for usage(). You dont want to know about the sleepless night i spent
+ * trying to figure this out (yup, the source helped)
+ */
+ QTranslator *translator = new QTranslator(0);
+ QString f = QString("kdocker_") + QTextCodec::locale();
+
+ if (!translator->load(f, QString(TRANSLATIONS_PATH)) &&
+ !translator->load(f, applicationDirPath() + "/i18n") &&
+ !translator->load(f, QDir::currentDirPath() + "/i18n")) {
+ qDebug("Sorry, your locale is not supported. If you are interested "
+ "in providing translations for your locale, contact "
+ }
+ installTranslator(translator);
+
+ // Attempt doing anything only if the CLI arguments were good
+ opterr = 0; // suppress the warning
+ int option;
+ while ((option = getopt(argc, argv, TrayLabelMgr::options().latin1())) != EOF)
+ {
+ if (option == '?')
+ {
+ if (optopt == 'v') printVersion(); else printUsage(optopt);
+ ::exit(0);
+ }
+ }
+
+ /*
+ * Detect and transfer control to previous instance (if one exists)
+ * _KDOCKER_RUNNING is a X Selection. We start out by trying to locate the
+ * selection owner. If someone else owns it, transfer control to that
+ * instance of KDocker
+ */
+ Display *display = QPaintDevice::x11AppDisplay();
+ Atom kdocker = XInternAtom(display, "_KDOCKER_RUNNING", False);
+ Window prev_instance = XGetSelectionOwner(display, kdocker);
+
+ if (prev_instance == None)
+ {
+ mSelectionOwner = XCreateSimpleWindow(display, qt_xrootwin(), 1, 1, 1,
+ 1, 1, 1, 1);
+ XSetSelectionOwner(display, kdocker, mSelectionOwner, CurrentTime);
+ TRACE("Selection owner set to 0x%x", (unsigned) mSelectionOwner);
+ mTrayLabelMgr = TrayLabelMgr::instance();
+ }
+ else
+ notifyPreviousInstance(prev_instance); // does not return
+}
+
+void KDocker::printVersion(void)
+{
+ qDebug("Qt: %s", qVersion());
+ qDebug("KDocker: %s", KDOCKER_APP_VERSION);
+}
+
+// Prints the CLI arguments. Does not return
+void KDocker::printUsage(char optopt)
+{
+ if (optopt != 'h') qDebug(tr("kdocker: invalid option -- %1").arg(optopt));
+
+ qDebug(tr("Usage: KDocker [options] command\n"));
+ qDebug(tr("Docks any application into the system tray\n"));
+ qDebug(tr("command \tCommand to execute\n"));
+ qDebug(tr("Options"));
+ qDebug(tr("-a \tShow author information"));
+ qDebug(tr("-b \tDont warn about non-normal windows (blind mode)"));
+ qDebug(tr("-d \tDisable session management"));
+ qDebug(tr("-e \tEnable session management"));
+ qDebug(tr("-f \tDock window that has the focus(active window)"));
+ qDebug(tr("-h \tDisplay this help"));
+ qDebug(tr("-i icon\tCustom dock Icon"));
+ qDebug(tr("-l \tLaunch on startup"));
+ qDebug(tr("-m \tKeep application window mapped (dont hide on dock)"));
+ qDebug(tr("-o \tDock when obscured"));
+ qDebug(tr("-p secs\tSet ballooning timeout (popup time)"));
+ qDebug(tr("-q \tDisable ballooning title changes (quiet)"));
+ qDebug(tr("-t \tRemove this application from the task bar"));
+ qDebug(tr("-v \tDisplay version"));
+ qDebug(tr("-w wid \tWindow id of the application to dock\n"));
+
+ qDebug(tr("NOTE: Use -d for all startup scripts.\n"));
+
+ qDebug(tr("Bugs and wishes to [email protected]"));
+ qDebug(tr("Project information at http://kdocker.sourceforge.net"));
+}
+
+void KDocker::notifyPreviousInstance(Window prevInstance)
+{
+ Display *display = QPaintDevice::x11AppDisplay();
+
+ TRACE("Notifying previous instance [%x]", (unsigned) prevInstance);
+
+ // Dump all arguments in temporary file
+ QFile f(TMPFILE_PREFIX + QString().setNum(getpid()));
+ if (!f.open(IO_WriteOnly)) return;
+ QTextStream s(&f);
+
+ /*
+ * Its normal to use KDocker in startup scripts. We could be getting restored
+ * from a session at the same time. So, if we were getting restored and
+ * another instance already exists, send across the session id. Remember, qt
+ * strips out all the arguments that it understands. So need to do it by hand.
+ */
+ if (isSessionRestored())
+ s << argv()[0] << " " << "-session" << " " << sessionId();
+ else
+ for (int i = 0; i < argc(); i++) s << argv()[i] << " ";
+
+ f.close();
+
+ /*
+ * Now tell our previous instance that we came to pass. Actually, it can
+ * figure it out itself using PropertyNotify events but this is a lot nicer
+ */
+ XClientMessageEvent dock_event;
+ memset(&dock_event, 0, sizeof(XClientMessageEvent));
+ dock_event.display = display;
+ dock_event.window = prevInstance;
+ dock_event.send_event = True;
+ dock_event.type = ClientMessage;
+ dock_event.message_type = 0x220679; // it all started this day
+ dock_event.format = 8;
+ dock_event.data.l[0] = 0xBABE; // love letter ;)
+ dock_event.data.l[1] = getpid();
+ XSendEvent(display, prevInstance, False, 0, (XEvent *) &dock_event);
+ XSync(display, False);
+
+ ::exit(0);
+}
+
+/*
+ * The X11 Event filter called by Qt. Look out for ClientMessage events from
+ * our new instance
+ */
+bool KDocker::x11EventFilter(XEvent * event)
+{
+ if (event->type == ClientMessage)
+ {
+ // look for requests from a new instance of kdocker
+ XClientMessageEvent *client = (XClientMessageEvent *) event;
+ if (!(client->message_type == 0x220679 && client->data.l[0] == 0xBABE))
+ return FALSE;
+
+ TRACE("ClientMessage from PID=%ld. SelOwn=0x%x",
+ client->data.l[1], (unsigned) mSelectionOwner);
+ char tmp[50];
+ struct stat buf;
+ sprintf(tmp, TMPFILE_PREFIX "%ld", client->data.l[1]);
+ if (stat(tmp, &buf) || (getuid()!=buf.st_uid))
+ {
+ /*
+ * We make sure that the owner of this process and the owner of the file
+ * are the same. This will prevent someone from executing arbitrary
+ * programs by sending client message. Of course, you can send a message
+ * only if you are authenticated to the X session and have permission to
+ * create files in TMPFILE_PREFIX. So this code is there just for the
+ * heck of it.
+ */
+ TRACE("User %i is trying something fishy...", buf.st_uid);
+ unlink(tmp);
+ return TRUE;
+ }
+ QFile f(tmp);
+ if (!f.open(IO_ReadOnly)) return TRUE;
+ QTextStream s(&f);
+ QStringList argv;
+ while (!s.atEnd()) { QString x; s >> x; argv += x; }
+ f.close();
+ unlink(tmp); // delete the tmp file
+ mTrayLabelMgr->processCommand(argv);
+ return TRUE;
+ }
+ else return mTrayLabelMgr->x11EventFilter(event);
+}
+
+/*
+ * XSMP Support
+ */
+void KDocker::saveState(QSessionManager &sm)
+{
+ QString sf = mTrayLabelMgr->saveSession();
+
+ QStringList discard_command;
+ discard_command << "rm" << sf;
+ sm.setDiscardCommand(discard_command);
+
+ sm.setRestartHint(QSessionManager::RestartIfRunning);
+ QStringList restart_command;
+ restart_command << this->argv()[0]
+ << "-session" << sm.sessionId();
+ sm.setRestartCommand(restart_command);
+
+ TRACE("SessionFile=%s AppName=%s", sf.latin1(), this->argv()[0]);
+ DUMP_TRACE(QDir::homeDirPath() + "/kdocker.trace");
+ // sm.setRestartCommand(applicationFilePath());
+}
+
diff --git a/src/kdocker.h b/src/kdocker.h
new file mode 100644
index 0000000..f3126c3
--- /dev/null
+++ b/src/kdocker.h
@@ -0,0 +1,59 @@
+/*
+ * 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: kdocker.h,v 1.11 2005/02/09 03:32:26 cs19713 Exp $
+
+#ifndef _KDOCKER_H
+#define _KDOCKER_H
+
+#include <qapplication.h>
+#include <X11/Xlib.h>
+
+#define KDOCKER_APP_VERSION "1.3"
+
+class TrayLabelMgr;
+
+class KDocker : public QApplication
+{
+ Q_OBJECT
+
+public:
+ KDocker(int& argc, char** argv);
+
+ TrayLabelMgr *trayLabelMgr(void) { return mTrayLabelMgr; }
+
+ void dumpState(const QString &file);
+ void printUsage(char optopt = 'h');
+
+protected:
+ bool x11EventFilter(XEvent * event);
+ void saveState(QSessionManager &sm);
+
+private:
+ QString saveSession();
+ bool restoreSession();
+ void notifyPreviousInstance(Window prevInstance);
+
+ void printVersion();
+
+ Window mSelectionOwner;
+ TrayLabelMgr *mTrayLabelMgr;
+};
+
+#endif // _KDOCKER_H
diff --git a/src/kdocker.xpm b/src/kdocker.xpm
new file mode 100644
index 0000000..63fb2fa
--- /dev/null
+++ b/src/kdocker.xpm
@@ -0,0 +1,304 @@
+/* XPM */
+static char * kdocker_xpm[] = {
+"24 24 277 2",
+" c None",
+". c #0080FF",
+"+ c #007AF3",
+"@ c #349AFF",
+"# c #43A2FF",
+"$ c #43A1FE",
+"% c #3694F2",
+"& c #3B9AF8",
+"* c #409FFD",
+"= c #3F9EFD",
+"- c #3E9EFC",
+"; c #3E9DFC",
+"> c #1F87ED",
+", c #C5E2FE",
+"' c #FEFEFE",
+") c #E2E5E4",
+"! c #267126",
+"~ c #206D20",
+"{ c #327734",
+"] c #518754",
+"^ c #82A688",
+"/ c #BECBC9",
+"( c #DDE4EB",
+"_ c #74AEE8",
+": c #FAFBFC",
+"< c #EDF0F2",
+"[ c #8DAD92",
+"} c #3B7B3D",
+"| c #045F05",
+"1 c #005D00",
+"2 c #025F02",
+"3 c #056005",
+"4 c #37793C",
+"5 c #3D7F94",
+"6 c #F5F7FA",
+"7 c #F0F3F8",
+"8 c #EBF0F5",
+"9 c #E0E6ED",
+"0 c #ADC0B9",
+"a c #3B7B40",
+"b c #046004",
+"c c #539A48",
+"d c #4A9243",
+"e c #025E03",
+"f c #005100",
+"g c #FBFCFD",
+"h c #F6F8FA",
+"i c #F0F4F8",
+"j c #E5ECF3",
+"k c #E1E8F1",
+"l c #DCE5EE",
+"m c #D0DAE4",
+"n c #6D977A",
+"o c #82BB73",
+"p c #ADD69F",
+"q c #21741F",
+"r c #005700",
+"s c #FDFDFE",
+"t c #F8F9FB",
+"u c #F2F5F9",
+"v c #ECF1F6",
+"w c #E7EDF3",
+"x c #E1E9F1",
+"y c #D6E1EC",
+"z c #D1DDEA",
+"A c #CAD6E4",
+"B c #679276",
+"C c #096308",
+"D c #AEDB9B",
+"E c #CDEBBE",
+"F c #2E7C2B",
+"G c #005600",
+"H c #EFF3F7",
+"I c #E9EFF5",
+"J c #E3EAF2",
+"K c #DDE6EF",
+"L c #D8E2EC",
+"M c #D2DDEA",
+"N c #CDD9E7",
+"O c #C7D6E5",
+"P c #BBCADA",
+"Q c #307339",
+"R c #3E8B35",
+"S c #D2F5BA",
+"T c #C7E6B9",
+"U c #136A12",
+"V c #004C00",
+"W c #F9FBFC",
+"X c #F3F6F9",
+"Y c #EDF1F6",
+"Z c #DBE4EE",
+"` c #D5DFEB",
+" . c #CFDBE8",
+".. c #C9D7E6",
+"+. c #C3D2E3",
+"@. c #BECEE0",
+"#. c #82A29C",
+"$. c #035F02",
+"%. c #B0E294",
+"&. c #D4F5BD",
+"*. c #88BB7E",
+"=. c #005B00",
+"-. c #C5E1FE",
+";. c #F8FAFB",
+">. c #ECF0F6",
+",. c #DFE7F0",
+"'. c #D9E3ED",
+"). c #D3DEEA",
+"!. c #C6D5E4",
+"~. c #C0D0E2",
+"{. c #BACCDF",
+"]. c #A8BBCD",
+"^. c #116415",
+"/. c #74BA5C",
+"(. c #C5F2A7",
+"_. c #D1F2BB",
+":. c #156B13",
+"<. c #C4E1FE",
+"[. c #F2F5F8",
+"}. c #E5EBF3",
+"|. c #D9E2ED",
+"1. c #D2DEEA",
+"2. c #CCD9E7",
+"3. c #C6D4E4",
+"4. c #BFD0E1",
+"5. c #B6C7DA",
+"6. c #ACBFD3",
+"7. c #286E32",
+"8. c #52A23C",
+"9. c #B7EF90",
+"0. c #C7F3AA",
+"a. c #559B4C",
+"b. c #005400",
+"c. c #70B5F8",
+"d. c #8CC1F5",
+"e. c #89BEF3",
+"f. c #85BCF2",
+"g. c #82B9F0",
+"h. c #7EB6EE",
+"i. c #7BB4ED",
+"j. c #77B1EB",
+"k. c #74AFEA",
+"l. c #70ACE8",
+"m. c #689FD6",
+"n. c #2E7138",
+"o. c #276E30",
+"p. c #0E6312",
+"q. c #449B2D",
+"r. c #A8EC79",
+"s. c #B9F093",
+"t. c #7BBA68",
+"u. c #004E00",
+"v. c #004D00",
+"w. c #096702",
+"x. c #359A13",
+"y. c #6AC739",
+"z. c #99E963",
+"A. c #AAED7C",
+"B. c #9EDB7E",
+"C. c #3B8932",
+"D. c #196F16",
+"E. c #005C00",
+"F. c #015D00",
+"G. c #4DB915",
+"H. c #7AE233",
+"I. c #8BE64C",
+"J. c #9BEA66",
+"K. c #ACED80",
+"L. c #AFE68E",
+"M. c #176E14",
+"N. c #005000",
+"O. c #0E6F02",
+"P. c #68DB1B",
+"Q. c #7CE335",
+"R. c #8DE64F",
+"S. c #9DE968",
+"T. c #3D902D",
+"U. c #005800",
+"V. c #005A00",
+"W. c #319D07",
+"X. c #6DE01F",
+"Y. c #7EE338",
+"Z. c #5EB835",
+"`. c #046200",
+" + c #52CA09",
+".+ c #65D41E",
+"++ c #0A6805",
+"@+ c #9C9CA1",
+"#+ c #95959A",
+"$+ c #E0E0E1",
+"%+ c #C7C7C9",
+"&+ c #CDCDCF",
+"*+ c #005500",
+"=+ c #1A8100",
+"-+ c #1C8302",
+";+ c #BAB9BA",
+">+ c #F2DEC0",
+",+ c #F8F8F9",
+"'+ c #EBEBED",
+")+ c #065F06",
+"!+ c #086007",
+"~+ c #B8B6B3",
+"{+ c #E0CDAC",
+"]+ c #FAD468",
+"^+ c #EFE9D9",
+"/+ c #E5DBBD",
+"(+ c #B5B5BF",
+"_+ c #C5C3B7",
+":+ c #BEC1B4",
+"<+ c #85A0A1",
+"[+ c #B6BDB7",
+"}+ c #E1DECF",
+"|+ c #D3D1C3",
+"1+ c #AABFCA",
+"2+ c #307877",
+"3+ c #367B7F",
+"4+ c #98B0C1",
+"5+ c #CCC9BC",
+"6+ c #B8B4AC",
+"7+ c #958E9D",
+"8+ c #BCBAAE",
+"9+ c #BEBCAF",
+"0+ c #D5D2C4",
+"a+ c #BAB4A8",
+"b+ c #F3E055",
+"c+ c #E8E2AA",
+"d+ c #DEC768",
+"e+ c #9998A1",
+"f+ c #CAC7BA",
+"g+ c #77966A",
+"h+ c #67A18A",
+"i+ c #3B82AB",
+"j+ c #D8D6C8",
+"k+ c #D3D0C3",
+"l+ c #DBE3E4",
+"m+ c #F5F8FA",
+"n+ c #E2E9F1",
+"o+ c #B0BEC8",
+"p+ c #8A5593",
+"q+ c #8C3BA2",
+"r+ c #9687B4",
+"s+ c #C5C2B5",
+"t+ c #DCDACB",
+"u+ c #AFAEB0",
+"v+ c #B1ADA8",
+"w+ c #C2BBB0",
+"x+ c #ADACAE",
+"y+ c #B0AEAB",
+"z+ c #DDDACB",
+"A+ c #729472",
+"B+ c #609F92",
+"C+ c #3C82AC",
+"D+ c #D9D6C8",
+"E+ c #D9E2E4",
+"F+ c #E6EDF4",
+"G+ c #CBD9E7",
+"H+ c #A1B3C2",
+"I+ c #915A8A",
+"J+ c #8C2E8C",
+"K+ c #9F8EAF",
+"L+ c #DFDCCD",
+"M+ c #CDCBBE",
+"N+ c #B5B3B0",
+"O+ c #E0DDCE",
+"P+ c #C2C4B8",
+"Q+ c #89A2A6",
+"R+ c #BBC0B9",
+"S+ c #D5D3C5",
+"T+ c #C4C7BF",
+"U+ c #AAB2B1",
+"V+ c #A6AEAE",
+"W+ c #ADB2AD",
+"X+ c #CAC8BA",
+"Y+ c #C1BEB2",
+"Z+ c #A7A19D",
+"`+ c #C2C0B3",
+" @ c #C2BFB2",
+" ",
+" . . . . . . . . . . . . . + ",
+" @ # # # # $ % % & * = - ; > ",
+" , ' ' ' ' ) ! ~ { ] ^ / ( _ ",
+" , ' ' ' : < [ } | 1 2 3 4 5 ",
+" , ' ' : 6 7 8 9 0 a b c d e f ",
+" , ' g h i 8 j k l m n 3 o p q r ",
+" , s t u v w x l y z A B C D E F G ",
+" , g 6 H I J K L M N O P Q R S T U V ",
+" , W X Y w k Z ` ...+.@.#.$.%.&.*.=. ",
+" -.;.u >.j ,.'.).N !.~.{.].^./.(._.:. ",
+" <.t [.8 }.,.|.1.2.3.4.5.6.7.8.9.0.a.b. ",
+" c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.=.u.v. ",
+" G w.x.y.z.A.B.C.D.E. ",
+" F.G.H.I.J.K.L.M.N. ",
+" N.O.P.Q.R.S.T.U. ",
+" V.W.X.Y.Z.F. ",
+" `. +.+++v. ",
+" @+#+$+%+&+ *+=+-+r ",
+" ;+>+,+'+ )+!+ ",
+"~+{+]+^+/+(+_+:+<+[+}+}+}+}+|+1+2+3+4+5+6+7+8+9+",
+"0+a+b+c+d+e+f+g+h+i+j+}+}+}+k+l+m+n+o+}+p+q+r+s+",
+"t+u+v+w+x+y+z+A+B+C+D+}+}+}+k+E+F+G+H+}+I+J+K+s+",
+"}+L+M+N+O+L+}+P+Q+R+}+}+}+}+S+T+U+V+W+X+Y+Z+`+ @"};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..e507079
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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: main.cpp,v 1.7 2004/11/08 07:14:18 cs19713 Exp $
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <qdir.h>
+#include "kdocker.h"
+#include "traylabelmgr.h"
+#include "trace.h"
+#include <X11/Xlib.h>
+
+static void sighandler(int sig)
+{
+ if (sig == SIGUSR1)
+ {
+ DUMP_TRACE(QDir::homeDirPath() + "/kdocker.trace");
+ return;
+ }
+
+ qDebug(qApp->translate("KDocker", "Caught signal %1. Cleaning up.").arg(sig));
+ ((KDocker *)qApp)->trayLabelMgr()->undockAll();
+}
+
+int main(int argc, char *argv[])
+{
+ // setup signal handlers that undock and quit
+ signal(SIGHUP, sighandler);
+ signal(SIGSEGV, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGUSR1, sighandler);
+
+ KDocker app(argc, argv);
+ return app.exec();
+}
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
+
diff --git a/src/qtraylabel.h b/src/qtraylabel.h
new file mode 100644
index 0000000..1ae83bc
--- /dev/null
+++ b/src/qtraylabel.h
@@ -0,0 +1,161 @@
+/*
+ * 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.h,v 1.21 2005/06/21 10:04:36 cs19713 Exp $
+
+#ifndef _QTRAYLABEL_H
+#define _QTRAYLABEL_H
+
+#include <qlabel.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qpixmap.h>
+#include <qtimer.h>
+#include <qtextstream.h>
+#include <qsettings.h>
+#include <qevent.h>
+#include <qsize.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+class QMouseEvent;
+class QDragEnterEvent;
+class QPoint;
+
+class QTrayLabel : public QLabel
+{
+ Q_OBJECT
+
+public:
+ QTrayLabel(Window w, QWidget* p = 0, const QString& text = QString::null);
+ QTrayLabel(const QStringList& argv, pid_t pid, QWidget* parent = 0);
+ virtual ~QTrayLabel();
+
+ // Accessors
+ Window dockedWindow(void) const { return mDockedWindow; }
+ int balloonTimeout(void) const { return mBalloonTimeout; }
+ bool isSkippingTaskbar(void) const { return mSkippingTaskbar; }
+ bool isWithdrawn(void) const { return mWithdrawn; }
+ bool isDockWhenMinimized(void) const { return mDockWhenMinimized; }
+
+ QString appName(void) const { return mProgName[0]; }
+ virtual void setAppName(const QString& prog) { mProgName[0] = prog; }
+ QString appClass(void) const { return mClass; }
+ QString appTitle(void) const { return mTitle; }
+ QPixmap appIcon(void) const { return mAppIcon; }
+
+ // Pass on all events through this interface
+ bool x11EventFilter(XEvent * event);
+
+ // Session Management
+ virtual bool saveState(QSettings& settings);
+ virtual bool restoreState(QSettings& settings);
+
+public slots:
+ void dock(void); // puts us in the system tray
+ void undock(void); // removes us from the system tray
+ void map(void); // maps the window that we are docking
+ void withdraw(void); // withdraws the window that we are docking
+ void toggleShow(void) // convenience slot
+ { if (mWithdrawn) map(); else withdraw(); }
+ void close(void); // close the docked window
+ void setTrayIcon(const QString& icon); // sets custom icon
+
+ // and some property setters
+ virtual void setSkipTaskbar(bool skip); // skip the taskbar
+ virtual void setBalloonTimeout(int msecs) { mBalloonTimeout = msecs; }
+ virtual void setDockWhenMinimized(bool dwm) { mDockWhenMinimized = dwm; }
+
+protected slots:
+ void scanClients(void); // scans existing client connections
+
+signals:
+ void clicked(const ButtonState&, const QPoint&);
+ void docked(QTrayLabel *); // emitted when we get docked
+ void docked(void); // emitted when we get docked
+ void undocked(QTrayLabel *); // emitted when we get undocked
+ void undocked(void); // emitted when we get undock
+ // window are monitoring dies
+ void sysTrayDestroyed(void); // emitted when the system tray disappears
+
+protected:
+ // reimplement these event handlers in subclass as needed
+ virtual void mouseReleaseEvent(QMouseEvent *event);
+ virtual void dragEnterEvent(QDragEnterEvent *event);
+
+ // the events that follow are events of the docked window (NOT QTrayLabel)
+ virtual void updateIcon(void); // updates the icon
+ virtual void updateTitle(void); // sets the tooltip
+ virtual void balloonText(void); // balloons text
+ virtual void obscureEvent(void) { }
+ virtual void mapEvent(void) { }
+ virtual void focusLostEvent(void) { }
+ virtual void unmapEvent(void) { }
+ virtual void minimizeEvent(void);
+ virtual void destroyEvent(void);
+
+ // needs to return if we can unsubscribe for root
+ virtual bool canUnsubscribeFromRoot(void) { return true; }
+ // needs to return if we can dock a candidate window
+ virtual bool canDockWindow(Window) { return true; }
+ virtual void processDead(void) { }
+
+ void propertyChangeEvent(Atom);
+ void setDockedWindow(Window w); // set docked window to anything you want
+
+private slots:
+ void realityCheck(void);
+ void showOnAllDesktops(void);
+ void toggleDockWhenMinimized(void)
+ { mDockWhenMinimized = !mDockWhenMinimized; }
+ void skipTaskbar(void);
+
+private:
+ // Helpers
+ void initialize(void);
+ void handleTitleChange(void);
+ void handleIconChange(void);
+ const char *me(void) const;
+
+ // Member variables
+ long mDesktop; // desktop on which the window is being shown
+ QLabel *mBalloon; // tooltip text simulator
+ QString mCustomIcon; // CustomIcon of the docked application
+ Window mDockedWindow; // the window which is being docked
+ int mBalloonTimeout;
+ bool mDocked, mWithdrawn, mSkippingTaskbar;
+ bool mDockWhenMinimized;
+
+ QString mTitle, mClass; // Title and hint of mDockedWindow
+ QPixmap mAppIcon; // The current app icon (may not be same as pixmap())
+ XSizeHints mSizeHint; // SizeHint of mDockedWindow
+
+ QTimer mRealityMonitor; // Helps us sync up with reality
+ QStringList mProgName; // The program whose window we are docking
+ pid_t mPid; // The PID of program whose window we are docking
+
+ Window mSysTray; // System tray window id
+};
+
+#endif // _QTRAYLABEL_H
diff --git a/src/question.xpm b/src/question.xpm
new file mode 100644
index 0000000..112452d
--- /dev/null
+++ b/src/question.xpm
@@ -0,0 +1,39 @@
+/* XPM */
+static char * question_xpm[] = {
+"32 32 4 1",
+" c None",
+". c #000000",
+"+ c #008000",
+"@ c #FFFFFF",
+" ........ ",
+" ...++++++++... ",
+" ..++++++++++++++.. ",
+" .++++++@@@@@@++++++. ",
+" .+++++@@@@@@@@@@+++++. ",
+" .+++++@@@@@@@@@@@@+++++. ",
+" .++++++@@@@@@@@@@@@++++++. ",
+" .++++++@@@@@@@@@@@@@@++++++. ",
+" .++++++@@@@@@@@@@@@@@++++++. ",
+" .+++++++@@@@@@++@@@@@@+++++++. ",
+" .+++++++@@@@@@++@@@@@@+++++++. ",
+" .+++++++@@@@@@++@@@@@@+++++++. ",
+".+++++++++++++++@@@@@@+++++++++.",
+".+++++++++++++++@@@@@@+++++++++.",
+".++++++++++++++@@@@@@++++++++++.",
+".++++++++++++++@@@@@@++++++++++.",
+".+++++++++++++@@@@@@+++++++++++.",
+".+++++++++++++@@@@@@+++++++++++.",
+".++++++++++++@@@@@@++++++++++++.",
+".++++++++++++@@@@@@++++++++++++.",
+" .+++++++++++@@@@@@+++++++++++. ",
+" .++++++++++++++++++++++++++++. ",
+" .+++++++++++++@@+++++++++++++. ",
+" .++++++++++@@@@@@++++++++++. ",
+" .++++++++++@@@@@@++++++++++. ",
+" .++++++++@@@@@@@@++++++++. ",
+" .+++++++@@@@@@@@+++++++. ",
+" .+++++++@@@@@@+++++++. ",
+" .++++++@@@@@@++++++. ",
+" ..++++++@@++++++.. ",
+" ...++++++++... ",
+" ........ "};
diff --git a/src/trace.cpp b/src/trace.cpp
new file mode 100644
index 0000000..c41247e
--- /dev/null
+++ b/src/trace.cpp
@@ -0,0 +1,44 @@
+#ifdef ENABLE_TRACING
+#include <qapplication.h>
+#include <qtextedit.h>
+#include <qfile.h>
+#include <stdio.h>
+
+static QTextEdit *tracer = NULL;
+
+void TRACER(QtMsgType, const char *msg)
+{
+ tracer->append(&msg[*msg == '~' ? 1 : 0]);
+ if (msg[0] != '~')
+ {
+ fprintf(stderr, msg);
+ fprintf(stderr, "\r\n");
+ }
+}
+
+void INIT_TRACE()
+{
+ if (tracer) return; // de javu
+ tracer = new QTextEdit();
+ tracer->setTextFormat(Qt::LogText);
+ tracer->setMaxLogLines(10000);
+ tracer->resize(750, 300);
+ qInstallMsgHandler(TRACER);
+}
+
+void SHOW_TRACE()
+{
+ tracer->show();
+}
+
+void DUMP_TRACE(const char *f)
+{
+ QFile file(f); // Write the text to a file
+ if (file.open(IO_WriteOnly))
+ {
+ QTextStream stream(&file);
+ stream << tracer->text();
+ }
+}
+
+#endif // ENABLE_TRACER
diff --git a/src/trace.h b/src/trace.h
new file mode 100644
index 0000000..b3425ec
--- /dev/null
+++ b/src/trace.h
@@ -0,0 +1,30 @@
+#ifndef _TRACE_H
+#define _TRACE_H
+
+#ifdef ENABLE_TRACING
+ extern void INIT_TRACE(void);
+ extern void SHOW_TRACE(void);
+ extern void DUMP_TRACE(const char *file);
+
+ /*
+ * Lets admit it, shall we. C macros are sometimes are so much cooler than
+ * C++ static inlines ;)
+ * WARNING: fmt has to be a static string
+ */
+ #define TRACE(fmt,args...) \
+ do { qDebug("~%s - \t" fmt, __PRETTY_FUNCTION__, ##args); } while (0)
+
+ #define SHOW_TRACE_TEXT "Show Trace"
+
+#else // !ENABLE_TRACING
+
+ #define INIT_TRACE()
+ #define TRACE(fmt, ...)
+ #define SHOW_TRACE()
+ #define SHOW_TRACE_TEXT QString::null
+ #define DUMP_TRACE(file)
+
+#endif // ENABLE_TRACING
+
+#endif // _TRACE_H
+
diff --git a/src/traylabelmgr.cpp b/src/traylabelmgr.cpp
new file mode 100644
index 0000000..3b63cfc
--- /dev/null
+++ b/src/traylabelmgr.cpp
@@ -0,0 +1,523 @@
+/*
+ * 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: traylabelmgr.cpp,v 1.10 2005/02/09 03:38:43 cs19713 Exp $
+
+#include <qdir.h>
+#include <qapplication.h>
+#include <qmessagebox.h>
+#include <qtimer.h>
+#include <qfile.h>
+#include <qaction.h>
+#include <qpopupmenu.h>
+#include <qtextstream.h>
+#include <qfiledialog.h>
+
+#include "trace.h"
+#include "traylabelmgr.h"
+#include "util.h"
+
+#include <Xmu/WinUtil.h>
+#include <errno.h>
+#include <stdlib.h>
+
+TrayLabelMgr* TrayLabelMgr::gTrayLabelMgr = NULL;
+const char *TrayLabelMgr::mOptionString = "+abdefi:lmop:qtw:";
+
+TrayLabelMgr* TrayLabelMgr::instance(void)
+{
+ if (gTrayLabelMgr) return gTrayLabelMgr;
+ TRACE("Creating new instance");
+ return (gTrayLabelMgr = new TrayLabelMgr());
+}
+
+TrayLabelMgr::TrayLabelMgr() : mReady(false), mHiddenLabelsCount(0)
+{
+ // Set ourselves up to be called from the application loop
+ QTimer::singleShot(0, this, SLOT(startup()));
+}
+
+TrayLabelMgr::~TrayLabelMgr()
+{
+ undockAll();
+}
+
+void TrayLabelMgr::about(void)
+{
+ if (QMessageBox::information(NULL, tr("About KDocker"),
+ tr("Bugs/wishes to Girish Ramakrishnan ([email protected])\n"
+ "English translation by Girish ([email protected])\n\n"
+ "http://kdocker.sourceforge.net for updates"),
+ QString::null, SHOW_TRACE_TEXT) == 1) SHOW_TRACE();
+}
+
+void TrayLabelMgr::startup(void)
+{
+ const int WAIT_TIME = 10;
+ static int wait_time = WAIT_TIME;
+ /*
+ * If it appears that we were launched from some startup script (check whether
+ * stdout is a tty) OR if we are getting restored, wait for WAIT_TIME until
+ * the system tray shows up (before informing the user)
+ */
+ static bool do_wait = !isatty(fileno(stdout)) || qApp->isSessionRestored();
+
+ SysTrayState state = sysTrayStatus(QPaintDevice::x11AppDisplay());
+
+ if (state != SysTrayPresent)
+ {
+ if (wait_time-- > 0 && do_wait)
+ {
+ TRACE("Will check sys tray status after 1 second");
+ QTimer::singleShot(1000, this, SLOT(startup()));
+ return;
+ }
+
+ if (QMessageBox::warning(NULL, tr("KDocker"),
+ tr(state == SysTrayAbsent ? "No system tray found"
+ : "System tray appears to be hidden"),
+ QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
+ {
+ qApp->quit();
+ return;
+ }
+ }
+
+ // Things are fine or user with OK with the state of system tray
+ mReady = true;
+ bool ok = false;
+ if (qApp->isSessionRestored()) ok = restoreSession(qApp->sessionId());
+ else ok = processCommand(qApp->argc(), qApp->argv());
+ // Process the request Q from previous instances
+
+ TRACE("Request queue has %i requests", mRequestQ.count());
+ for(unsigned i=0; i < mRequestQ.count(); i++)
+ ok |= processCommand(mRequestQ[i]);
+ if (!ok) qApp->quit();
+}
+
+// Initialize a QTrayLabel after its creation
+void TrayLabelMgr::manageTrayLabel(QTrayLabel *t)
+{
+ connect(t, SIGNAL(destroyed(QObject *)),
+ this, SLOT(trayLabelDestroyed(QObject *)));
+ connect(t, SIGNAL(undocked(QTrayLabel *)), t, SLOT(deleteLater()));
+
+ // All QTrayLabels will emit this signal. We just need one of them
+ if (mTrayLabels.count() == 0)
+ connect(t, SIGNAL(sysTrayDestroyed()), this, SLOT(sysTrayDestroyed()));
+ mTrayLabels.prepend(t);
+
+ TRACE("New QTrayLabel prepended. Count=%i", mTrayLabels.count());
+}
+
+void TrayLabelMgr::dockAnother()
+{
+ QTrayLabel *t = selectAndDock();
+ if (t == NULL) return;
+ manageTrayLabel(t);
+ t->withdraw();
+ t->dock();
+}
+
+// Undock all the windows
+void TrayLabelMgr::undockAll()
+{
+ TRACE("Number of tray labels = %i", mTrayLabels.count());
+ QPtrListIterator<QTrayLabel> it(mTrayLabels);
+ QTrayLabel *t;
+ while ((t = it.current()) != 0)
+ {
+ ++it;
+ t->undock();
+ }
+}
+
+// Process the command line
+bool TrayLabelMgr::processCommand(const QStringList& args)
+{
+ if (!mReady)
+ {
+ // If we are still looking for system tray, just add it to the Q
+ mRequestQ.append(args);
+ return true;
+ }
+
+ const int MAX_ARGS = 20;
+ const char *argv[MAX_ARGS];
+ int argc = args.count();
+ if (argc >= MAX_ARGS) argc = MAX_ARGS - 1;
+
+ for(int i =0 ; i<argc; i++)
+ argv[i] = args[i].latin1();
+
+ argv[argc] = NULL; // null terminate the array
+
+ return processCommand(argc, const_cast<char **>(argv));
+}
+
+// Process the command line
+bool TrayLabelMgr::processCommand(int argc, char** argv)
+{
+ TRACE("CommandLine arguments");
+ for(int i = 0; i < argc; i++) TRACE("\t%s", argv[i]);
+
+ if (argc < 1) return false;
+
+ // Restore session (See the comments in KDocker::notifyPreviousInstance()
+ if (qstrcmp(argv[1], "-session") == 0)
+ {
+ TRACE("Restoring session %s (new instance request)", argv[2]);
+ return restoreSession(QString(argv[2]));
+ }
+
+ int option;
+ Window w = None;
+ const char *icon = NULL;
+ int balloon_timeout = 4000;
+ bool withdraw = true, skip_taskbar = false,
+ auto_launch = false, dock_obscure = false, check_normality = true,
+ enable_sm = true;
+
+ optind = 0; // initialise the getopt static
+
+ while ((option = getopt(argc, argv, mOptionString)) != EOF)
+ {
+ switch (option)
+ {
+ case '?':
+ return false;
+ case 'a':
+ qDebug(tr("Girish Ramakrishnan ([email protected])"));
+ return false;
+ case 'b':
+ check_normality = false;
+ break;
+ case 'd':
+ enable_sm = false;
+ break;
+ case 'e':
+ enable_sm = true;
+ break;
+ case 'f':
+ w = activeWindow(QPaintDevice::x11AppDisplay());
+ TRACE("Active window is %i", (unsigned) w);
+ break;
+ case 'i':
+ icon = optarg;
+ break;
+ case 'l':
+ auto_launch = true;
+ break;
+ case 'm':
+ withdraw = false;
+ break;
+ case 'o':
+ dock_obscure = true;
+ break;
+ case 'p':
+ balloon_timeout = atoi(optarg) * 1000; // convert to ms
+ break;
+ case 'q':
+ balloon_timeout = 0; // same as '-p 0'
+ break;
+ case 't':
+ skip_taskbar = true;
+ break;
+ case 'w':
+ if ((optarg[1] == 'x') || (optarg[1] == 'X'))
+ sscanf(optarg, "%x", (unsigned *) &w);
+ else
+ w = (Window) atoi(optarg);
+ if (!isValidWindowId(QPaintDevice::x11AppDisplay(), w))
+ {
+ qDebug("Window 0x%x invalid", (unsigned) w);
+ return false;
+ }
+ break;
+ } // switch (option)
+ } // while (getopt)
+
+ // Launch an application if present in command line. else request from user
+ CustomTrayLabel *t = (CustomTrayLabel *) // this should be dynamic_cast
+ ((optind < argc) ? dockApplication(&argv[optind])
+ : selectAndDock(w, check_normality));
+ if (t == NULL) return false;
+ // apply settings and add to tray
+ manageTrayLabel(t);
+ if (icon) t->setTrayIcon(icon);
+ t->setSkipTaskbar(skip_taskbar);
+ t->setBalloonTimeout(balloon_timeout);
+ t->setDockWhenObscured(dock_obscure);
+ if (withdraw) t->withdraw(); else t->map();
+ t->enableSessionManagement(enable_sm);
+ t->dock();
+ t->setLaunchOnStartup(auto_launch);
+ return true;
+}
+
+/*
+ * Request user to make a window selection if necessary. Dock the window.
+ */
+QTrayLabel *TrayLabelMgr::selectAndDock(Window w, bool checkNormality)
+{
+ if (w == None)
+ {
+ qDebug(tr("Select the application/window to dock with button1."));
+ qDebug(tr("Click any other button to abort\n"));
+
+ const char *err = NULL;
+
+ if ((w = selectWindow(QPaintDevice::x11AppDisplay(), &err)) == None)
+ {
+ if (err) QMessageBox::critical(NULL, tr("KDocker"), tr(err));
+ return NULL;
+ }
+ }
+
+ if (checkNormality && !isNormalWindow(QPaintDevice::x11AppDisplay(), w))
+ {
+ /*
+ * Abort should be the only option here really. "Ignore" is provided here
+ * for the curious user who wants to screw himself very badly
+ */
+ if (QMessageBox::warning(NULL, tr("KDocker"),
+ tr("The window you are attempting to dock does not seem to be a"
+ " normal window."), QMessageBox::Abort,
+ QMessageBox::Ignore) == QMessageBox::Abort)
+ return NULL;
+ }
+
+ if (!isWindowDocked(w)) return new CustomTrayLabel(w);
+
+ TRACE("0x%x is not docked", (unsigned) w);
+
+ QMessageBox::message(tr("KDocker"),
+ tr("This window is already docked.\n"
+ "Click on system tray icon to toggle docking."));
+ return NULL;
+}
+
+bool TrayLabelMgr::isWindowDocked(Window w)
+{
+ QPtrListIterator<QTrayLabel> it(mTrayLabels);
+ for(QTrayLabel *t; (t = it.current()); ++it)
+ if (t->dockedWindow() == w) return true;
+
+ return false;
+}
+
+/*
+ * Forks application specified by argv. Requests root window SubstructreNotify
+ * notifications (for MapEvent on children). We will monitor these new windows
+ * to make a pid to wid mapping (see HACKING for more details)
+ */
+QTrayLabel *TrayLabelMgr::dockApplication(char *argv[])
+{
+ pid_t pid = -1;
+ int filedes[2];
+ char buf[4] = { 't', 'e', 'e', 'e' }; // teeeeeee :x :-*
+
+ /*
+ * The pipe created here serves as a synchronization mechanism between the
+ * parent and the child. QTrayLabel ctor keeps looking out for newly created
+ * windows. Need to make sure that the application is actually exec'ed only
+ * after we QTrayLabel is created (it requires pid of child)
+ */
+ pipe(filedes);
+
+ if ((pid = fork()) == 0)
+ {
+ close(filedes[1]);
+ read(filedes[0], buf, sizeof(buf));
+ close(filedes[0]);
+
+ if (execvp(argv[0], argv) == -1)
+ {
+ qDebug(tr("Failed to exec [%1]: %2").arg(argv[0]).arg(strerror(errno)));
+ ::exit(0); // will become a zombie in some systems :(
+ return NULL;
+ }
+ }
+
+ if (pid == -1)
+ {
+ QMessageBox::critical(NULL, "KDocker",
+ tr("Failed to fork: %1").arg(strerror(errno)));
+ return NULL;
+ }
+
+ QStringList cmd_line;
+ for(int i=0;;i++)
+ if (argv[i]) cmd_line << argv[i]; else break;
+
+ QTrayLabel *label = new CustomTrayLabel(cmd_line, pid);
+ qApp->syncX();
+ write(filedes[1], buf, sizeof(buf));
+ close(filedes[0]);
+ close(filedes[1]);
+ return label;
+}
+
+/*
+ * Returns the number of QTrayLabels actually created but not show in the
+ * System Tray
+ */
+int TrayLabelMgr::hiddenLabelsCount(void) const
+{
+ QPtrListIterator<QTrayLabel> it(mTrayLabels);
+ int count = 0;
+ for(QTrayLabel *t; (t=it.current()); ++it)
+ if (t->dockedWindow() == None) ++count;
+ return count;
+}
+
+// The number of labes that are docked in the system tray
+int TrayLabelMgr::dockedLabelsCount(void) const
+{
+ return mTrayLabels.count() - hiddenLabelsCount();
+}
+
+void TrayLabelMgr::trayLabelDestroyed(QObject *t)
+{
+ bool reconnect = ((QObject *)mTrayLabels.getLast() == t);
+ mTrayLabels.removeRef((QTrayLabel*)t);
+ if (mTrayLabels.isEmpty()) qApp->quit();
+ else if (reconnect)
+ {
+ TRACE("Reconnecting");
+ connect(mTrayLabels.getFirst(), SIGNAL(sysTrayDestroyed()),
+ this, SLOT(sysTrayDestroyed()));
+ }
+}
+
+void TrayLabelMgr::sysTrayDestroyed(void)
+{
+ /*
+ * The system tray got destroyed. This could happen when it was
+ * hidden/removed or killed/crashed/exited. Now we must be genteel enough
+ * to not pop up a box when the user is logging out. So, we set ourselves
+ * up to notify user after 3 seconds.
+ */
+ QTimer::singleShot(3000, this, SLOT(notifySysTrayAbsence()));
+}
+
+void TrayLabelMgr::notifySysTrayAbsence()
+{
+ SysTrayState state = sysTrayStatus(QPaintDevice::x11AppDisplay());
+
+ if (state == SysTrayPresent)
+ return; // So sweet of the systray to come back so soon
+
+ if (QMessageBox::warning(NULL, tr("KDocker"),
+ tr("The System tray was hidden or removed"),
+ tr("Undock All"), tr("Ignore")) == 0)
+ undockAll();
+}
+
+/*
+ * Session Management. Always return "true". Atleast, for now
+ */
+bool TrayLabelMgr::restoreSession(const QString& sessionId)
+{
+ QString session_file = "kdocker_" + sessionId;
+
+ QSettings settings;
+ settings.beginGroup(QString("/" + session_file));
+
+ for(int i = 1;; i++)
+ {
+ settings.beginGroup(QString("/Instance") + QString("").setNum(i));
+ QString pname = settings.readEntry("/Application");
+ TRACE("Restoring Application[%s]", pname.latin1());
+ if (pname.isEmpty()) break;
+ if (settings.readBoolEntry("/LaunchOnStartup"))
+ {
+ QStringList args("kdocker");
+ args += QStringList::split(" ", pname);
+ TRACE("Triggering AutoLaunch");
+ if (!processCommand(args)) continue;
+ }
+ else
+ manageTrayLabel(new CustomTrayLabel(QStringList::split(" ", pname), 0));
+
+ QTrayLabel *tl = mTrayLabels.getFirst(); // the one that was created above
+ tl->restoreState(settings);
+ settings.endGroup();
+ }
+
+ return true;
+}
+
+QString TrayLabelMgr::saveSession(void)
+{
+ QString session_file = "kdocker_" + qApp->sessionId();
+
+ QSettings settings;
+ settings.beginGroup(QString("/" + session_file));
+
+ TRACE("Saving session");
+
+ QPtrListIterator <QTrayLabel> it(mTrayLabels);
+ QTrayLabel *t;
+ int i = 1;
+ while ((t = it.current()) != 0)
+ {
+ ++it;
+ TRACE("Saving instance %i", i);
+ settings.beginGroup(QString("/Instance") + QString("").setNum(i));
+ bool ok = t->saveState(settings);
+ settings.endGroup();
+ if (ok) ++i; else TRACE("Saving of instance %i was skipped", i);
+ }
+
+ // Aaaaaaaaaaaaaa.........
+ settings.removeEntry(QString("/Instance") + QString("").setNum(i));
+
+ return QDir::homeDirPath() + "/.qt/" + session_file + "rc";
+}
+
+/*
+ * The X11 Event Filter. Pass on events to the QTrayLabels that we created.
+ * The logic and the code below is a bit fuzzy.
+ * a) Events about windows that are being docked need to be processed only by
+ * the QTrayLabel object that is docking that window.
+ * b) Events about windows that are not docked but of interest (like
+ * SystemTray) need to be passed on to all QTrayLabel objects.
+ * c) When a QTrayLabel manages to find the window that is was looking for, we
+ * need not process the event further
+ */
+bool TrayLabelMgr::x11EventFilter(XEvent *ev)
+{
+ QPtrListIterator<QTrayLabel> it(mTrayLabels);
+ bool ret = false;
+
+ // We pass on the event to all tray labels
+ for(QTrayLabel *t; (t = it.current()); ++it)
+ {
+ Window w = t->dockedWindow();
+ bool res = t->x11EventFilter(ev);
+ if (w == (((XAnyEvent *)ev)->window)) return res;
+ if (w != None) ret |= res;
+ else if (res) return TRUE;
+ }
+
+ return ret;
+}
+
diff --git a/src/traylabelmgr.h b/src/traylabelmgr.h
new file mode 100644
index 0000000..016460f
--- /dev/null
+++ b/src/traylabelmgr.h
@@ -0,0 +1,81 @@
+/*
+ * 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,
+ */
+
+// $Id: traylabelmgr.h,v 1.3 2005/01/07 12:16:07 cs19713 Exp $
+
+#ifndef _TRAYLABELMGR_H
+#define _TRAYLABELMGR_H
+
+#include <qobject.h>
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+
+#include "customtraylabel.h"
+
+class CustomTrayLabel;
+
+class TrayLabelMgr : public QObject
+{
+ Q_OBJECT
+
+public:
+ static TrayLabelMgr* instance();
+ static QString options(void) { return QString(mOptionString); }
+
+ ~TrayLabelMgr();
+
+ QString saveSession();
+
+ bool x11EventFilter(XEvent *);
+ bool processCommand(const QStringList& argv);
+ int hiddenLabelsCount(void) const;
+ int dockedLabelsCount(void) const;
+ bool isWindowDocked(Window w);
+
+public slots:
+ void about();
+ void undockAll();
+ void dockAnother();
+
+private slots:
+ void startup();
+ void trayLabelDestroyed(QObject *);
+ void sysTrayDestroyed(void);
+ void notifySysTrayAbsence();
+
+private:
+ TrayLabelMgr();
+ bool processCommand(int argc, char** argv);
+ void manageTrayLabel(QTrayLabel *l);
+ bool restoreSession(const QString& sessionId);
+
+ QTrayLabel *dockApplication(char *argv[]);
+ QTrayLabel *selectAndDock(Window w = None, bool checkNormality = true);
+
+ QPtrList<QTrayLabel> mTrayLabels;
+ QValueList<QStringList> mRequestQ;
+ bool mReady;
+ int mHiddenLabelsCount;
+
+ static const char *mOptionString;
+ static TrayLabelMgr *gTrayLabelMgr;
+};
+
+#endif // _TRAYLABELMGR_H
+
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..6ddf920
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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: util.cpp,v 1.7 2005/01/29 09:56:08 cs19713 Exp $
+
+#include "trace.h"
+#include "util.h"
+
+#include <X11/Xutil.h>
+#include <Xmu/WinUtil.h>
+#include <X11/Xatom.h>
+#include <X11/cursorfont.h>
+
+#include <stdio.h>
+
+/*
+ * Assert validity of the window id. Get window attributes for the heck of it
+ * and see if the request went through.
+ */
+bool isValidWindowId(Display *display, Window w)
+{
+ XWindowAttributes attrib;
+ return XGetWindowAttributes(display, w, &attrib) != 0;
+}
+
+/*
+ * Checks if this window is a normal window (i.e)
+ * - Has a WM_STATE
+ * - Not modal window
+ * - Not a purely transient window (with no window type set)
+ * - Not a special window (desktop/menu/util) as indicated in the window type
+ */
+bool isNormalWindow(Display *display, Window w)
+{
+ Atom __attribute__ ((unused)) type;
+ int __attribute__ ((unused)) format;
+ unsigned long __attribute__ ((unused)) left;
+ Atom *data = NULL;
+ unsigned long nitems;
+ Window transient_for = None;
+
+ static Atom wmState = XInternAtom(display, "WM_STATE", False);
+ static Atom windowState = XInternAtom(display, "_NET_WM_STATE", False);
+ static Atom modalWindow =
+ XInternAtom(display, "_NET_WM_STATE_MODAL", False);
+
+ static Atom windowType = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
+ static Atom normalWindow =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+ static Atom dialogWindow =
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+
+ int ret = XGetWindowProperty(display, w, wmState, 0,
+ 10, False, AnyPropertyType, &type, &format,
+ &nitems, &left, (unsigned char **) &data);
+
+ TRACE("0x%x (%i)", (unsigned) w, (unsigned) w);
+
+ if (ret != Success || data == NULL) return false;
+ TRACE("\tHas WM_STATE");
+ if (data) XFree(data);
+
+ ret = XGetWindowProperty(display, w, windowState, 0,
+ 10, False, AnyPropertyType, &type, &format,
+ &nitems, &left, (unsigned char **) &data);
+ if (ret == Success)
+ {
+ unsigned int i;
+ for (i = 0; i < nitems; i++)
+ {
+ if (data[i] == modalWindow) break;
+ }
+ XFree(data);
+ if (i < nitems) { TRACE("\tMODAL"); return false; }
+ }
+ else TRACE("\tNo _NET_WM_STATE");
+
+ XGetTransientForHint(display, w, &transient_for);
+ TRACE("\tTransientFor=0x%x", (unsigned) transient_for);
+
+ ret = XGetWindowProperty(display, w, windowType, 0,
+ 10, False, AnyPropertyType, &type, &format,
+ &nitems, &left, (unsigned char **) &data);
+
+ if ((ret == Success) && data)
+ {
+ unsigned int i;
+ for (i = 0; i < nitems; i++)
+ {
+ if (data[i] != normalWindow && data[i] != dialogWindow) break;
+ }
+ XFree(data);
+ TRACE("\tAbnormal = %i", (i == nitems));
+ return (i == nitems);
+ }
+ else
+ return (transient_for == None);
+}
+
+/*
+ * Returns the contents of the _NET_WM_PID (which is supposed to contain the
+ * process id of the application that created the window)
+ */
+pid_t pid(Display *display, Window w)
+{
+ Atom actual_type;
+ int actual_format;
+ unsigned long nitems, leftover;
+ unsigned char *pid;
+ pid_t pid_return = -1;
+
+ if (XGetWindowProperty(display, w,
+ XInternAtom(display, "_NET_WM_PID", False), 0,
+ 1, False, XA_CARDINAL, &actual_type,
+ &actual_format, &nitems, &leftover, &pid) == Success)
+ {
+ if (pid) pid_return = *(pid_t *) pid;
+ XFree(pid);
+ }
+ TRACE("0x%x has PID=%i", (unsigned) w, pid_return);
+ return pid_return;
+}
+
+/*
+ * Sends ClientMessage to a window
+ */
+void sendMessage(Display* display, Window to, Window w, char* type,
+ int format, long mask, void* data, int size)
+{
+ XEvent ev;
+ memset(&ev, 0, sizeof(ev));
+ ev.xclient.type = ClientMessage;
+ ev.xclient.window = w;
+ ev.xclient.message_type = XInternAtom(display, type, True);
+ ev.xclient.format = format;
+ memcpy((char *) &ev.xclient.data, (const char *) data, size);
+ XSendEvent(display, to, False, mask, &ev);
+ XSync(display, False);
+}
+
+/*
+ * The Grand Window Analyzer. Checks if window w has a expected pid of epid
+ * or a expected name of ename
+ */
+bool analyzeWindow(Display *display, Window w, pid_t epid, const QString &ename)
+{
+ XClassHint ch;
+ pid_t apid = pid(display, w);
+
+ TRACE("WID=0x%x PID=%i ExpectedName=%s", (unsigned) w, (unsigned) epid,
+ ename.latin1());
+ if (epid == apid) return true;
+
+ // no plans to analyze windows without a name
+ char *window_name = NULL;
+ if (!XFetchName(display, w, &window_name)) return false;
+ TRACE("Window has name [%s]", window_name);
+ if (window_name) XFree(window_name); else return false;
+
+ bool this_is_our_man = false;
+ // lets try the program name
+ if (XGetClassHint(display, w, &ch))
+ {
+ if (QString(ch.res_name).find(ename, 0, FALSE) != -1)
+ {
+ TRACE("ResName [%s] matched", ch.res_name);
+ this_is_our_man = true;
+ }
+ else if (QString(ch.res_class).find(ename, 0, FALSE) != -1)
+ {
+ TRACE("ResClass [%s] matched", ch.res_class);
+ this_is_our_man = true;
+ }
+ else
+ {
+ // sheer desperation
+ char *wm_name = NULL;
+ XFetchName(display, w, &wm_name);
+ if (wm_name && (QString(wm_name).find(ename, 0, FALSE) != -1))
+ {
+ TRACE("WM_NAME [%s] matched", wm_name);
+ this_is_our_man = true;
+ }
+ }
+
+ if (ch.res_class) XFree(ch.res_class);
+ if (ch.res_name) XFree(ch.res_name);
+ }
+
+ // its probably a good idea to check (obsolete) WM_COMMAND here
+ return this_is_our_man;
+}
+
+Window activeWindow(Display *display)
+{
+ Atom active_window_atom = XInternAtom(display, "_NET_ACTIVE_WINDOW", True);
+ Atom type = None;
+ int format;
+ unsigned long nitems, after;
+ unsigned char *data = NULL;
+ int screen = DefaultScreen(display);
+ Window root = RootWindow(display, screen);
+ int r = XGetWindowProperty(display, root, active_window_atom,0, 1,
+ False, AnyPropertyType, &type, &format, &nitems, &after, &data);
+
+ Window w = None;
+ if ((r == Success) && data && (*(Window *)data != None))
+ w = *(Window *)data;
+ else
+ {
+ int revert;
+ XGetInputFocus(display, &w, &revert);
+ }
+ TRACE("Active window is 0x%x", (unsigned) w);
+ return w;
+}
+
+/*
+ * Requests user to select a window by grabbing the mouse. A left click will
+ * select the application. Clicking any other button will abort the selection
+ */
+Window selectWindow(Display *display, const char **err)
+{
+ int screen = DefaultScreen(display);
+ Window root = RootWindow(display, screen);
+
+ if (err) *err = NULL;
+
+ Cursor cursor = XCreateFontCursor(display, XC_draped_box);
+ if (cursor == None)
+ {
+ if (err) *err = "Failed to create XC_draped_box";
+ return None;
+ }
+
+ if (XGrabPointer(display, root, False, ButtonPressMask | ButtonReleaseMask,
+ GrabModeSync, GrabModeAsync, None, cursor, CurrentTime)
+ != GrabSuccess)
+ {
+ if (err) *err = "Failed to grab mouse";
+ return None;
+ }
+
+ XAllowEvents(display, SyncPointer, CurrentTime);
+ XEvent event;
+ XWindowEvent(display, root, ButtonPressMask, &event);
+ Window selected_window =
+ (event.xbutton.subwindow == None) ? RootWindow(display, screen)
+ : event.xbutton.subwindow;
+ XUngrabPointer(display, CurrentTime);
+ XFreeCursor(display, cursor);
+
+ if (event.xbutton.button != Button1) return None;
+ return XmuClientWindow(display, selected_window);
+}
+
+void subscribe(Display *display, Window w, long mask, bool set)
+{
+ Window root = RootWindow(display, DefaultScreen(display));
+ XWindowAttributes attr;
+
+ TRACE("Window=0x%x Mask=%ld Set=%i", (unsigned) w, mask, set);
+
+ XGetWindowAttributes(display, w == None ? root : w, &attr);
+
+ if (set && (attr.your_event_mask & mask == mask)) return;
+ if (!set && (attr.your_event_mask | mask == attr.your_event_mask)) return;
+
+ XSelectInput(display, w == None ? root : w,
+ set ? attr.your_event_mask | mask : attr.your_event_mask & mask);
+ XSync(display, False);
+}
+
+// Returns the state of the SystemTray and the Wid if it exists
+SysTrayState sysTrayStatus(Display *display, Window *tray)
+{
+ Screen *screen = XDefaultScreenOfDisplay(display);
+ Window sys_tray;
+ Atom selection = None;
+
+ char temp[50];
+ sprintf(temp, "_NET_SYSTEM_TRAY_S%i", XScreenNumberOfScreen(screen));
+ selection = XInternAtom(display, temp, True);
+ if (selection == None) return SysTrayAbsent;
+ sys_tray = XGetSelectionOwner(display, selection);
+ if (tray) *tray = sys_tray;
+ return sys_tray == None ? SysTrayHidden : SysTrayPresent;
+}
+
+bool getCardinalProperty(Display *display, Window w, Atom prop, long *data)
+{
+ Atom type;
+ int format;
+ unsigned long nitems, bytes;
+ unsigned char *d = NULL;
+
+ if (XGetWindowProperty(display, w, prop, 0, 1, False, XA_CARDINAL,&type,
+ &format, &nitems, &bytes, &d) == Success && d)
+ {
+ TRACE("%ld", *(long *)d);
+ if (data) *data = *(long *)d;
+ XFree(d);
+ return true;
+ }
+ TRACE("FAILED");
+ return false;
+}
+
diff --git a/src/util.h b/src/util.h
new file mode 100644
index 0000000..5d27cc1
--- /dev/null
+++ b/src/util.h
@@ -0,0 +1,47 @@
+/*
+ * 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: util.h,v 1.3 2004/11/19 15:44:51 cs19713 Exp $
+
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include <qstring.h>
+#include <X11/Xlib.h>
+#include <sys/types.h>
+
+extern bool isNormalWindow(Display *display, Window w);
+extern bool isValidWindowId(Display *display, Window w);
+extern pid_t pid(Display *display, Window w);
+extern void sendMessage(Display *display, Window to, Window w, char *type,
+ int format, long mask, void *data, int size);
+extern bool analyzeWindow(Display *display, Window w, pid_t epid,
+ const QString &ename);
+extern Window activeWindow(Display *display);
+extern Window selectWindow(Display *display, const char **err = 0);
+extern void subscribe(Display *display, Window w, long mask, bool set);
+extern bool getCardinalProperty(Display *display, Window w, Atom prop,
+ long *data);
+
+typedef enum { SysTrayAbsent, SysTrayHidden, SysTrayPresent } SysTrayState;
+
+extern SysTrayState sysTrayStatus(Display *display, Window *tray = 0);
+
+#endif
+