File indexing completed on 2024-04-14 04:52:46
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kfmclient.h" 0008 0009 #include <kio/job.h> 0010 0011 #include <KLocalizedString> 0012 #include <kprocess.h> 0013 #include <config-konqueror.h> 0014 0015 #include <kmessagebox.h> 0016 #include <kservice.h> 0017 #include <KIO/CommandLauncherJob> 0018 #include <KIO/ApplicationLauncherJob> 0019 #include <KStartupInfo> 0020 #include <kurifilter.h> 0021 #include <KConfig> 0022 #include <KConfigGroup> 0023 #include <KService> 0024 #include <KAboutData> 0025 #include <KWindowSystem> 0026 #include <KShell> 0027 #include <KSharedConfig> 0028 0029 #include <kcoreaddons_version.h> 0030 0031 #include <QApplication> 0032 #include <QDBusConnection> 0033 #include <QDir> 0034 #include <QMimeDatabase> 0035 #include <QUrl> 0036 #include <QCommandLineParser> 0037 #include <QCommandLineOption> 0038 #include <QTimer> 0039 0040 #ifdef WIN32 0041 #include <process.h> 0042 #endif 0043 0044 #include <unistd.h> 0045 0046 #include "konqclientrequest.h" 0047 #include "kfmclient_debug.h" 0048 0049 static const char appName[] = "kfmclient"; 0050 static const char version[] = "2.0"; 0051 0052 #if QT_VERSION_MAJOR < 6 0053 extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) 0054 #else 0055 int main(int argc, char **argv) 0056 #endif 0057 { 0058 QApplication app(argc, argv); 0059 KLocalizedString::setApplicationDomain("kfmclient"); 0060 0061 KAboutData aboutData(appName, i18n("kfmclient"), QLatin1String(version)); 0062 aboutData.setShortDescription(i18n("KDE tool for opening URLs from the command line")); 0063 KAboutData::setApplicationData(aboutData); 0064 0065 QCommandLineParser parser; 0066 aboutData.setupCommandLine(&parser); 0067 0068 //qCDebug(KFMCLIENT_LOG) << "kfmclient starting" << QTime::currentTime(); 0069 0070 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("noninteractive"), i18n("Non interactive use: no message boxes"))); 0071 0072 parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("commands"), i18n("Show available commands"))); 0073 0074 //This option is needed to fix a bug caused by the fact that Plasma inserts kfmclient_html in the Favorites section of the K menu. 0075 //kfmclient_html calls "kfmclient_openURL %u text/html", where %u is replaced by an URL. However, when the user 0076 //activates it from the K menu, there's no URL, so the command becomes "kfmclient openURL text/html", which causes 0077 //kfmclient to attempt to open the URL text/html. Adding this option allows to change the exec line in kfmclient_html 0078 //to kfmclient --mimetype text/html openURL %u, allowing an easy fix of the bug (see doIt()). 0079 //TODO: remove the old syntax of specifying the mimetype after the URL when building for KF6 0080 parser.addOption({{QStringLiteral("mimetype"), QStringLiteral("t")}, 0081 i18n("The mimetype of the URL. Allows Konqueror to determine in advance which component to use, making it start faster."), 0082 i18nc("the name for a the value of an option on the command line help", "type"), QString()}); 0083 0084 parser.addPositionalArgument(QStringLiteral("command"), i18n("Command (see --commands)")); 0085 0086 parser.addPositionalArgument(QStringLiteral("[URL(s)]"), i18n("Arguments for command")); 0087 0088 parser.addOption(QCommandLineOption(QStringList{"tempfile"}, i18n("The files/URLs opened by the application will be deleted after use"))); 0089 0090 parser.process(app); 0091 aboutData.processCommandLine(&parser); 0092 0093 const QStringList args = parser.positionalArguments(); 0094 0095 if (args.isEmpty() || parser.isSet(QStringLiteral("commands"))) { 0096 QTextStream ts(stdout, QIODevice::WriteOnly); 0097 ts << i18n("\nSyntax:\n"); 0098 ts << i18n(" kfmclient openURL 'url' ['mimetype']\n" 0099 " # Opens a window showing 'url'.\n" 0100 " # 'url' may be a relative path\n" 0101 " # or file name, such as . or subdir/\n" 0102 " # If 'url' is omitted, the start page is shown.\n\n"); 0103 ts << i18n(" # If 'mimetype' is specified, it will be used to determine the\n" 0104 " # component that Konqueror should use. For instance, set it to\n" 0105 " # text/html for a web page, to make it appear faster\n" 0106 " # Note: this way of specifying mimetype is deprecated.\n" 0107 " # Please use the --mimetype option\n\n"); 0108 ts << i18n(" kfmclient newTab 'url' ['mimetype']\n" 0109 " # Same as above but opens a new tab with 'url' in an existing Konqueror\n" 0110 " # window on the current active desktop if possible.\n\n"); 0111 return 0; 0112 } 0113 0114 // Use kfmclient from the session KDE version 0115 if ((args.at(0) == QLatin1String("openURL") || args.at(0) == QLatin1String("newTab")) 0116 && qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { 0117 const int version = qEnvironmentVariableIntValue("KDE_SESSION_VERSION"); 0118 if (version != 0 && version != KCOREADDONS_VERSION_MAJOR) { 0119 qCDebug(KFMCLIENT_LOG) << "Forwarding to kfmclient from KDE version " << version; 0120 char wrapper[ 10 ]; 0121 sprintf(wrapper, "kde%d", version); 0122 char **newargv = new char *[ argc + 2 ]; 0123 newargv[ 0 ] = wrapper; 0124 for (int i = 0; 0125 i < argc; 0126 ++i) { 0127 newargv[ i + 1 ] = argv[ i ]; 0128 } 0129 newargv[ argc + 1 ] = nullptr; 0130 #ifdef WIN32 0131 _execvp(wrapper, newargv); 0132 #else 0133 execvp(wrapper, newargv); 0134 #endif 0135 // just continue if failed 0136 } 0137 } 0138 0139 ClientApp client; 0140 return client.doIt(parser) ? 0 /*no error*/ : 1 /*error*/; 0141 } 0142 0143 static bool s_dbus_initialized = false; 0144 static void needDBus() 0145 { 0146 if (!s_dbus_initialized) { 0147 extern void qDBusBindToApplication(); 0148 qDBusBindToApplication(); 0149 if (!QDBusConnection::sessionBus().isConnected()) { 0150 qFatal("Session bus not found"); 0151 } 0152 s_dbus_initialized = true; 0153 } 0154 } 0155 0156 static QUrl filteredUrl(const QString &url) 0157 { 0158 KUriFilterData data; 0159 data.setData(url); 0160 data.setAbsolutePath(QDir::currentPath()); 0161 data.setCheckForExecutables(false); 0162 0163 return data.uri(); 0164 } 0165 0166 ClientApp::ClientApp() 0167 { 0168 } 0169 0170 ClientApp::BrowserApplicationParsingResult ClientApp::parseBrowserApplicationString(const QString& str) 0171 { 0172 // There is a configured browser application. 0173 // See whether it is a literal command (starting with '!') 0174 // or a service (no '!'). 0175 BrowserApplicationParsingResult res; 0176 if (str.isEmpty()) { 0177 return res; 0178 } 0179 res.isCommand = str.startsWith('!'); 0180 if (res.isCommand) { 0181 // A literal command. Split the string up into a shell 0182 // command and arguments. 0183 res.args = KShell::splitArgs(str.mid(1), KShell::AbortOnMeta); 0184 res.isValid = !res.args.isEmpty(); 0185 if (res.isValid) { 0186 res.commandOrService = res.args.takeFirst(); 0187 } 0188 else { 0189 res.error = "Parsing browser command failed"; 0190 } 0191 } else { 0192 res.commandOrService = str; 0193 res.isValid = true; 0194 } 0195 // Ensure that we are not calling ourselves recursively; 0196 // that is, the external command is not "kfmclient" or 0197 // any variation of it. 0198 if (res.commandOrService.startsWith("kfmclient")) { 0199 res.isValid = false; 0200 res.error = "Recursive external browser command or service detected"; 0201 } 0202 return res; 0203 } 0204 0205 bool ClientApp::launchExternalBrowser(const ClientApp::BrowserApplicationParsingResult& parseResult, const QUrl& url, bool tempFile) 0206 { 0207 KJob *job = nullptr; 0208 if (parseResult.isCommand) { 0209 QStringList args(parseResult.args); 0210 args << url.url(); 0211 KStartupInfo::appStarted(); 0212 job = new KIO::CommandLauncherJob(parseResult.commandOrService, args); 0213 } else { 0214 KService::Ptr service = KService::serviceByStorageId(parseResult.commandOrService); 0215 if (!service) { 0216 qCWarning(KFMCLIENT_LOG) << "External browser service not known:" << parseResult.commandOrService; 0217 return false; 0218 } 0219 auto launcherJob = new KIO::ApplicationLauncherJob(service); 0220 launcherJob->setUrls({url}); 0221 if (tempFile) { 0222 launcherJob->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles); 0223 } 0224 job = launcherJob; 0225 } 0226 QObject::connect(job, &KJob::result, this, &ClientApp::slotResult); 0227 job->setUiDelegate(nullptr); 0228 job->start(); 0229 return qApp->exec() == 0; 0230 } 0231 0232 bool ClientApp::createNewWindow(const QUrl &url, bool newTab, bool tempFile, const QString &mimetype) 0233 { 0234 qCDebug(KFMCLIENT_LOG) << url << "mimetype=" << mimetype; 0235 0236 bool launched = false; 0237 0238 if (url.scheme().startsWith(QLatin1String("http"))) { 0239 KConfig config(QStringLiteral("kfmclientrc")); 0240 KConfigGroup generalGroup(&config, "General"); 0241 const QString browserApp = generalGroup.readEntry("BrowserApplication"); 0242 if (!browserApp.isEmpty()) { 0243 //Parse the BrowserApplication string and act accordingly 0244 BrowserApplicationParsingResult parseRes = parseBrowserApplicationString(browserApp); 0245 qCDebug(KFMCLIENT_LOG) << "Using external browser" << (parseRes.isCommand ? "command" : "service") << browserApp; 0246 if (parseRes.isValid) { 0247 launched = launchExternalBrowser(parseRes, url, tempFile); 0248 } else { 0249 qCWarning(KFMCLIENT_LOG) << parseRes.error; 0250 } 0251 } 0252 } 0253 0254 if (!launched) { 0255 needDBus(); 0256 // Launch Konqueror, or reuse an existing instance if possible. 0257 KonqClientRequest req; 0258 req.setUrl(url); 0259 req.setNewTab(newTab); 0260 req.setTempFile(tempFile); 0261 req.setMimeType(mimetype); 0262 launched = req.openUrl(); 0263 } 0264 0265 return launched; 0266 } 0267 0268 bool ClientApp::openProfile(const QString &profileName, const QUrl &url, const QString &mimetype) 0269 { 0270 Q_UNUSED(profileName); // the concept disappeared 0271 return createNewWindow(url, false, false, mimetype); 0272 } 0273 0274 void ClientApp::delayedQuit() 0275 { 0276 // Quit in 2 seconds. This leaves time for OpenUrlJob to pop up 0277 // "app not found" in KProcessRunner, if that was the case. 0278 QTimer::singleShot(2000, qApp, &QApplication::quit); 0279 } 0280 0281 static void checkArgumentCount(int count, int min, int max) 0282 { 0283 if (count < min) { 0284 fprintf(stderr, "%s: %s", appName, i18n("Syntax error, not enough arguments\n").toLocal8Bit().data()); 0285 ::exit(1); 0286 } 0287 if (max && (count > max)) { 0288 fprintf(stderr, "%s: %s", appName, i18n("Syntax error, too many arguments\n").toLocal8Bit().data()); 0289 ::exit(1); 0290 } 0291 } 0292 0293 bool ClientApp::doIt(const QCommandLineParser &parser) 0294 { 0295 const QStringList args = parser.positionalArguments(); 0296 int argc = args.count(); 0297 checkArgumentCount(argc, 1, 0); 0298 0299 if (!parser.isSet(QStringLiteral("noninteractive"))) { 0300 m_interactive = false; 0301 } 0302 QString command = args.at(0); 0303 0304 if (command == QLatin1String("openURL") || command == QLatin1String("newTab")) { 0305 checkArgumentCount(argc, 1, 3); 0306 const bool tempFile = parser.isSet(QStringLiteral("tempfile")); 0307 0308 QUrl url = argc > 1 ? filteredUrl(args.at(1)) : QUrl(); 0309 0310 //If the given URL is empty, show the start page 0311 if (url.isEmpty()) { 0312 KConfigGroup grp = KSharedConfig::openConfig(QStringLiteral("konquerorrc"))->group("UserSettings"); 0313 url = QUrl(grp.readEntry("StartURL", QStringLiteral("konq:konqueror"))); 0314 } 0315 0316 QString mimetype = argc == 3 ? args.at(2) : QString(); 0317 if (mimetype.isEmpty()) { 0318 mimetype = parser.value(QStringLiteral("mimetype")); 0319 } 0320 0321 return createNewWindow(url, command == QLatin1String("newTab"), tempFile, mimetype); 0322 } else if (command == QLatin1String("openProfile")) { // deprecated command, kept for compat 0323 checkArgumentCount(argc, 2, 3); 0324 QUrl url; 0325 if (argc == 3) { 0326 url = QUrl::fromUserInput(args.at(2), QDir::currentPath()); 0327 } 0328 return openProfile(args.at(1), url); 0329 } else if (command == QLatin1String("exec") && argc >= 2) { 0330 // compatibility with KDE 3 and xdg-open 0331 QStringList kioclientArgs; 0332 if (!m_interactive) { 0333 kioclientArgs << QStringLiteral("--noninteractive"); 0334 } 0335 kioclientArgs << QStringLiteral("exec") << args.at(1); 0336 if (argc == 3) { 0337 kioclientArgs << args.at(2); 0338 } 0339 0340 int ret = KProcess::execute(QStringLiteral("kioclient5"), kioclientArgs); 0341 return ret == 0; 0342 } else { 0343 fprintf(stderr, "%s: %s", appName, i18n("Syntax error, unknown command '%1'\n", command).toLocal8Bit().data()); 0344 return false; 0345 } 0346 return true; 0347 } 0348 0349 0350 void ClientApp::slotResult(KJob *job) 0351 { 0352 if (job->error()) { 0353 qApp->exit(1); 0354 } else { 0355 delayedQuit(); 0356 } 0357 }