You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
619 lines
16 KiB
619 lines
16 KiB
/*
|
|
* 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 <tqfile.h>
|
|
#include <tqtextstream.h>
|
|
|
|
#include <tdeapplication.h>
|
|
#include <tdecmdlineargs.h>
|
|
#include <tdeconfig.h>
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h>
|
|
|
|
#include "trace.h"
|
|
#include "traylabelmgr.h"
|
|
#include "util.h"
|
|
|
|
#include <X11/Xmu/WinUtil.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
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<TQTrayLabel> 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<TQTrayLabel> 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<char **>(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<TQTrayLabel> 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<TQTrayLabel> 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));
|
|
if (!mTrayLabels.getFirst()->restoreState(config))
|
|
{
|
|
// Failed to restore the application, remove the tray label
|
|
delete mTrayLabels.take(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 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<TQTrayLabel> 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<TQTrayLabel> 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"
|