From bf3c4abb9dff716e098f05852d9a3d8ac8cbcb44 Mon Sep 17 00:00:00 2001 From: zecke <zecke> Date: Mon, 25 Aug 2003 15:00:50 +0000 Subject: Initial revision --- (limited to 'core/launcher/applauncher.cpp') diff --git a/core/launcher/applauncher.cpp b/core/launcher/applauncher.cpp new file mode 100644 index 0000000..50c1b71 --- a/dev/null +++ b/core/launcher/applauncher.cpp @@ -0,0 +1,705 @@ +/********************************************************************** +** Copyright (C) 2000-2002 Trolltech AS. All rights reserved. +** +** This file is part of the Qtopia Environment. +** +** This file may be distributed and/or modified under the terms of the +** GNU General Public License version 2 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +** See http://www.trolltech.com/gpl/ for GPL licensing information. +** +** Contact info@trolltech.com if any conditions of this licensing are +** not clear to you. +** +**********************************************************************/ + +#ifndef QTOPIA_INTERNAL_PRELOADACCESS +#define QTOPIA_INTERNAL_PRELOADACCESS +#endif +#ifndef QTOPIA_INTERNAL_FILEOPERATIONS +#define QTOPIA_INTERNAL_FILEOPERATIONS +#endif +#ifndef QTOPIA_PROGRAM_MONITOR +#define QTOPIA_PROGRAM_MONITOR +#endif +#include <qtopia/qpeglobal.h> + +#ifndef Q_OS_WIN32 +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/file.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <errno.h> +#else +#include <process.h> +#include <windows.h> +#include <winbase.h> +#endif + +#include <signal.h> +#include <sys/types.h> +#include <stdlib.h> + +#include <qtimer.h> +#include <qwindowsystem_qws.h> +#include <qmessagebox.h> +#include <qfile.h> +#include <qfileinfo.h> + +#include <qtopia/qcopenvelope_qws.h> +#include <qtopia/applnk.h> +#include <qtopia/qpeapplication.h> +#include <qtopia/config.h> +#include <qtopia/global.h> + +#include "applauncher.h" +#include "documentlist.h" + +const int AppLauncher::RAISE_TIMEOUT_MS = 5000; + +//--------------------------------------------------------------------------- + +static AppLauncher* appLauncherPtr; + +const int appStopEventID = 1290; + +class AppStoppedEvent : public QCustomEvent +{ +public: + AppStoppedEvent(int pid, int status) + : QCustomEvent( appStopEventID ), mPid(pid), mStatus(status) { } + + int pid() { return mPid; } + int status() { return mStatus; } + +private: + int mPid, mStatus; +}; + +AppLauncher::AppLauncher(QObject *parent, const char *name) + : QObject(parent, name), qlPid(0), qlReady(FALSE), + appKillerBox(0) +{ + connect(qwsServer, SIGNAL(newChannel(const QString&)), this, SLOT(newQcopChannel(const QString&))); + connect(qwsServer, SIGNAL(removedChannel(const QString&)), this, SLOT(removedQcopChannel(const QString&))); + QCopChannel* channel = new QCopChannel( "QPE/System", this ); + connect( channel, SIGNAL(received(const QCString&, const QByteArray&)), + this, SLOT(received(const QCString&, const QByteArray&)) ); + + channel = new QCopChannel( "QPE/Server", this ); + connect( channel, SIGNAL(received(const QCString&, const QByteArray&)), + this, SLOT(received(const QCString&, const QByteArray&)) ); + +#ifndef Q_OS_WIN32 + signal(SIGCHLD, signalHandler); +#else + runningAppsProc.setAutoDelete( TRUE ); +#endif + QString tmp = qApp->argv()[0]; + int pos = tmp.findRev('/'); + if ( pos > -1 ) + tmp = tmp.mid(++pos); + runningApps[::getpid()] = tmp; + + appLauncherPtr = this; + + QTimer::singleShot( 1000, this, SLOT(createQuickLauncher()) ); +} + +AppLauncher::~AppLauncher() +{ + appLauncherPtr = 0; +#ifndef Q_OS_WIN32 + signal(SIGCHLD, SIG_DFL); +#endif + if ( qlPid ) { + int status; + ::kill( qlPid, SIGTERM ); + waitpid( qlPid, &status, 0 ); + } +} + +/* We use the QCopChannel of the app as an indicator of when it has been launched + so that we can disable the busy indicators */ +void AppLauncher::newQcopChannel(const QString& channelName) +{ +// qDebug("channel %s added", channelName.data() ); + QString prefix("QPE/Application/"); + if (channelName.startsWith(prefix)) { + { + QCopEnvelope e("QPE/System", "newChannel(QString)"); + e << channelName; + } + QString appName = channelName.mid(prefix.length()); + if ( appName != "quicklauncher" ) { + emit connected( appName ); + QCopEnvelope e("QPE/System", "notBusy(QString)"); + e << appName; + } + } else if (channelName.startsWith("QPE/QuickLauncher-")) { + qDebug("Registered %s", channelName.latin1()); + int pid = channelName.mid(18).toInt(); + if (pid == qlPid) + qlReady = TRUE; + } +} + +void AppLauncher::removedQcopChannel(const QString& channelName) +{ + if (channelName.startsWith("QPE/Application/")) { + QCopEnvelope e("QPE/System", "removedChannel(QString)"); + e << channelName; + } +} + +void AppLauncher::received(const QCString& msg, const QByteArray& data) +{ + QDataStream stream( data, IO_ReadOnly ); + if ( msg == "execute(QString)" ) { + QString t; + stream >> t; + if ( !executeBuiltin( t, QString::null ) ) + execute(t, QString::null); + } else if ( msg == "execute(QString,QString)" ) { + QString t,d; + stream >> t >> d; + if ( !executeBuiltin( t, d ) ) + execute( t, d ); + } else if ( msg == "processQCop(QString)" ) { // from QPE/Server + QString t; + stream >> t; + if ( !executeBuiltin( t, QString::null ) ) + execute( t, QString::null, TRUE); + } else if ( msg == "raise(QString)" ) { + QString appName; + stream >> appName; + + if ( !executeBuiltin( appName, QString::null ) ) { + if ( !waitingHeartbeat.contains( appName ) && appKillerName != appName ) { + //qDebug( "Raising: %s", appName.latin1() ); + QCString channel = "QPE/Application/"; + channel += appName.latin1(); + + // Need to lock it to avoid race conditions with QPEApplication::processQCopFile + QFile f("/tmp/qcop-msg-" + appName); + if ( f.open(IO_WriteOnly | IO_Append) ) { +#ifndef Q_OS_WIN32 + flock(f.handle(), LOCK_EX); +#endif + QDataStream ds(&f); + QByteArray b; + QDataStream bstream(b, IO_WriteOnly); + ds << channel << QCString("raise()") << b; + f.flush(); +#ifndef Q_OS_WIN32 + flock(f.handle(), LOCK_UN); +#endif + f.close(); + } + bool alreadyRunning = isRunning( appName ); + if ( execute(appName, QString::null) ) { + int id = startTimer(RAISE_TIMEOUT_MS + alreadyRunning?2000:0); + waitingHeartbeat.insert( appName, id ); + } + } + } + } else if ( msg == "sendRunningApps()" ) { + QStringList apps; + QMap<int,QString>::Iterator it; + for( it = runningApps.begin(); it != runningApps.end(); ++it ) + apps.append( *it ); + QCopEnvelope e( "QPE/Desktop", "runningApps(QStringList)" ); + e << apps; + } else if ( msg == "appRaised(QString)" ) { + QString appName; + stream >> appName; + qDebug("Got a heartbeat from %s", appName.latin1()); + QMap<QString,int>::Iterator it = waitingHeartbeat.find(appName); + if ( it != waitingHeartbeat.end() ) { + killTimer( *it ); + waitingHeartbeat.remove(it); + } + // Check to make sure we're not waiting on user input... + if ( appKillerBox && appName == appKillerName ) { + // If we are, we kill the dialog box, and the code waiting on the result + // will clean us up (basically the user said "no"). + delete appKillerBox; + appKillerBox = 0; + appKillerName = QString::null; + } + } +} + +void AppLauncher::signalHandler(int) +{ +#ifndef Q_OS_WIN32 + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); +/* if (pid == 0 || &status == 0 ) { + qDebug("hmm, could not get return value from signal"); + } +*/ + QApplication::postEvent(appLauncherPtr, new AppStoppedEvent(pid, status) ); +#else + qDebug("Unhandled signal see by AppLauncher::signalHandler(int)"); +#endif +} + +bool AppLauncher::event(QEvent *e) +{ + if ( e->type() == appStopEventID ) { + AppStoppedEvent *ae = (AppStoppedEvent *) e; + sigStopped(ae->pid(), ae->status() ); + return TRUE; + } + + return QObject::event(e); +} + +void AppLauncher::timerEvent( QTimerEvent *e ) +{ + int id = e->timerId(); + QMap<QString,int>::Iterator it; + for ( it = waitingHeartbeat.begin(); it != waitingHeartbeat.end(); ++it ) { + if ( *it == id ) { + if ( appKillerBox ) // we're already dealing with one + return; + + appKillerName = it.key(); + killTimer( id ); + waitingHeartbeat.remove( it ); + + // qDebug("Checking in on %s", appKillerName.latin1()); + + // We store this incase the application responds while we're + // waiting for user input so we know not to delete ourselves. + appKillerBox = new QMessageBox(tr("Application Problem"), + tr("<p>%1 is not responding.</p>").arg(appKillerName) + + tr("<p>Would you like to force the application to exit?</p>"), + QMessageBox::Warning, QMessageBox::Yes, + QMessageBox::No | QMessageBox::Default, + QMessageBox::NoButton); + if (appKillerBox->exec() == QMessageBox::Yes) { + // qDebug("Killing the app!!! Bwuhahahaha!"); + int pid = pidForName(appKillerName); + if ( pid > 0 ) + kill( pid ); + } + appKillerName = QString::null; + delete appKillerBox; + appKillerBox = 0; + return; + } + } + + QObject::timerEvent( e ); +} + +#ifndef Q_OS_WIN32 +void AppLauncher::sigStopped(int sigPid, int sigStatus) +{ + int exitStatus = 0; + + bool crashed = WIFSIGNALED(sigStatus); + if ( !crashed ) { + if ( WIFEXITED(sigStatus) ) + exitStatus = WEXITSTATUS(sigStatus); + } else { + exitStatus = WTERMSIG(sigStatus); + } + + QMap<int,QString>::Iterator it = runningApps.find( sigPid ); + if ( it == runningApps.end() ) { + if ( sigPid == qlPid ) { + qDebug( "quicklauncher stopped" ); + qlPid = 0; + qlReady = FALSE; + QFile::remove("/tmp/qcop-msg-quicklauncher" ); + QTimer::singleShot( 2000, this, SLOT(createQuickLauncher()) ); + } +/* + if ( sigPid == -1 ) + qDebug("non-qtopia application exited (disregarded)"); + else + qDebug("==== no pid matching %d in list, definite bug", sigPid); +*/ + return; + } + QString appName = *it; + runningApps.remove(it); + + QMap<QString,int>::Iterator hbit = waitingHeartbeat.find(appName); + if ( hbit != waitingHeartbeat.end() ) { + killTimer( *hbit ); + waitingHeartbeat.remove( hbit ); + } + if ( appName == appKillerName ) { + appKillerName = QString::null; + delete appKillerBox; + appKillerBox = 0; + } + + /* we must disable preload for an app that crashes as the system logic relies on preloaded apps + actually being loaded. If eg. the crash happened in the constructor, we can't automatically reload + the app (withouth some timeout value for eg. 3 tries (which I think is a bad solution) + */ + bool preloadDisabled = FALSE; + if ( !DocumentList::appLnkSet ) return; + const AppLnk* app = DocumentList::appLnkSet->findExec( appName ); + if ( !app ) return; // QCop messages processed to slow? + if ( crashed && app->isPreloaded() ) { + Config cfg("Launcher"); + cfg.setGroup("Preload"); + QStringList apps = cfg.readListEntry("Apps",','); + QString exe = app->exec(); + apps.remove(exe); + cfg.writeEntry("Apps",apps,','); + preloadDisabled = TRUE; + } + + // clean up + if ( exitStatus ) { + QCopEnvelope e("QPE/System", "notBusy(QString)"); + e << app->exec(); + } +/* + // debug info + for (it = runningApps.begin(); it != runningApps.end(); ++it) { + qDebug("running according to internal list: %s, with pid %d", (*it).data(), it.key() ); + } +*/ + +#ifdef QTOPIA_PROGRAM_MONITOR + if ( crashed ) { + QString sig; + switch( exitStatus ) { + case SIGABRT: sig = "SIGABRT"; break; + case SIGALRM: sig = "SIGALRM"; break; + case SIGBUS: sig = "SIGBUS"; break; + case SIGFPE: sig = "SIGFPE"; break; + case SIGHUP: sig = "SIGHUP"; break; + case SIGILL: sig = "SIGILL"; break; + case SIGKILL: sig = "SIGKILL"; break; + case SIGPIPE: sig = "SIGPIPE"; break; + case SIGQUIT: sig = "SIGQUIT"; break; + case SIGSEGV: sig = "SIGSEGV"; break; + case SIGTERM: sig = "SIGTERM"; break; + case SIGTRAP: sig = "SIGTRAP"; break; + default: sig = QString("Unkown %1").arg(exitStatus); + } + if ( preloadDisabled ) + sig += tr("<qt><p>Fast loading has been disabled for this application. Tap and hold the application icon to reenable it.</qt>"); + + QString str = tr("<qt><b>%1</b> was terminated due to signal code %2</qt>").arg( app->name() ).arg( sig ); + QMessageBox::information(0, tr("Application terminated"), str ); + } else { + if ( exitStatus == 255 ) { //could not find app (because global returns -1) + QMessageBox::information(0, tr("Application not found"), tr("<qt>Could not locate application <b>%1</b></qt>").arg( app->exec() ) ); + } else { + QFileInfo fi(Global::tempDir() + "qcop-msg-" + appName); + if ( fi.exists() && fi.size() ) { + emit terminated(sigPid, appName); + execute( appName, QString::null ); + return; + } + } + } + +#endif + + emit terminated(sigPid, appName); +} +#else +void AppLauncher::sigStopped(int sigPid, int sigStatus) +{ + qDebug("Unhandled signal : AppLauncher::sigStopped(int sigPid, int sigStatus)"); +} +#endif // Q_OS_WIN32 + +bool AppLauncher::isRunning(const QString &app) +{ + for (QMap<int,QString>::ConstIterator it = runningApps.begin(); it != runningApps.end(); ++it) { + if ( *it == app ) { +#ifdef Q_OS_UNIX + pid_t t = ::__getpgid( it.key() ); + if ( t == -1 ) { + qDebug("appLauncher bug, %s believed running, but pid %d is not existing", app.data(), it.key() ); + runningApps.remove( it.key() ); + return FALSE; + } +#endif + return TRUE; + } + } + + return FALSE; +} + +bool AppLauncher::executeBuiltin(const QString &c, const QString &document) +{ + Global::Command* builtin = Global::builtinCommands(); + QGuardedPtr<QWidget> *running = Global::builtinRunning(); + + // Attempt to execute the app using a builtin class for the app + if (builtin) { + for (int i = 0; builtin[i].file; i++) { + if ( builtin[i].file == c ) { + if ( running[i] ) { + if ( !document.isNull() && builtin[i].documentary ) + Global::setDocument(running[i], document); + running[i]->raise(); + running[i]->show(); + running[i]->setActiveWindow(); + } else { + running[i] = builtin[i].func( builtin[i].maximized ); + } +#ifndef QT_NO_COP + QCopEnvelope e("QPE/System", "notBusy(QString)" ); + e << c; // that was quick ;-) +#endif + return TRUE; + } + } + } + + // Convert the command line in to a list of arguments + QStringList list = QStringList::split(QRegExp(" *"),c); + QString ap=list[0]; + + if ( ap == "suspend" ) { // No tr + QWSServer::processKeyEvent( 0xffff, Qt::Key_F34, FALSE, TRUE, FALSE ); + return TRUE; + } + + return FALSE; +} + +bool AppLauncher::execute(const QString &c, const QString &docParam, bool noRaise) +{ + // Convert the command line in to a list of arguments + QStringList list = QStringList::split(QRegExp(" *"),c); + if ( !docParam.isEmpty() ) + list.append( docParam ); + + QString appName = list[0]; + if ( isRunning(appName) ) { + QCString channel = "QPE/Application/"; + channel += appName.latin1(); + + // Need to lock it to avoid race conditions with QPEApplication::processQCopFile + QFile f(Global::tempDir() + "qcop-msg-" + appName); + if ( !noRaise && f.open(IO_WriteOnly | IO_Append) ) { +#ifndef Q_OS_WIN32 + flock(f.handle(), LOCK_EX); +#endif + + QDataStream ds(&f); + QByteArray b; + QDataStream bstream(b, IO_WriteOnly); + if ( !f.size() ) { + ds << channel << QCString("raise()") << b; + if ( !waitingHeartbeat.contains( appName ) && appKillerName != appName ) { + int id = startTimer(RAISE_TIMEOUT_MS); + waitingHeartbeat.insert( appName, id ); + } + } + if ( !docParam.isEmpty() ) { + bstream << docParam; + ds << channel << QCString("setDocument(QString)") << b; + } + + f.flush(); +#ifndef Q_OS_WIN32 + flock(f.handle(), LOCK_UN); +#endif + f.close(); + } + if ( QCopChannel::isRegistered(channel) ) // avoid unnecessary warnings + QCopChannel::send(channel,"QPEProcessQCop()"); + + return TRUE; + } + +#ifdef QT_NO_QWS_MULTIPROCESS + QMessageBox::warning( 0, tr("Error"), tr("Could not find the application %1").arg(c), + tr("OK"), 0, 0, 0, 1 ); +#else + + QStrList slist; + unsigned j; + for ( j = 0; j < list.count(); j++ ) + slist.append( list[j].utf8() ); + + const char **args = new const char *[slist.count() + 1]; + for ( j = 0; j < slist.count(); j++ ) + args[j] = slist.at(j); + args[j] = NULL; + +#ifndef Q_OS_WIN32 + if ( qlPid && qlReady && QFile::exists( QPEApplication::qpeDir()+"plugins/application/lib"+args[0] + ".so" ) ) { + qDebug( "Quick launching: %s", args[0] ); + if ( getuid() == 0 ) + setpriority( PRIO_PROCESS, qlPid, 0 ); + QCString qlch("QPE/QuickLauncher-"); + qlch += QString::number(qlPid); + QCopEnvelope env( qlch, "execute(QStrList)" ); + env << slist; + runningApps[qlPid] = QString(args[0]); + emit launched(qlPid, QString(args[0])); + QCopEnvelope e("QPE/System", "busy()"); + qlPid = 0; + qlReady = FALSE; + QTimer::singleShot( getuid() == 0 ? 800 : 1500, this, SLOT(createQuickLauncher()) ); + } else { + int pid = ::vfork(); + if ( !pid ) { + for ( int fd = 3; fd < 100; fd++ ) + ::close( fd ); + ::setpgid( ::getpid(), ::getppid() ); + // Try bindir first, so that foo/bar works too + ::execv( QPEApplication::qpeDir()+"bin/"+args[0], (char * const *)args ); + ::execvp( args[0], (char * const *)args ); + _exit( -1 ); + } + + runningApps[pid] = QString(args[0]); + emit launched(pid, QString(args[0])); + QCopEnvelope e("QPE/System", "busy()"); + } +#else + QProcess *proc = new QProcess(this); + if (proc){ + for (int i=0; i < slist.count(); i++) + proc->addArgument(args[i]); + connect(proc, SIGNAL(processExited()), this, SLOT(processExited())); + if (!proc->start()){ + qDebug("Unable to start application %s", args[0]); + }else{ + PROCESS_INFORMATION *procInfo = (PROCESS_INFORMATION *)proc->processIdentifier(); + if (procInfo){ + DWORD pid = procInfo->dwProcessId; + runningApps[pid] = QString(args[0]); + runningAppsProc.append(proc); + emit launched(pid, QString(args[0])); + QCopEnvelope e("QPE/System", "busy()"); + }else{ + qDebug("Unable to read process inforation #1 for %s", args[0]); + } + } + }else{ + qDebug("Unable to create process for application %s", args[0]); + return FALSE; + } +#endif +#endif //QT_NO_QWS_MULTIPROCESS + + delete [] args; + return TRUE; +} + +void AppLauncher::kill( int pid ) +{ +#ifndef Q_OS_WIN32 + ::kill( pid, SIGTERM ); +#else + for ( QProcess *proc = runningAppsProc.first(); proc; proc = runningAppsProc.next() ) { + if ( proc->processIdentifier() == pid ) { + proc->kill(); + break; + } + } +#endif +} + +int AppLauncher::pidForName( const QString &appName ) +{ + int pid = -1; + + QMap<int, QString>::Iterator it; + for (it = runningApps.begin(); it!= runningApps.end(); ++it) { + if (*it == appName) { + pid = it.key(); + break; + } + } + + return pid; +} + +void AppLauncher::createQuickLauncher() +{ + qlReady = FALSE; + qlPid = ::vfork(); + if ( !qlPid ) { + char **args = new char *[2]; + args[0] = "quicklauncher"; + args[1] = 0; + for ( int fd = 3; fd < 100; fd++ ) + ::close( fd ); + ::setpgid( ::getpid(), ::getppid() ); + // Try bindir first, so that foo/bar works too + setenv( "LD_BIND_NOW", "1", 1 ); + ::execv( QPEApplication::qpeDir()+"bin/quicklauncher", args ); + ::execvp( "quicklauncher", args ); + _exit( -1 ); + } else if ( qlPid == -1 ) { + qlPid = 0; + } else { + if ( getuid() == 0 ) + setpriority( PRIO_PROCESS, qlPid, 19 ); + } +} + +// Used only by Win32 +void AppLauncher::processExited() +{ +#ifdef Q_OS_WIN32 + qDebug("AppLauncher::processExited()"); + bool found = FALSE; + QProcess *proc = (QProcess *) sender(); + if (!proc){ + qDebug("Interanl error NULL proc"); + return; + } + + QString appName = proc->arguments()[0]; + qDebug("Removing application %s", appName.latin1()); + runningAppsProc.remove(proc); + + QMap<QString,int>::Iterator hbit = waitingHeartbeat.find(appName); + if ( hbit != waitingHeartbeat.end() ) { + killTimer( *hbit ); + waitingHeartbeat.remove( hbit ); + } + if ( appName == appKillerName ) { + appKillerName = QString::null; + delete appKillerBox; + appKillerBox = 0; + } + + // Search for the app to find its PID + QMap<int, QString>::Iterator it; + for (it = runningApps.begin(); it!= runningApps.end(); ++it){ + if (it.data() == appName){ + found = TRUE; + break; + } + } + + if (found){ + emit terminated(it.key(), it.data()); + runningApps.remove(it.key()); + }else{ + qDebug("Internal error application %s not listed as running", appName.latin1()); + } + +#endif +} + -- cgit v0.9.0.2