summaryrefslogtreecommitdiffstats
path: root/src/traylabelmgr.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/traylabelmgr.cpp')
-rw-r--r--src/traylabelmgr.cpp523
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;
+}
+