/* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krun.h" #include #include #include #include #include #include #include #include "kuserprofile.h" #include "kmimetype.h" #include "kmimemagic.h" #include "tdeio/job.h" #include "tdeio/global.h" #include "tdeio/scheduler.h" #include "tdeio/netaccess.h" #include "tdefile/kopenwith.h" #include "tdefile/tderecentdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_WS_X11 #include #endif class KRun::KRunPrivate { public: KRunPrivate() { m_showingError = false; } bool m_showingError; bool m_runExecutables; TQString m_preferredService; TQString m_externalBrowser; TQString m_localPath; TQString m_suggestedFileName; TQGuardedPtr m_window; TQCString m_asn; }; pid_t KRun::runURL( const KURL& u, const TQString& _mimetype ) { return runURL( u, _mimetype, false, true, TQString::null ); } pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile ) { return runURL( u, _mimetype, tempFile, true, TQString::null ); } pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile, bool runExecutables ) { return runURL( u, _mimetype, tempFile, runExecutables, TQString::null ); } bool KRun::isExecutableFile( const KURL& url, const TQString &mimetype ) { if ( !url.isLocalFile() ) return false; TQFileInfo file( url.path() ); if ( file.isExecutable() ) // Got a prospective file to run { KMimeType::Ptr mimeType = KMimeType::mimeType( mimetype ); if ( mimeType->is("application/x-executable") || mimeType->is("application/x-executable-script") ) return true; } return false; } pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, bool tempFile, bool runExecutables, const TQString& suggestedFileName ) { return runURL( u, _mimetype, NULL, "", tempFile, runExecutables, suggestedFileName ); } // This is called by foundMimeType, since it knows the mimetype of the URL pid_t KRun::runURL( const KURL& u, const TQString& _mimetype, TQWidget* window, const TQCString& asn, bool tempFile, bool runExecutables, const TQString& suggestedFileName ) { bool noRun = false; bool noAuth = false; if ( _mimetype == "inode/directory-locked" ) { KMessageBoxWrapper::error( window, i18n("Unable to enter %1.\nYou do not have access rights to this location.").arg(u.htmlURL()) ); return 0; } else if ( (_mimetype == "application/x-desktop") || (_mimetype == "media/builtin-mydocuments") || (_mimetype == "media/builtin-mycomputer") || (_mimetype == "media/builtin-mynetworkplaces") || (_mimetype == "media/builtin-printers") || (_mimetype == "media/builtin-trash") || (_mimetype == "media/builtin-webbrowser") ) { if ( u.isLocalFile() && runExecutables ) return KDEDesktopMimeType::run( u, true ); } else if ( isExecutableFile(u, _mimetype) ) { if ( u.isLocalFile() && runExecutables) { if (kapp->authorize("shell_access")) { TQString path = u.path(); shellQuote( path ); return (KRun::runCommand(path, TQString::null, TQString::null, window, asn)); // just execute the url as a command // ## TODO implement deleting the file if tempFile==true } else { noAuth = true; } } else if (_mimetype == "application/x-executable") noRun = true; } else if ( isExecutable(_mimetype) ) { if (!runExecutables) noRun = true; if (!kapp->authorize("shell_access")) noAuth = true; } if ( noRun ) { KMessageBox::sorry( window, i18n("The file %1 is an executable program. " "For safety it will not be started.").arg(u.htmlURL())); return 0; } if ( noAuth ) { KMessageBoxWrapper::error( window, i18n("You do not have permission to run %1.").arg(u.htmlURL()) ); return 0; } KURL::List lst; lst.append( u ); static const TQString& app_str = TDEGlobal::staticQString("Application"); KService::Ptr offer = KServiceTypeProfile::preferredService( _mimetype, app_str ); if ( !offer ) { // Open-with dialog // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog ! // Hmm, in fact KOpenWithDlg::setServiceType already guesses the mimetype from the first URL of the list... return displayOpenWithDialog( lst, tempFile, suggestedFileName ); } return KRun::run( *offer, lst, window, asn, tempFile, suggestedFileName ); } bool KRun::displayOpenWithDialog( const KURL::List& lst ) { return displayOpenWithDialog( lst, false, TQString::null ); } bool KRun::displayOpenWithDialog( const KURL::List& lst, bool tempFiles ) { return displayOpenWithDialog( lst, tempFiles, TQString::null ); } bool KRun::displayOpenWithDialog( const KURL::List& lst, bool tempFiles, const TQString& suggestedFileName ) { if (kapp && !kapp->authorizeTDEAction("openwith")) { // TODO: Better message, i18n freeze :-( KMessageBox::sorry(0L, i18n("You are not authorized to open this file.")); return false; } KOpenWithDlg l( lst, i18n("Open with:"), TQString::null, 0L ); if ( l.exec() ) { KService::Ptr service = l.service(); if ( !!service ) return KRun::run( *service, lst, 0 /*window*/, tempFiles, suggestedFileName ); kdDebug(7010) << "No service set, running " << l.text() << endl; return KRun::run( l.text(), lst, suggestedFileName ); // TODO handle tempFiles } return false; } void KRun::shellQuote( TQString &_str ) { // Credits to Walter, says Bernd G. :) if (_str.isEmpty()) // Don't create an explicit empty parameter return; TQChar q('\''); _str.replace(q, "'\\''").prepend(q).append(q); } class KRunMX1 : public KMacroExpanderBase { public: KRunMX1( const KService &_service ) : KMacroExpanderBase( '%' ), hasUrls( false ), hasSpec( false ), service( _service ) {} bool hasUrls:1, hasSpec:1; protected: virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ); private: const KService &service; }; int KRunMX1::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) { uint option = str[pos + 1]; switch( option ) { case 'c': ret << service.name().replace( '%', "%%" ); break; case 'k': ret << service.desktopEntryPath().replace( '%', "%%" ); break; case 'i': ret << "-icon" << service.icon().replace( '%', "%%" ); break; case 'm': ret << "-miniicon" << service.icon().replace( '%', "%%" ); break; case 'u': case 'U': hasUrls = true; /* fallthrough */ case 'f': case 'F': case 'n': case 'N': case 'd': case 'D': case 'v': hasSpec = true; /* fallthrough */ default: return -2; // subst with same and skip } return 2; } class KRunMX2 : public KMacroExpanderBase { public: KRunMX2( const KURL::List &_urls ) : KMacroExpanderBase( '%' ), ignFile( false ), urls( _urls ) {} bool ignFile:1; protected: virtual int expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ); private: void subst( int option, const KURL &url, TQStringList &ret ); const KURL::List &urls; }; void KRunMX2::subst( int option, const KURL &url, TQStringList &ret ) { switch( option ) { case 'u': ret << url.pathOrURL(); break; case 'd': ret << url.directory(); break; case 'f': ret << url.path(); break; case 'n': ret << url.fileName(); break; case 'v': if (url.isLocalFile() && TQFile::exists( url.path() ) ) ret << KDesktopFile( url.path(), true ).readEntry( "Dev" ); break; } return; } int KRunMX2::expandEscapedMacro( const TQString &str, uint pos, TQStringList &ret ) { uint option = str[pos + 1]; switch( option ) { case 'f': case 'u': case 'n': case 'd': case 'v': if( urls.isEmpty() ) { if (!ignFile) kdDebug() << "KRun::processDesktopExec: No URLs supplied to single-URL service " << str << endl; } else if( urls.count() > 1 ) kdWarning() << "KRun::processDesktopExec: " << urls.count() << " URLs supplied to single-URL service " << str << endl; else subst( option, urls.first(), ret ); break; case 'F': case 'U': case 'N': case 'D': option += 'a' - 'A'; for( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it ) subst( option, *it, ret ); break; case '%': ret = "%"; break; default: return -2; // subst with same and skip } return 2; } // BIC: merge methods below TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell) { return processDesktopExec( _service, _urls, has_shell, false, TQString::null ); } TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell /* KDE4: remove */, bool tempFiles) { return processDesktopExec( _service, _urls, has_shell, tempFiles, TQString::null ); } TQStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell /* KDE4: remove */, bool tempFiles, const TQString& suggestedFileName) { TQString exec = _service.exec(); TQStringList result; bool appHasTempFileOption; KRunMX1 mx1( _service ); KRunMX2 mx2( _urls ); /// compatibility hack -- KDE 4: remove TQRegExp re("^\\s*(?:/bin/)?sh\\s+-c\\s+(.*)$"); if (!re.search( exec )) { exec = TQString(re.cap( 1 )).stripWhiteSpace(); for (uint pos = 0; pos < exec.length(); ) { TQChar c = exec.unicode()[pos]; if (c != '\'' && c != '"') goto synerr; // what else can we do? after normal parsing the substs would be insecure int pos2 = exec.find( c, pos + 1 ) - 1; if (pos2 < 0) goto synerr; // quoting error memcpy( (void *)(exec.unicode() + pos), exec.unicode() + pos + 1, (pos2 - pos) * sizeof(TQChar)); pos = pos2; exec.remove( pos, 2 ); } } if( !mx1.expandMacrosShellQuote( exec ) ) goto synerr; // error in shell syntax // FIXME: the current way of invoking tdeioexec disables term and su use // Check if we need "tempexec" (tdeioexec in fact) appHasTempFileOption = tempFiles && _service.property("X-TDE-HasTempFileOption").toBool(); if( tempFiles && !appHasTempFileOption && _urls.size() ) { result << "tdeioexec" << "--tempfiles" << exec; result += _urls.toStringList(); if (has_shell) result = KShell::joinArgs( result ); return result; } // Check if we need tdeioexec if( !mx1.hasUrls ) { for( KURL::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it ) if ( !(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it) ) { // We need to run the app through tdeioexec result << "tdeioexec"; if ( tempFiles ) result << "--tempfiles"; if ( !suggestedFileName.isEmpty() ) { result << "--suggestedfilename"; result << suggestedFileName; } result << exec; result += _urls.toStringList(); if (has_shell) result = KShell::joinArgs( result ); return result; } } if ( appHasTempFileOption ) exec += " --tempfile"; // Did the user forget to append something like '%f'? // If so, then assume that '%f' is the right choice => the application // accepts only local files. if( !mx1.hasSpec ) { exec += " %f"; mx2.ignFile = true; } mx2.expandMacrosShellQuote( exec ); // syntax was already checked, so don't check return value /* 1 = need_shell, 2 = terminal, 4 = su, 8 = has_shell 0 << split(cmd) 1 << "sh" << "-c" << cmd 2 << split(term) << "-e" << split(cmd) 3 << split(term) << "-e" << "sh" << "-c" << cmd 4 << "tdesu" << "-u" << user << "-c" << cmd 5 << "tdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd)) 6 << split(term) << "-e" << "su" << user << "-c" << cmd 7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd)) 8 << cmd 9 << cmd a << term << "-e" << cmd b << term << "-e" << ("sh -c " + quote(cmd)) c << "tdesu" << "-u" << user << "-c" << quote(cmd) d << "tdesu" << "-u" << user << "-c" << quote("sh -c " + quote(cmd)) e << term << "-e" << "su" << user << "-c" << quote(cmd) f << term << "-e" << "su" << user << "-c" << quote("sh -c " + quote(cmd)) "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh. this could be optimized with the -s switch of some su versions (e.g., debian linux). */ if (_service.terminal()) { TDEConfigGroupSaver gs(TDEGlobal::config(), "General"); TQString terminal = TDEGlobal::config()->readPathEntry("TerminalApplication", "konsole"); if (terminal == "konsole") terminal += " -caption=%c %i %m"; terminal += " "; terminal += _service.terminalOptions(); if( !mx1.expandMacrosShellQuote( terminal ) ) { kdWarning() << "KRun: syntax error in command `" << terminal << "', service `" << _service.name() << "'" << endl; return TQStringList(); } mx2.expandMacrosShellQuote( terminal ); if (has_shell) result << terminal; else result = KShell::splitArgs( terminal ); // assuming that the term spec never needs a shell! result << "-e"; } int err; if (_service.substituteUid()) { if (_service.terminal()) result << "su"; else result << "tdesu" << "-u"; result << _service.username() << "-c"; KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err); if (err == KShell::FoundMeta) { shellQuote( exec ); exec.prepend( "/bin/sh -c " ); } else if (err != KShell::NoError) goto synerr; if (has_shell) shellQuote( exec ); result << exec; } else { if (has_shell) { if (_service.terminal()) { KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err); if (err == KShell::FoundMeta) { shellQuote( exec ); exec.prepend( "/bin/sh -c " ); } else if (err != KShell::NoError) goto synerr; } result << exec; } else { result += KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err); if (err == KShell::FoundMeta) result << "/bin/sh" << "-c" << exec; else if (err != KShell::NoError) goto synerr; } } return result; synerr: kdWarning() << "KRun: syntax error in command `" << _service.exec() << "', service `" << _service.name() << "'" << endl; return TQStringList(); } //static TQString KRun::binaryName( const TQString & execLine, bool removePath ) { // Remove parameters and/or trailing spaces. TQStringList args = KShell::splitArgs( execLine ); for (TQStringList::ConstIterator it = args.begin(); it != args.end(); ++it) if (!(*it).contains('=')) // Remove path if wanted return removePath ? (*it).mid(TQString(*it).findRev('/') + 1) : *it; return TQString(); } static pid_t runCommandInternal( TDEProcess* proc, const KService* service, const TQString& binName, const TQString &execName, const TQString & iconName, TQWidget* window, TQCString asn ) { if (service && !service->desktopEntryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile( service->desktopEntryPath() )) { kdWarning() << "No authorization to execute " << service->desktopEntryPath() << endl; KMessageBox::sorry(window, i18n("You are not authorized to execute this file.")); return 0; } TQString bin = KRun::binaryName( binName, true ); #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification bool silent; TQCString wmclass; TDEStartupInfoId id; bool startup_notify = ( asn != "0" && KRun::checkStartupNotify( binName, service, &silent, &wmclass )); if( startup_notify ) { id.initId( asn ); id.setupStartupEnv(); TDEStartupInfoData data; data.setHostname(); data.setBin( bin ); if( !execName.isEmpty()) data.setName( execName ); else if( service && !service->name().isEmpty()) data.setName( service->name()); data.setDescription( i18n( "Launching %1" ).arg( data.name())); if( !iconName.isEmpty()) data.setIcon( iconName ); else if( service && !service->icon().isEmpty()) data.setIcon( service->icon()); if( !wmclass.isEmpty()) data.setWMClass( wmclass ); if( silent ) data.setSilent( TDEStartupInfoData::Yes ); data.setDesktop( KWin::currentDesktop()); if( window ) data.setLaunchedBy( window->winId()); TDEStartupInfo::sendStartup( id, data ); } pid_t pid = TDEProcessRunner::run( proc, binName, id ); if( startup_notify && pid ) { TDEStartupInfoData data; data.addPid( pid ); TDEStartupInfo::sendChange( id, data ); TDEStartupInfo::resetStartupEnv(); } return pid; #else Q_UNUSED( execName ); Q_UNUSED( iconName ); return TDEProcessRunner::run( proc, bin ); #endif } // This code is also used in tdelauncher. bool KRun::checkStartupNotify( const TQString& /*binName*/, const KService* service, bool* silent_arg, TQCString* wmclass_arg ) { bool silent = false; TQCString wmclass; if( service && service->property( "StartupNotify" ).isValid()) { silent = !service->property( "StartupNotify" ).toBool(); wmclass = service->property( "StartupWMClass" ).toString().latin1(); } else if( service && service->property( "X-TDE-StartupNotify" ).isValid()) { silent = !service->property( "X-TDE-StartupNotify" ).toBool(); wmclass = service->property( "X-TDE-WMClass" ).toString().latin1(); } else // non-compliant app { if( service ) { if( service->type() == "Application" ) wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant else return false; // no startup notification at all } else { #if 0 // Create startup notification even for apps for which there shouldn't be any, // just without any visual feedback. This will ensure they'll be positioned on the proper // virtual desktop, and will get user timestamp from the ASN ID. wmclass = "0"; silent = true; #else // That unfortunately doesn't work, when the launched non-compliant application // launches another one that is compliant and there is any delay inbetween (bnc:#343359) return false; #endif } } if( silent_arg != NULL ) *silent_arg = silent; if( wmclass_arg != NULL ) *wmclass_arg = wmclass; return true; } static pid_t runTempService( const KService& _service, const KURL::List& _urls, TQWidget* window, const TQCString& asn, bool tempFiles, const TQString& suggestedFileName ) { if (!_urls.isEmpty()) { kdDebug(7010) << "runTempService: first url " << _urls.first().url() << endl; } TQStringList args; if ((_urls.count() > 1) && !_service.allowMultipleFiles()) { // We need to launch the application N times. That sucks. // We ignore the result for application 2 to N. // For the first file we launch the application in the // usual way. The reported result is based on this // application. KURL::List::ConstIterator it = _urls.begin(); while(++it != _urls.end()) { KURL::List singleUrl; singleUrl.append(*it); runTempService( _service, singleUrl, window, "", tempFiles, suggestedFileName ); } KURL::List singleUrl; singleUrl.append(_urls.first()); args = KRun::processDesktopExec(_service, singleUrl, false, tempFiles, suggestedFileName); } else { args = KRun::processDesktopExec(_service, _urls, false, tempFiles, suggestedFileName); } kdDebug(7010) << "runTempService: TDEProcess args=" << args << endl; TDEProcess * proc = new TDEProcess; *proc << args; if (!_service.path().isEmpty()) proc->setWorkingDirectory(_service.path()); return runCommandInternal( proc, &_service, KRun::binaryName( _service.exec(), false ), _service.name(), _service.icon(), window, asn ); } // WARNING: don't call this from processDesktopExec, since tdelauncher uses that too... static KURL::List resolveURLs( const KURL::List& _urls, const KService& _service ) { // Check which protocols the application supports. // This can be a list of actual protocol names, or just TDEIO for KDE apps. TQStringList supportedProtocols = _service.property("X-TDE-Protocols").toStringList(); KRunMX1 mx1( _service ); TQString exec = _service.exec(); if ( mx1.expandMacrosShellQuote( exec ) && !mx1.hasUrls ) { Q_ASSERT( supportedProtocols.isEmpty() ); // huh? If you support protocols you need %u or %U... } else { if ( supportedProtocols.isEmpty() ) { // compat mode: assume TDEIO if not set and it's a KDE app TQStringList categories = _service.property("Categories").toStringList(); if (( categories.find("TDE") != categories.end() ) && ( categories.find("KDE") != categories.end() )) supportedProtocols.append( "TDEIO" ); else { // if no KDE app, be a bit over-generic supportedProtocols.append( "http"); supportedProtocols.append( "ftp"); } } } kdDebug(7010) << "supportedProtocols:" << supportedProtocols << endl; KURL::List urls( _urls ); if ( supportedProtocols.find( "TDEIO" ) == supportedProtocols.end() ) { for( KURL::List::Iterator it = urls.begin(); it != urls.end(); ++it ) { const KURL url = *it; bool supported = url.isLocalFile() || supportedProtocols.find( url.protocol().lower() ) != supportedProtocols.end(); kdDebug(7010) << "Looking at url=" << url << " supported=" << supported << endl; if ( !supported && KProtocolInfo::protocolClass(url.protocol()) == ":local" ) { // Maybe we can resolve to a local URL? KURL localURL = TDEIO::NetAccess::mostLocalURL( url, 0 ); if ( localURL != url ) { *it = localURL; kdDebug(7010) << "Changed to " << localURL << endl; } } } } return urls; } // BIC merge methods below pid_t KRun::run( const KService& _service, const KURL::List& _urls ) { return run( _service, _urls, 0, false, TQString::null ); } pid_t KRun::run( const KService& _service, const KURL::List& _urls, bool tempFiles ) { return run( _service, _urls, 0, tempFiles, TQString::null ); } pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, bool tempFiles ) { return run( _service, _urls, window, "", tempFiles, TQString::null ); } pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, const TQCString& asn, bool tempFiles ) { return run( _service, _urls, window, asn, tempFiles, TQString::null ); } pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, bool tempFiles, const TQString& suggestedFileName ) { return run( _service, _urls, window, "", tempFiles, suggestedFileName ); } pid_t KRun::run( const KService& _service, const KURL::List& _urls, TQWidget* window, const TQCString& asn, bool tempFiles, const TQString& suggestedFileName ) { if (!_service.desktopEntryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile( _service.desktopEntryPath())) { kdWarning() << "No authorization to execute " << _service.desktopEntryPath() << endl; KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); return 0; } if ( !tempFiles ) { // Remember we opened those urls, for the "recent documents" menu in kicker KURL::List::ConstIterator it = _urls.begin(); for(; it != _urls.end(); ++it) { //kdDebug(7010) << "TDERecentDocument::adding " << (*it).url() << endl; TDERecentDocument::add( *it, _service.desktopEntryName() ); } } if ( tempFiles || _service.desktopEntryPath().isEmpty() || !suggestedFileName.isEmpty() ) { return runTempService(_service, _urls, window, asn, tempFiles, suggestedFileName); } kdDebug(7010) << "KRun::run " << _service.desktopEntryPath() << endl; if (!_urls.isEmpty()) { kdDebug(7010) << "First url " << _urls.first().url() << endl; } // Resolve urls if needed, depending on what the app supports const KURL::List urls = resolveURLs( _urls, _service ); TQString error; int pid = 0; TQCString myasn = asn; // startServiceByDesktopPath() doesn't take TQWidget*, add it to the startup info now if( window != NULL ) { if( myasn.isEmpty()) myasn = TDEStartupInfo::createNewStartupId(); if( myasn != "0" ) { TDEStartupInfoId id; id.initId( myasn ); TDEStartupInfoData data; data.setLaunchedBy( window->winId()); TDEStartupInfo::sendChange( id, data ); } } int i = TDEApplication::startServiceByDesktopPath( _service.desktopEntryPath(), urls.toStringList(), &error, 0L, &pid, myasn ); if (i != 0) { kdDebug(7010) << error << endl; KMessageBox::sorry( window, error ); return 0; } kdDebug(7010) << "startServiceByDesktopPath worked fine" << endl; return (pid_t) pid; } pid_t KRun::run( const TQString& _exec, const KURL::List& _urls, const TQString& _name, const TQString& _icon, const TQString&, const TQString&) { KService::Ptr service = new KService(_name, _exec, _icon); return run(*service, _urls); } pid_t KRun::runCommand( TQString cmd ) { return KRun::runCommand( cmd, TQString::null, TQString::null, NULL, "" ); } pid_t KRun::runCommand( const TQString& cmd, const TQString &execName, const TQString & iconName ) { return KRun::runCommand( cmd, execName, iconName, NULL, "" ); } pid_t KRun::runCommand( const TQString& cmd, const TQString &execName, const TQString & iconName, TQWidget* window, const TQCString& asn ) { kdDebug(7010) << "runCommand " << cmd << "," << execName << endl; TDEProcess * proc = new TDEProcess; proc->setUseShell(true); *proc << cmd; KService::Ptr service = KService::serviceByDesktopName( binaryName( execName, true ) ); TQString bin = binaryName( cmd, false ); int pos = bin.findRev( '/' ); if (pos != -1) { proc->setWorkingDirectory( bin.mid(0, pos) ); } return runCommandInternal( proc, service.data(), binaryName( execName, false ), execName, iconName, window, asn ); } KRun::KRun( const KURL& url, mode_t mode, bool isLocalFile, bool showProgressInfo ) :m_timer(0,"KRun::timer") { init (url, 0, "", mode, isLocalFile, showProgressInfo); } KRun::KRun( const KURL& url, TQWidget* window, mode_t mode, bool isLocalFile, bool showProgressInfo ) :m_timer(0,"KRun::timer") { init (url, window, "", mode, isLocalFile, showProgressInfo); } KRun::KRun( const KURL& url, TQWidget* window, const TQCString& asn, mode_t mode, bool isLocalFile, bool showProgressInfo ) :m_timer(0,"KRun::timer") { init (url, window, asn, mode, isLocalFile, showProgressInfo); } void KRun::init ( const KURL& url, TQWidget* window, const TQCString& asn, mode_t mode, bool isLocalFile, bool showProgressInfo ) { m_bFault = false; m_bAutoDelete = true; m_bProgressInfo = showProgressInfo; m_bFinished = false; m_job = 0L; m_strURL = url; m_bScanFile = false; m_bIsDirectory = false; m_bIsLocalFile = isLocalFile; m_mode = mode; d = new KRunPrivate; d->m_runExecutables = true; d->m_window = window; d->m_asn = asn; setEnableExternalBrowser(true); // Start the timer. This means we will return to the event // loop and do initialization afterwards. // Reason: We must complete the constructor before we do anything else. m_bInit = true; connect( &m_timer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotTimeout() ) ); m_timer.start( 0, true ); kdDebug(7010) << " new KRun " << this << " " << url.prettyURL() << " timer=" << &m_timer << endl; kapp->ref(); } void KRun::init() { kdDebug(7010) << "INIT called" << endl; bool bypassErrorMessage = false; if (m_strURL.url().startsWith("$(")) { // check for environment variables and make necessary translations TQString aValue = m_strURL.url(); int nDollarPos = aValue.find( '$' ); while( nDollarPos != -1 && nDollarPos+1 < static_cast(aValue.length())) { // there is at least one $ if( (aValue)[nDollarPos+1] == '(' ) { uint nEndPos = nDollarPos+1; // the next character is no $ while ( (nEndPos <= aValue.length()) && (aValue[nEndPos]!=')') ) nEndPos++; nEndPos++; TQString cmd = aValue.mid( nDollarPos+2, nEndPos-nDollarPos-3 ); TQString result; FILE *fs = popen(TQFile::encodeName(cmd).data(), "r"); if (fs) { { TQTextStream ts(fs, IO_ReadOnly); result = ts.read().stripWhiteSpace(); } pclose(fs); } aValue.replace( nDollarPos, nEndPos-nDollarPos, result ); } else if( (aValue)[nDollarPos+1] != '$' ) { uint nEndPos = nDollarPos+1; // the next character is no $ TQString aVarName; if (aValue[nEndPos]=='{') { while ( (nEndPos <= aValue.length()) && (aValue[nEndPos]!='}') ) nEndPos++; nEndPos++; aVarName = aValue.mid( nDollarPos+2, nEndPos-nDollarPos-3 ); } else { while ( nEndPos <= aValue.length() && (aValue[nEndPos].isNumber() || aValue[nEndPos].isLetter() || aValue[nEndPos]=='_' ) ) nEndPos++; aVarName = aValue.mid( nDollarPos+1, nEndPos-nDollarPos-1 ); } const char* pEnv = 0; if (!aVarName.isEmpty()) pEnv = getenv( aVarName.ascii() ); if( pEnv ) { // !!! Sergey A. Sukiyazov !!! // A environment variables may contain values in 8bit // locale cpecified encoding or in UTF8 encoding. aValue.replace( nDollarPos, nEndPos-nDollarPos, KStringHandler::from8Bit( pEnv ) ); } else aValue.remove( nDollarPos, nEndPos-nDollarPos ); } else { // remove one of the dollar signs aValue.remove( nDollarPos, 1 ); nDollarPos++; } nDollarPos = aValue.find( '$', nDollarPos ); } m_strURL = KURL(aValue); bypassErrorMessage = true; } if ( !m_strURL.isValid() ) { if (bypassErrorMessage == false) { d->m_showingError = true; KMessageBoxWrapper::error( d->m_window, i18n( "Malformed URL\n%1" ).arg( m_strURL.url() ) ); d->m_showingError = false; } m_bFault = true; m_bFinished = true; m_timer.start( 0, true ); return; } if ( !kapp->authorizeURLAction( "open", KURL(), m_strURL)) { TQString msg = TDEIO::buildErrorString(TDEIO::ERR_ACCESS_DENIED, m_strURL.prettyURL()); d->m_showingError = true; KMessageBoxWrapper::error( d->m_window, msg ); d->m_showingError = false; m_bFault = true; m_bFinished = true; m_timer.start( 0, true ); return; } if ( !m_bIsLocalFile && m_strURL.isLocalFile() ) m_bIsLocalFile = true; TQString exec; if (m_strURL.protocol().startsWith("http")) { exec = d->m_externalBrowser; } if ( m_bIsLocalFile ) { if ( m_mode == 0 ) { KDE_struct_stat buff; if ( KDE_stat( TQFile::encodeName(m_strURL.path()), &buff ) == -1 ) { d->m_showingError = true; KMessageBoxWrapper::error( d->m_window, i18n( "Unable to run the command specified. The file or folder %1 does not exist." ).arg( m_strURL.htmlURL() ) ); d->m_showingError = false; m_bFault = true; m_bFinished = true; m_timer.start( 0, true ); return; } m_mode = buff.st_mode; } KMimeType::Ptr mime = KMimeType::findByURL( m_strURL, m_mode, m_bIsLocalFile ); assert( mime != 0L ); kdDebug(7010) << "MIME TYPE is " << mime->name() << endl; foundMimeType( mime->name() ); return; } else if ( !exec.isEmpty() || KProtocolInfo::isHelperProtocol( m_strURL ) ) { kdDebug(7010) << "Helper protocol" << endl; bool ok = false; KURL::List urls; if (!((m_strURL.protocol().startsWith("http")) && (m_strURL.url() == "http://default.browser"))) urls.append( m_strURL ); if (exec.isEmpty()) { exec = KProtocolInfo::exec( m_strURL.protocol() ); if (exec.isEmpty()) { foundMimeType(KProtocolInfo::defaultMimetype(m_strURL)); return; } run( exec, urls ); ok = true; } else if (exec.startsWith("!")) { exec = exec.mid(1); // Literal command exec += " %u"; run( exec, urls ); ok = true; } else { KService::Ptr service = KService::serviceByStorageId( exec ); if (service) { run( *service, urls, d->m_window, d->m_asn ); ok = true; } } if (ok) { m_bFinished = true; // will emit the error and autodelete this m_timer.start( 0, true ); return; } } if ((m_strURL.protocol().startsWith("http")) && (m_strURL.url() == "http://default.browser")) { KURL::List urls; run( "kfmclient openProfile webbrowsing", urls ); m_bFinished = true; // will emit the error and autodelete this m_timer.start( 0, true ); return; } // Did we already get the information that it is a directory ? if ( S_ISDIR( m_mode ) ) { foundMimeType( "inode/directory" ); return; } // Let's see whether it is a directory if ( !KProtocolInfo::supportsListing( m_strURL ) ) { //kdDebug(7010) << "Protocol has no support for listing" << endl; // No support for listing => it can't be a directory (example: http) scanFile(); return; } kdDebug(7010) << "Testing directory (stating)" << endl; // It may be a directory or a file, let's stat TDEIO::StatJob *job = TDEIO::stat( m_strURL, true, 0 /* no details */, m_bProgressInfo ); job->setWindow (d->m_window); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), this, TQT_SLOT( slotStatResult( TDEIO::Job * ) ) ); m_job = job; kdDebug(7010) << " Job " << job << " is about stating " << m_strURL.url() << endl; } KRun::~KRun() { kdDebug(7010) << "KRun::~KRun() " << this << endl; m_timer.stop(); killJob(); kapp->deref(); kdDebug(7010) << "KRun::~KRun() done " << this << endl; delete d; } void KRun::scanFile() { kdDebug(7010) << "###### KRun::scanFile " << m_strURL.url() << endl; // First, let's check for well-known extensions // Not when there is a query in the URL, in any case. if ( m_strURL.query().isEmpty() ) { KMimeType::Ptr mime = KMimeType::findByURL( m_strURL ); assert( mime != 0L ); if ( mime->name() != "application/octet-stream" || m_bIsLocalFile ) { kdDebug(7010) << "Scanfile: MIME TYPE is " << mime->name() << endl; foundMimeType( mime->name() ); return; } } // No mimetype found, and the URL is not local (or fast mode not allowed). // We need to apply the 'TDEIO' method, i.e. either asking the server or // getting some data out of the file, to know what mimetype it is. if ( !KProtocolInfo::supportsReading( m_strURL ) ) { kdError(7010) << "#### NO SUPPORT FOR READING!" << endl; m_bFault = true; m_bFinished = true; m_timer.start( 0, true ); return; } kdDebug(7010) << this << " Scanning file " << m_strURL.url() << endl; TDEIO::TransferJob *job = TDEIO::get( m_strURL, false /*reload*/, m_bProgressInfo ); job->setWindow (d->m_window); connect(job, TQT_SIGNAL( result(TDEIO::Job *)), this, TQT_SLOT( slotScanFinished(TDEIO::Job *))); connect(job, TQT_SIGNAL( mimetype(TDEIO::Job *, const TQString &)), this, TQT_SLOT( slotScanMimeType(TDEIO::Job *, const TQString &))); m_job = job; kdDebug(7010) << " Job " << job << " is about getting from " << m_strURL.url() << endl; } void KRun::slotTimeout() { kdDebug(7010) << this << " slotTimeout called" << endl; if ( m_bInit ) { m_bInit = false; init(); return; } if ( m_bFault ) { emit error(); } if ( m_bFinished ) { emit finished(); } else { if ( m_bScanFile ) { m_bScanFile = false; scanFile(); return; } else if ( m_bIsDirectory ) { m_bIsDirectory = false; foundMimeType( "inode/directory" ); return; } } if ( m_bAutoDelete ) { delete this; return; } } void KRun::slotStatResult( TDEIO::Job * job ) { m_job = 0L; if (job->error()) { d->m_showingError = true; kdError(7010) << this << " ERROR " << job->error() << " " << job->errorString() << endl; job->showErrorDialog(); //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl; d->m_showingError = false; m_bFault = true; m_bFinished = true; // will emit the error and autodelete this m_timer.start( 0, true ); } else { kdDebug(7010) << "Finished" << endl; if(!dynamic_cast(job)) kdFatal() << "job is a " << typeid(*job).name() << " should be a StatJob" << endl; TQString knownMimeType; TDEIO::UDSEntry entry = ((TDEIO::StatJob*)job)->statResult(); TDEIO::UDSEntry::ConstIterator it = entry.begin(); for( ; it != entry.end(); it++ ) { switch( (*it).m_uds ) { case TDEIO::UDS_FILE_TYPE: if ( S_ISDIR( (mode_t)((*it).m_long) ) ) m_bIsDirectory = true; // it's a dir else m_bScanFile = true; // it's a file break; case TDEIO::UDS_MIME_TYPE: // mimetype already known? (e.g. print:/manager) knownMimeType = (*it).m_str; break; case TDEIO::UDS_LOCAL_PATH: d->m_localPath = (*it).m_str; break; default: break; } } if ( !knownMimeType.isEmpty() ) { foundMimeType( knownMimeType ); m_bFinished = true; } // We should have found something assert ( m_bScanFile || m_bIsDirectory ); // Start the timer. Once we get the timer event this // protocol server is back in the pool and we can reuse it. // This gives better performance than starting a new slave m_timer.start( 0, true ); } } void KRun::slotScanMimeType( TDEIO::Job *, const TQString &mimetype ) { if ( mimetype.isEmpty() ) kdWarning(7010) << "KRun::slotScanFinished : MimetypeJob didn't find a mimetype! Probably a tdeioslave bug." << endl; foundMimeType( mimetype ); m_job = 0; } void KRun::slotScanFinished( TDEIO::Job *job ) { m_job = 0; if (job->error()) { d->m_showingError = true; kdError(7010) << this << " ERROR (stat) : " << job->error() << " " << job->errorString() << endl; job->showErrorDialog(); //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl; d->m_showingError = false; m_bFault = true; m_bFinished = true; // will emit the error and autodelete this m_timer.start( 0, true ); } } void KRun::foundMimeType( const TQString& type ) { kdDebug(7010) << "Resulting mime type is " << type << endl; /* // Automatically unzip stuff // Disabled since the new TDEIO doesn't have filters yet. if ( type == "application/x-gzip" || type == "application/x-bzip" || type == "application/x-bzip2" ) { KURL::List lst = KURL::split( m_strURL ); if ( lst.isEmpty() ) { TQString tmp = i18n( "Malformed URL" ); tmp += "\n"; tmp += m_strURL.url(); KMessageBoxWrapper::error( 0L, tmp ); return; } if ( type == "application/x-gzip" ) lst.prepend( KURL( "gzip:/decompress" ) ); else if ( type == "application/x-bzip" ) lst.prepend( KURL( "bzip:/decompress" ) ); else if ( type == "application/x-bzip2" ) lst.prepend( KURL( "bzip2:/decompress" ) ); else if ( type == "application/x-tar" ) lst.prepend( KURL( "tar:/" ) ); // Move the HTML style reference to the leftmost URL KURL::List::Iterator it = lst.begin(); ++it; (*lst.begin()).setRef( (*it).ref() ); (*it).setRef( TQString::null ); // Create the new URL m_strURL = KURL::join( lst ); kdDebug(7010) << "Now trying with " << debugString(m_strURL.url()) << endl; killJob(); // We don't know if this is a file or a directory. Let's test this first. // (For instance a tar.gz is a directory contained inside a file) // It may be a directory or a file, let's stat TDEIO::StatJob *job = TDEIO::stat( m_strURL, m_bProgressInfo ); connect( job, TQT_SIGNAL( result( TDEIO::Job * ) ), this, TQT_SLOT( slotStatResult( TDEIO::Job * ) ) ); m_job = job; return; } */ TDEIO::TransferJob *job = ::tqqt_cast( m_job ); if ( job ) { job->putOnHold(); TDEIO::Scheduler::publishSlaveOnHold(); m_job = 0; } Q_ASSERT( !m_bFinished ); // Suport for preferred service setting, see setPreferredService if ( !d->m_preferredService.isEmpty() ) { kdDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService << endl; KService::Ptr serv = KService::serviceByDesktopName( d->m_preferredService ); if ( serv && serv->hasServiceType( type ) ) { KURL::List lst; lst.append( m_strURL ); m_bFinished = KRun::run( *serv, lst, d->m_window, d->m_asn ); /// Note: the line above means that if that service failed, we'll /// go to runURL to maybe find another service, even though a dialog /// box was displayed. That's good if runURL tries another service, /// but it's not good if it tries the same one :} } } // Resolve .desktop files from media:/, remote:/, applications:/ etc. if ( ((type == "application/x-desktop") || (type == "media/builtin-mydocuments") || (type == "media/builtin-mycomputer") || (type == "media/builtin-mynetworkplaces") || (type == "media/builtin-printers") || (type == "media/builtin-trash") || (type == "media/builtin-webbrowser")) /* or inheriting? */ && (!d->m_localPath.isEmpty()) ) { m_strURL = KURL(); m_strURL.setPath( d->m_localPath ); } if (!m_bFinished && KRun::runURL( m_strURL, type, d->m_window, d->m_asn, false, d->m_runExecutables, d->m_suggestedFileName )){ m_bFinished = true; } else{ m_bFinished = true; m_bFault = true; } m_timer.start( 0, true ); } void KRun::killJob() { if ( m_job ) { kdDebug(7010) << "KRun::killJob run=" << this << " m_job=" << m_job << endl; m_job->kill(); m_job = 0L; } } void KRun::abort() { kdDebug(7010) << "KRun::abort " << this << " m_showingError=" << d->m_showingError << endl; killJob(); // If we're showing an error message box, the rest will be done // after closing the msgbox -> don't autodelete nor emit signals now. if ( d->m_showingError ) return; m_bFault = true; m_bFinished = true; m_bInit = false; m_bScanFile = false; // will emit the error and autodelete this m_timer.start( 0, true ); } void KRun::setEnableExternalBrowser(bool b) { if (b) d->m_externalBrowser = TDEConfigGroup(TDEGlobal::config(), "General").readEntry("BrowserApplication"); else d->m_externalBrowser = TQString::null; } void KRun::setPreferredService( const TQString& desktopEntryName ) { d->m_preferredService = desktopEntryName; } void KRun::setRunExecutables(bool b) { d->m_runExecutables = b; } void KRun::setSuggestedFileName( const TQString& fileName ) { d->m_suggestedFileName = fileName; } bool KRun::isExecutable( const TQString& serviceType ) { return ( serviceType == "application/x-desktop" || serviceType == "media/builtin-mydocuments" || serviceType == "media/builtin-mycomputer" || serviceType == "media/builtin-mynetworkplaces" || serviceType == "media/builtin-printers" || serviceType == "media/builtin-trash" || serviceType == "media/builtin-webbrowser" || serviceType == "application/x-executable" || serviceType == "application/x-msdos-program" || serviceType == "application/x-shellscript" ); } /****************/ pid_t TDEProcessRunner::run(TDEProcess * p, const TQString & binName) { return (new TDEProcessRunner(p, binName))->pid(); } #ifdef Q_WS_X11 pid_t TDEProcessRunner::run(TDEProcess * p, const TQString & binName, const TDEStartupInfoId& id ) { return (new TDEProcessRunner(p, binName, id))->pid(); } #endif TDEProcessRunner::TDEProcessRunner(TDEProcess * p, const TQString & _binName ) : TQObject(), process_(p), binName( _binName ) { TQObject::connect( process_, TQT_SIGNAL(processExited(TDEProcess *)), this, TQT_SLOT(slotProcessExited(TDEProcess *))); process_->start(); if ( !process_->pid() ) slotProcessExited( process_ ); } #ifdef Q_WS_X11 TDEProcessRunner::TDEProcessRunner(TDEProcess * p, const TQString & _binName, const TDEStartupInfoId& id ) : TQObject(), process_(p), binName( _binName ), id_( id ) { TQObject::connect( process_, TQT_SIGNAL(processExited(TDEProcess *)), this, TQT_SLOT(slotProcessExited(TDEProcess *))); process_->start(); if ( !process_->pid() ) slotProcessExited( process_ ); } #endif TDEProcessRunner::~TDEProcessRunner() { delete process_; } pid_t TDEProcessRunner::pid() const { return process_->pid(); } void TDEProcessRunner::slotProcessExited(TDEProcess * p) { if (p != process_) return; // Eh ? kdDebug(7010) << "slotProcessExited " << binName << endl; kdDebug(7010) << "normalExit " << process_->normalExit() << endl; kdDebug(7010) << "exitStatus " << process_->exitStatus() << endl; bool showErr = process_->normalExit() && ( process_->exitStatus() == 127 || process_->exitStatus() == 1 ); if ( !binName.isEmpty() && ( showErr || process_->pid() == 0 ) ) { // Often we get 1 (zsh, csh) or 127 (ksh, bash) because the binary doesn't exist. // We can't just rely on that, but it's a good hint. // Before assuming its really so, we'll try to find the binName // relatively to current directory, and then in the PATH. if ( !TQFile( binName ).exists() && TDEStandardDirs::findExe( binName ).isEmpty() ) { kapp->ref(); KMessageBox::sorry( 0L, i18n("Could not find the program '%1'").arg( binName ) ); kapp->deref(); } } #ifdef Q_WS_X11 if( !id_.none()) { TDEStartupInfoData data; data.addPid( pid()); // announce this pid for the startup notification has finished data.setHostname(); TDEStartupInfo::sendFinish( id_, data ); } #endif deleteLater(); } void KRun::virtual_hook( int, void* ) { /*BASE::virtual_hook( id, data );*/ } #include "krun.moc"