diff options
Diffstat (limited to 'src/traylabelmgr.cpp')
-rw-r--r-- | src/traylabelmgr.cpp | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/src/traylabelmgr.cpp b/src/traylabelmgr.cpp new file mode 100644 index 0000000..3b63cfc --- /dev/null +++ b/src/traylabelmgr.cpp @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2004 Girish Ramakrishnan All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// $Id: traylabelmgr.cpp,v 1.10 2005/02/09 03:38:43 cs19713 Exp $ + +#include <qdir.h> +#include <qapplication.h> +#include <qmessagebox.h> +#include <qtimer.h> +#include <qfile.h> +#include <qaction.h> +#include <qpopupmenu.h> +#include <qtextstream.h> +#include <qfiledialog.h> + +#include "trace.h" +#include "traylabelmgr.h" +#include "util.h" + +#include <Xmu/WinUtil.h> +#include <errno.h> +#include <stdlib.h> + +TrayLabelMgr* TrayLabelMgr::gTrayLabelMgr = NULL; +const char *TrayLabelMgr::mOptionString = "+abdefi:lmop:qtw:"; + +TrayLabelMgr* TrayLabelMgr::instance(void) +{ + if (gTrayLabelMgr) return gTrayLabelMgr; + TRACE("Creating new instance"); + return (gTrayLabelMgr = new TrayLabelMgr()); +} + +TrayLabelMgr::TrayLabelMgr() : mReady(false), mHiddenLabelsCount(0) +{ + // Set ourselves up to be called from the application loop + QTimer::singleShot(0, this, SLOT(startup())); +} + +TrayLabelMgr::~TrayLabelMgr() +{ + undockAll(); +} + +void TrayLabelMgr::about(void) +{ + if (QMessageBox::information(NULL, tr("About KDocker"), + tr("Bugs/wishes to Girish Ramakrishnan ([email protected])\n" + "English translation by Girish ([email protected])\n\n" + "http://kdocker.sourceforge.net for updates"), + QString::null, SHOW_TRACE_TEXT) == 1) SHOW_TRACE(); +} + +void TrayLabelMgr::startup(void) +{ + const int WAIT_TIME = 10; + static int wait_time = WAIT_TIME; + /* + * If it appears that we were launched from some startup script (check whether + * stdout is a tty) OR if we are getting restored, wait for WAIT_TIME until + * the system tray shows up (before informing the user) + */ + static bool do_wait = !isatty(fileno(stdout)) || qApp->isSessionRestored(); + + SysTrayState state = sysTrayStatus(QPaintDevice::x11AppDisplay()); + + if (state != SysTrayPresent) + { + if (wait_time-- > 0 && do_wait) + { + TRACE("Will check sys tray status after 1 second"); + QTimer::singleShot(1000, this, SLOT(startup())); + return; + } + + if (QMessageBox::warning(NULL, tr("KDocker"), + tr(state == SysTrayAbsent ? "No system tray found" + : "System tray appears to be hidden"), + QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort) + { + qApp->quit(); + return; + } + } + + // Things are fine or user with OK with the state of system tray + mReady = true; + bool ok = false; + if (qApp->isSessionRestored()) ok = restoreSession(qApp->sessionId()); + else ok = processCommand(qApp->argc(), qApp->argv()); + // Process the request Q from previous instances + + TRACE("Request queue has %i requests", mRequestQ.count()); + for(unsigned i=0; i < mRequestQ.count(); i++) + ok |= processCommand(mRequestQ[i]); + if (!ok) qApp->quit(); +} + +// Initialize a QTrayLabel after its creation +void TrayLabelMgr::manageTrayLabel(QTrayLabel *t) +{ + connect(t, SIGNAL(destroyed(QObject *)), + this, SLOT(trayLabelDestroyed(QObject *))); + connect(t, SIGNAL(undocked(QTrayLabel *)), t, SLOT(deleteLater())); + + // All QTrayLabels will emit this signal. We just need one of them + if (mTrayLabels.count() == 0) + connect(t, SIGNAL(sysTrayDestroyed()), this, SLOT(sysTrayDestroyed())); + mTrayLabels.prepend(t); + + TRACE("New QTrayLabel prepended. Count=%i", mTrayLabels.count()); +} + +void TrayLabelMgr::dockAnother() +{ + QTrayLabel *t = selectAndDock(); + if (t == NULL) return; + manageTrayLabel(t); + t->withdraw(); + t->dock(); +} + +// Undock all the windows +void TrayLabelMgr::undockAll() +{ + TRACE("Number of tray labels = %i", mTrayLabels.count()); + QPtrListIterator<QTrayLabel> it(mTrayLabels); + QTrayLabel *t; + while ((t = it.current()) != 0) + { + ++it; + t->undock(); + } +} + +// Process the command line +bool TrayLabelMgr::processCommand(const QStringList& args) +{ + if (!mReady) + { + // If we are still looking for system tray, just add it to the Q + mRequestQ.append(args); + return true; + } + + const int MAX_ARGS = 20; + const char *argv[MAX_ARGS]; + int argc = args.count(); + if (argc >= MAX_ARGS) argc = MAX_ARGS - 1; + + for(int i =0 ; i<argc; i++) + argv[i] = args[i].latin1(); + + argv[argc] = NULL; // null terminate the array + + return processCommand(argc, const_cast<char **>(argv)); +} + +// Process the command line +bool TrayLabelMgr::processCommand(int argc, char** argv) +{ + TRACE("CommandLine arguments"); + for(int i = 0; i < argc; i++) TRACE("\t%s", argv[i]); + + if (argc < 1) return false; + + // Restore session (See the comments in KDocker::notifyPreviousInstance() + if (qstrcmp(argv[1], "-session") == 0) + { + TRACE("Restoring session %s (new instance request)", argv[2]); + return restoreSession(QString(argv[2])); + } + + int option; + Window w = None; + const char *icon = NULL; + int balloon_timeout = 4000; + bool withdraw = true, skip_taskbar = false, + auto_launch = false, dock_obscure = false, check_normality = true, + enable_sm = true; + + optind = 0; // initialise the getopt static + + while ((option = getopt(argc, argv, mOptionString)) != EOF) + { + switch (option) + { + case '?': + return false; + case 'a': + qDebug(tr("Girish Ramakrishnan ([email protected])")); + return false; + case 'b': + check_normality = false; + break; + case 'd': + enable_sm = false; + break; + case 'e': + enable_sm = true; + break; + case 'f': + w = activeWindow(QPaintDevice::x11AppDisplay()); + TRACE("Active window is %i", (unsigned) w); + break; + case 'i': + icon = optarg; + break; + case 'l': + auto_launch = true; + break; + case 'm': + withdraw = false; + break; + case 'o': + dock_obscure = true; + break; + case 'p': + balloon_timeout = atoi(optarg) * 1000; // convert to ms + break; + case 'q': + balloon_timeout = 0; // same as '-p 0' + break; + case 't': + skip_taskbar = true; + break; + case 'w': + if ((optarg[1] == 'x') || (optarg[1] == 'X')) + sscanf(optarg, "%x", (unsigned *) &w); + else + w = (Window) atoi(optarg); + if (!isValidWindowId(QPaintDevice::x11AppDisplay(), w)) + { + qDebug("Window 0x%x invalid", (unsigned) w); + return false; + } + break; + } // switch (option) + } // while (getopt) + + // Launch an application if present in command line. else request from user + CustomTrayLabel *t = (CustomTrayLabel *) // this should be dynamic_cast + ((optind < argc) ? dockApplication(&argv[optind]) + : selectAndDock(w, check_normality)); + if (t == NULL) return false; + // apply settings and add to tray + manageTrayLabel(t); + if (icon) t->setTrayIcon(icon); + t->setSkipTaskbar(skip_taskbar); + t->setBalloonTimeout(balloon_timeout); + t->setDockWhenObscured(dock_obscure); + if (withdraw) t->withdraw(); else t->map(); + t->enableSessionManagement(enable_sm); + t->dock(); + t->setLaunchOnStartup(auto_launch); + return true; +} + +/* + * Request user to make a window selection if necessary. Dock the window. + */ +QTrayLabel *TrayLabelMgr::selectAndDock(Window w, bool checkNormality) +{ + if (w == None) + { + qDebug(tr("Select the application/window to dock with button1.")); + qDebug(tr("Click any other button to abort\n")); + + const char *err = NULL; + + if ((w = selectWindow(QPaintDevice::x11AppDisplay(), &err)) == None) + { + if (err) QMessageBox::critical(NULL, tr("KDocker"), tr(err)); + return NULL; + } + } + + if (checkNormality && !isNormalWindow(QPaintDevice::x11AppDisplay(), w)) + { + /* + * Abort should be the only option here really. "Ignore" is provided here + * for the curious user who wants to screw himself very badly + */ + if (QMessageBox::warning(NULL, tr("KDocker"), + tr("The window you are attempting to dock does not seem to be a" + " normal window."), QMessageBox::Abort, + QMessageBox::Ignore) == QMessageBox::Abort) + return NULL; + } + + if (!isWindowDocked(w)) return new CustomTrayLabel(w); + + TRACE("0x%x is not docked", (unsigned) w); + + QMessageBox::message(tr("KDocker"), + tr("This window is already docked.\n" + "Click on system tray icon to toggle docking.")); + return NULL; +} + +bool TrayLabelMgr::isWindowDocked(Window w) +{ + QPtrListIterator<QTrayLabel> it(mTrayLabels); + for(QTrayLabel *t; (t = it.current()); ++it) + if (t->dockedWindow() == w) return true; + + return false; +} + +/* + * Forks application specified by argv. Requests root window SubstructreNotify + * notifications (for MapEvent on children). We will monitor these new windows + * to make a pid to wid mapping (see HACKING for more details) + */ +QTrayLabel *TrayLabelMgr::dockApplication(char *argv[]) +{ + pid_t pid = -1; + int filedes[2]; + char buf[4] = { 't', 'e', 'e', 'e' }; // teeeeeee :x :-* + + /* + * The pipe created here serves as a synchronization mechanism between the + * parent and the child. QTrayLabel ctor keeps looking out for newly created + * windows. Need to make sure that the application is actually exec'ed only + * after we QTrayLabel is created (it requires pid of child) + */ + pipe(filedes); + + if ((pid = fork()) == 0) + { + close(filedes[1]); + read(filedes[0], buf, sizeof(buf)); + close(filedes[0]); + + if (execvp(argv[0], argv) == -1) + { + qDebug(tr("Failed to exec [%1]: %2").arg(argv[0]).arg(strerror(errno))); + ::exit(0); // will become a zombie in some systems :( + return NULL; + } + } + + if (pid == -1) + { + QMessageBox::critical(NULL, "KDocker", + tr("Failed to fork: %1").arg(strerror(errno))); + return NULL; + } + + QStringList cmd_line; + for(int i=0;;i++) + if (argv[i]) cmd_line << argv[i]; else break; + + QTrayLabel *label = new CustomTrayLabel(cmd_line, pid); + qApp->syncX(); + write(filedes[1], buf, sizeof(buf)); + close(filedes[0]); + close(filedes[1]); + return label; +} + +/* + * Returns the number of QTrayLabels actually created but not show in the + * System Tray + */ +int TrayLabelMgr::hiddenLabelsCount(void) const +{ + QPtrListIterator<QTrayLabel> it(mTrayLabels); + int count = 0; + for(QTrayLabel *t; (t=it.current()); ++it) + if (t->dockedWindow() == None) ++count; + return count; +} + +// The number of labes that are docked in the system tray +int TrayLabelMgr::dockedLabelsCount(void) const +{ + return mTrayLabels.count() - hiddenLabelsCount(); +} + +void TrayLabelMgr::trayLabelDestroyed(QObject *t) +{ + bool reconnect = ((QObject *)mTrayLabels.getLast() == t); + mTrayLabels.removeRef((QTrayLabel*)t); + if (mTrayLabels.isEmpty()) qApp->quit(); + else if (reconnect) + { + TRACE("Reconnecting"); + connect(mTrayLabels.getFirst(), SIGNAL(sysTrayDestroyed()), + this, SLOT(sysTrayDestroyed())); + } +} + +void TrayLabelMgr::sysTrayDestroyed(void) +{ + /* + * The system tray got destroyed. This could happen when it was + * hidden/removed or killed/crashed/exited. Now we must be genteel enough + * to not pop up a box when the user is logging out. So, we set ourselves + * up to notify user after 3 seconds. + */ + QTimer::singleShot(3000, this, SLOT(notifySysTrayAbsence())); +} + +void TrayLabelMgr::notifySysTrayAbsence() +{ + SysTrayState state = sysTrayStatus(QPaintDevice::x11AppDisplay()); + + if (state == SysTrayPresent) + return; // So sweet of the systray to come back so soon + + if (QMessageBox::warning(NULL, tr("KDocker"), + tr("The System tray was hidden or removed"), + tr("Undock All"), tr("Ignore")) == 0) + undockAll(); +} + +/* + * Session Management. Always return "true". Atleast, for now + */ +bool TrayLabelMgr::restoreSession(const QString& sessionId) +{ + QString session_file = "kdocker_" + sessionId; + + QSettings settings; + settings.beginGroup(QString("/" + session_file)); + + for(int i = 1;; i++) + { + settings.beginGroup(QString("/Instance") + QString("").setNum(i)); + QString pname = settings.readEntry("/Application"); + TRACE("Restoring Application[%s]", pname.latin1()); + if (pname.isEmpty()) break; + if (settings.readBoolEntry("/LaunchOnStartup")) + { + QStringList args("kdocker"); + args += QStringList::split(" ", pname); + TRACE("Triggering AutoLaunch"); + if (!processCommand(args)) continue; + } + else + manageTrayLabel(new CustomTrayLabel(QStringList::split(" ", pname), 0)); + + QTrayLabel *tl = mTrayLabels.getFirst(); // the one that was created above + tl->restoreState(settings); + settings.endGroup(); + } + + return true; +} + +QString TrayLabelMgr::saveSession(void) +{ + QString session_file = "kdocker_" + qApp->sessionId(); + + QSettings settings; + settings.beginGroup(QString("/" + session_file)); + + TRACE("Saving session"); + + QPtrListIterator <QTrayLabel> it(mTrayLabels); + QTrayLabel *t; + int i = 1; + while ((t = it.current()) != 0) + { + ++it; + TRACE("Saving instance %i", i); + settings.beginGroup(QString("/Instance") + QString("").setNum(i)); + bool ok = t->saveState(settings); + settings.endGroup(); + if (ok) ++i; else TRACE("Saving of instance %i was skipped", i); + } + + // Aaaaaaaaaaaaaa......... + settings.removeEntry(QString("/Instance") + QString("").setNum(i)); + + return QDir::homeDirPath() + "/.qt/" + session_file + "rc"; +} + +/* + * The X11 Event Filter. Pass on events to the QTrayLabels that we created. + * The logic and the code below is a bit fuzzy. + * a) Events about windows that are being docked need to be processed only by + * the QTrayLabel object that is docking that window. + * b) Events about windows that are not docked but of interest (like + * SystemTray) need to be passed on to all QTrayLabel objects. + * c) When a QTrayLabel manages to find the window that is was looking for, we + * need not process the event further + */ +bool TrayLabelMgr::x11EventFilter(XEvent *ev) +{ + QPtrListIterator<QTrayLabel> it(mTrayLabels); + bool ret = false; + + // We pass on the event to all tray labels + for(QTrayLabel *t; (t = it.current()); ++it) + { + Window w = t->dockedWindow(); + bool res = t->x11EventFilter(ev); + if (w == (((XAnyEvent *)ev)->window)) return res; + if (w != None) ret |= res; + else if (res) return TRUE; + } + + return ret; +} + |