/* * 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 #include #include #include #include #include #include #include "trace.h" #include "traylabelmgr.h" #include "util.h" #include #include #include TrayLabelMgr* TrayLabelMgr::gTrayLabelMgr = NULL; TrayLabelMgr* TrayLabelMgr::instance() { if (gTrayLabelMgr) return gTrayLabelMgr; TRACE("Creating new instance"); return (gTrayLabelMgr = new TrayLabelMgr()); } TrayLabelMgr::TrayLabelMgr() : mReady(false), mHiddenLabelsCount(0) { connect(&restoreSessionTimer, SIGNAL(timeout()), this, SLOT(doRestoreSession())); // Set ourselves up to be called from the application loop TQTimer::singleShot(0, this, SLOT(startup())); } TrayLabelMgr::~TrayLabelMgr() { undockAll(); } 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)) || TDEApplication::kApplication()->isRestored(); SysTrayState state = sysTrayStatus(TQPaintDevice::x11AppDisplay()); if (state != SysTrayPresent) { if (wait_time-- > 0 && do_wait) { TRACE("Will check sys tray status after 1 second"); TQTimer::singleShot(1000, this, SLOT(startup())); return; } if (KMessageBox::warningContinueCancel(NULL, state == SysTrayAbsent ? i18n("No system tray found") : i18n("System tray appears to be hidden"), i18n("TDEDocker")) == KMessageBox::Cancel) { TDEApplication::kApplication()->quit(); return; } } // Things are fine or user with OK with the state of system tray mReady = true; bool ok = false; if (TDEApplication::kApplication()->isRestored()) { restoreSession(); ok = true; } else { ok = processCommand(TDECmdLineArgs::parsedArgs()); } // 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) { TDEApplication::kApplication()->quit(); } } // Initialize a TQTrayLabel after its creation void TrayLabelMgr::manageTrayLabel(TQTrayLabel *t) { connect(t, SIGNAL(destroyed(TQObject *)), this, SLOT(trayLabelDestroyed(TQObject *))); connect(t, SIGNAL(undocked(TQTrayLabel *)), t, SLOT(deleteLater())); // All TQTrayLabels 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 TQTrayLabel prepended. Count=%i", mTrayLabels.count()); } void TrayLabelMgr::dockAnother() { TQTrayLabel *t = selectAndDock(); if (t == NULL) return; manageTrayLabel(t); t->withdraw(); t->dock(); } // Close all the windows and quit void TrayLabelMgr::quitAll() { TRACE("quitAll: number of tray labels = %i", mTrayLabels.count()); TQPtrListIterator it(mTrayLabels); TQTrayLabel *t; while ((t = it.current()) != 0) { ++it; t->close(); } } // Undock all the windows void TrayLabelMgr::undockAll() { TRACE("undockAll: number of tray labels = %i", mTrayLabels.count()); TQPtrListIterator it(mTrayLabels); TQTrayLabel *t; while ((t = it.current()) != 0) { ++it; t->undock(); } } // Process the command line bool TrayLabelMgr::processCommand(const TQStringList &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 = 50; 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].local8Bit(); } argv[argc] = NULL; // null terminate the array return processCommand(argc, const_cast(argv)); } // Process the command line bool TrayLabelMgr::processCommand(TDECmdLineArgs *args) { TQStringList argl; argl.append(TDECmdLineArgs::appName()); if (args->isSet("b")) { argl.append("-b"); } if (args->isSet("d")) { argl.append("-d"); } if (args->isSet("e")) { argl.append("-e"); } if (args->isSet("f")) { argl.append("-f"); } if (args->isSet("i")) { argl.append("-i"); argl.append(args->getOption("i")); } if (args->isSet("m")) { argl.append("-m"); } if (args->isSet("o")) { argl.append("-o"); } if (args->isSet("p")) { argl.append("-p"); argl.append(args->getOption("p")); } if (args->isSet("q")) { argl.append("-q"); } if (args->isSet("t")) { argl.append("-t"); } if (args->isSet("w")) { argl.append("-w"); argl.append(args->getOption("w")); } for (int i = 0; i < args->count(); ++i) { argl.append(args->arg(i)); } return processCommand(argl); } // 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 TDEDocker::notifyPreviousInstance() ) if (qstrcmp(argv[1], "--restore-internal") == 0) { TRACE("Restoring session (new instance request)"); restoreSession(); return true; } int option; Window w = None; const char *icon = NULL; int balloon_timeout = 4000; bool withdraw = true, skip_taskbar = false, dock_obscure = false, check_normality = true, enable_sm = true; optind = 0; // initialise the getopt static while ((option = getopt(argc, argv, "+bdefi:lmop:qtw:")) != EOF) { switch (option) { case '?': return false; case 'b': check_normality = false; break; case 'd': enable_sm = false; break; case 'e': enable_sm = true; break; case 'f': w = activeWindow(TQPaintDevice::x11AppDisplay()); TRACE("Active window is %i", (unsigned) w); break; case 'i': icon = optarg; 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(TQPaintDevice::x11AppDisplay(), w)) { tqDebug("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 TQTrayLabel *t = (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->setSessionManagement(enable_sm); t->dock(); return true; } /* * Request user to make a window selection if necessary. Dock the window. */ TQTrayLabel *TrayLabelMgr::selectAndDock(Window w, bool checkNormality) { if (w == None) { tqDebug("%s", i18n("Select the application/window to dock with button1.").local8Bit().data()); tqDebug("%s", i18n("Click any other button to abort\n").local8Bit().data()); const char *err = NULL; if ((w = selectWindow(TQPaintDevice::x11AppDisplay(), &err)) == None) { if (err) { KMessageBox::error(NULL, err, i18n("TDEDocker")); } return NULL; } } if (checkNormality && !isNormalWindow(TQPaintDevice::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 (KMessageBox::warningContinueCancel(NULL, i18n("The window you are attempting to dock does not seem to be a normal window."), i18n("TDEDocker")) == KMessageBox::Cancel) { return NULL; } } if (!isWindowDocked(w)) { return new TQTrayLabel(w); } TRACE("0x%x is alredy docked", (unsigned) w); KMessageBox::error(NULL, i18n("This window is already docked.\n" "Click on system tray icon to toggle docking."), i18n("TDEDocker")); return NULL; } bool TrayLabelMgr::isWindowDocked(Window w) { TQPtrListIterator it(mTrayLabels); for(TQTrayLabel *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) */ TQTrayLabel *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. TQTrayLabel ctor keeps looking out for newly created * windows. Need to make sure that the application is actually exec'ed only * after we TQTrayLabel 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) { tqDebug("%s", i18n("Failed to exec [%1]: %2").arg(argv[0]).arg(strerror(errno)).local8Bit().data()); // Exit the forked process only. // Using TDEApplication::kApplication()->quit() crashes the parent application. exit(0); return NULL; } } if (pid == -1) { KMessageBox::error(NULL, i18n("Failed to fork: %1").arg(strerror(errno)), i18n("Ignore")); return NULL; } TQStringList cmd_line; int i = 0; while (argv[i]) { cmd_line.append(argv[i]); ++i; } TQTrayLabel *label = new TQTrayLabel(cmd_line, pid); TDEApplication::kApplication()->syncX(); write(filedes[1], buf, sizeof(buf)); close(filedes[0]); close(filedes[1]); return label; } /* * Returns the number of TQTrayLabels actually created but not show in the * System Tray */ int TrayLabelMgr::hiddenLabelsCount(void) const { TQPtrListIterator it(mTrayLabels); int count = 0; for(TQTrayLabel *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(TQObject *t) { bool reconnect = ((TQObject *)mTrayLabels.getLast() == t); mTrayLabels.removeRef((TQTrayLabel*)t); if (mTrayLabels.isEmpty()) { TDEApplication::kApplication()->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. */ TQTimer::singleShot(3000, this, SLOT(notifySysTrayAbsence())); } void TrayLabelMgr::notifySysTrayAbsence() { SysTrayState state = sysTrayStatus(TQPaintDevice::x11AppDisplay()); if (state == SysTrayPresent) return; // So sweet of the systray to come back so soon KMessageBox::error(NULL, i18n("The System tray was hidden or removed. All applications " "will be undocked."), i18n("TDEDocker")); undockAll(); } void TrayLabelMgr::restoreSession() { // After restoring a session, the TDE session manager will relaunch the applications // that were previously docked before terminating the previous session. To avoid // launching the same apps twice, wait for a while before restoring the tdedocker // session, so that we give tdedocker a chance to simply docks the applications // already launched by TDE session manager. restoreSessionTimer.start(5000, true); } void TrayLabelMgr::doRestoreSession() { TRACE("Restoring session"); TDEConfig *config = TDEApplication::kApplication()->sessionConfig(); if (config->hasGroup("General")) { config->setGroup("General"); int count = config->readNumEntry("InstanceCount", 0); for (int i = 0; i < count; ++i) { if (!config->hasGroup(TQString::number(i))) { break; } config->setGroup(TQString::number(i)); TQString pname = config->readEntry("Application", TQString::null); if (!pname.isEmpty()) { TRACE("Restoring Application[%s]", pname.ascii()); manageTrayLabel(new TQTrayLabel(TQStringList::split(" ", pname), 0)); mTrayLabels.getFirst()->restoreState(config); } } } // Exit if no application could be restored if (mTrayLabels.isEmpty()) { TDEApplication::kApplication()->quit(); } } bool TrayLabelMgr::saveState(TQSessionManager &sm) { TRACE("Saving session"); int i = 0; TQTrayLabel *t; TDEConfig *config = TDEApplication::kApplication()->sessionConfig(); TQPtrListIterator it(mTrayLabels); for (it.toFirst(); it.current(); ++it) { t = it.current(); config->setGroup(TQString::number(i)); if (t->saveState(config)) { TRACE("Saved instance %i", i); ++i; } else { config->deleteGroup(TQString::number(i)); } } config->setGroup("General"); config->writeEntry("InstanceCount", i); return true; } /* * The X11 Event Filter. Pass on events to the TQTrayLabels 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 TQTrayLabel 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 TQTrayLabel objects. * c) When a TQTrayLabel manages to find the window that is was looking for, we * need not process the event further */ bool TrayLabelMgr::x11EventFilter(XEvent *ev) { TQPtrListIterator it(mTrayLabels); bool ret = false; // We pass on the event to all tray labels for(TQTrayLabel *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; } #include "traylabelmgr.moc"