diff options
author | Slávek Banko <slavek.banko@axis.cz> | 2013-07-05 19:06:59 +0200 |
---|---|---|
committer | Slávek Banko <slavek.banko@axis.cz> | 2013-07-05 19:06:59 +0200 |
commit | 14286a0eb6ec163c90e3d13c27fea147dbb82481 (patch) | |
tree | a5d70bd732b00ec833525dc1d25abc80f2d49144 /src | |
download | tdedocker-14286a0eb6ec163c90e3d13c27fea147dbb82481.tar.gz tdedocker-14286a0eb6ec163c90e3d13c27fea147dbb82481.zip |
Initial import
Diffstat (limited to 'src')
-rw-r--r-- | src/customtraylabel.cpp | 360 | ||||
-rw-r--r-- | src/customtraylabel.h | 90 | ||||
-rw-r--r-- | src/kdocker.cpp | 249 | ||||
-rw-r--r-- | src/kdocker.h | 59 | ||||
-rw-r--r-- | src/kdocker.xpm | 304 | ||||
-rw-r--r-- | src/main.cpp | 56 | ||||
-rw-r--r-- | src/qtraylabel.cpp | 809 | ||||
-rw-r--r-- | src/qtraylabel.h | 161 | ||||
-rw-r--r-- | src/question.xpm | 39 | ||||
-rw-r--r-- | src/trace.cpp | 44 | ||||
-rw-r--r-- | src/trace.h | 30 | ||||
-rw-r--r-- | src/traylabelmgr.cpp | 523 | ||||
-rw-r--r-- | src/traylabelmgr.h | 81 | ||||
-rw-r--r-- | src/util.cpp | 324 | ||||
-rw-r--r-- | src/util.h | 47 |
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 " + "gramakri@uiuc.edu\n"); + } + 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 gramakri@uiuc.edu")); + 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 (gramakri@uiuc.edu)\n" + "English translation by Girish (gramakri@uiuc.edu)\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 (gramakri@uiuc.edu)")); + 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 + |