diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 4c140199..077bf6cd 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,165 +1,165 @@ remove_definitions(-DQT_NO_CAST_FROM_ASCII) remove_definitions(-DQT_NO_CAST_FROM_BYTEARRAY) include(ECMAddTests) add_subdirectory(http) add_subdirectory(kcookiejar) find_package(Qt5Widgets REQUIRED) ########### unittests ############### find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) ecm_add_tests( kacltest.cpp listdirtest.cpp kmountpointtest.cpp upurltest.cpp dataprotocoltest.cpp jobtest.cpp jobremotetest.cpp kfileitemtest.cpp kprotocolinfotest.cpp ktcpsockettest.cpp globaltest.cpp mkpathjobtest.cpp threadtest.cpp udsentrytest.cpp udsentry_benchmark.cpp kcoredirlister_benchmark.cpp deletejobtest.cpp urlutiltest.cpp batchrenamejobtest.cpp ksambasharetest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) target_link_libraries(threadtest Qt5::Concurrent) ecm_add_test( http_jobtest.cpp httpserver_p.cpp TEST_NAME http_jobtest NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) include(FindGem) find_gem(ftpd) set_package_properties(Gem_ftpd PROPERTIES DESCRIPTION "Ruby gem 'ftpd' required for testing the ftp slave.") if(Gem_ftpd_FOUND) add_definitions(-DRubyExe_EXECUTABLE="${RubyExe_EXECUTABLE}") ecm_add_tests( ftptest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) endif() if(UNIX) ecm_add_tests( klocalsockettest.cpp klocalsocketservertest.cpp privilegejobtest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) endif() if (TARGET KF5::KIOGui) ecm_add_tests( favicontest.cpp - processlauncherjobtest.cpp + applicationlauncherjobtest.cpp NAME_PREFIX "kiogui-" LINK_LIBRARIES KF5::KIOCore KF5::KIOGui Qt5::Test ) target_link_libraries(favicontest Qt5::Concurrent) endif() if (TARGET KF5::KIOWidgets) ecm_add_tests( clipboardupdatertest.cpp dropjobtest.cpp kdynamicjobtrackernowidgetstest.cpp krununittest.cpp kdirlistertest.cpp kdirmodeltest.cpp kfileitemactionstest.cpp fileundomanagertest.cpp kurifiltertest.cpp kurlcompletiontest.cpp jobguitest.cpp pastetest.cpp accessmanagertest.cpp kurifiltersearchprovideractionstest.cpp NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test Qt5::DBus ) set_target_properties(krununittest PROPERTIES COMPILE_FLAGS "-DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\"\\\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\\\"\"") # Same as accessmanagertest, but using QNetworkAccessManager, to make sure we # behave the same ecm_add_test( accessmanagertest.cpp TEST_NAME accessmanagertest-qnam NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(accessmanagertest-qnam PROPERTIES COMPILE_FLAGS "-DUSE_QNAM") # Same as kurlcompletiontest, but with immediate return, and results posted by thread later ecm_add_test( kurlcompletiontest.cpp TEST_NAME kurlcompletiontest-nowait NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT") endif() if (TARGET KF5::KIOFileWidgets) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets) ecm_add_tests( kurlnavigatortest.cpp kurlcomboboxtest.cpp kdiroperatortest.cpp kfilewidgettest.cpp kfilecustomdialogtest.cpp knewfilemenutest.cpp kfilecopytomenutest.cpp kfileplacesmodeltest.cpp kfileplacesviewtest.cpp kurlrequestertest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n ) # TODO: fix symbol exports for windows -> 'KSambaShare::KSambaShare': inconsistent dll linkage if (NOT WIN32) ecm_add_test( ksambashareprivatetest.cpp ../src/core/ksambashare.cpp ../src/core/kiocoredebug.cpp TEST_NAME ksambashareprivatetest NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore Qt5::Test Qt5::Network ) endif() set_tests_properties(kiofilewidgets-kfileplacesmodeltest PROPERTIES RUN_SERIAL TRUE) set_tests_properties(kiofilewidgets-kfileplacesviewtest PROPERTIES RUN_SERIAL TRUE) endif() # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) set_property(TARGET jobtest APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_BINARY_DIR}/src/core/moc_predefs.h) endif() diff --git a/autotests/processlauncherjobtest.cpp b/autotests/applicationlauncherjobtest.cpp similarity index 84% rename from autotests/processlauncherjobtest.cpp rename to autotests/applicationlauncherjobtest.cpp index 2b5a78f6..e8140a6b 100644 --- a/autotests/processlauncherjobtest.cpp +++ b/autotests/applicationlauncherjobtest.cpp @@ -1,212 +1,212 @@ /* This file is part of the KDE libraries Copyright (c) 2014, 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 "processlauncherjobtest.h" -#include "processlauncherjob.h" +#include "applicationlauncherjobtest.h" +#include "applicationlauncherjob.h" #include "kiotesthelper.h" // createTestFile etc. #include #include #include #ifdef Q_OS_UNIX #include // kill #endif #include #include #include #include -QTEST_GUILESS_MAIN(ProcessLauncherJobTest) +QTEST_GUILESS_MAIN(ApplicationLauncherJobTest) -void ProcessLauncherJobTest::initTestCase() +void ApplicationLauncherJobTest::initTestCase() { QStandardPaths::setTestModeEnabled(true); } -void ProcessLauncherJobTest::cleanupTestCase() +void ApplicationLauncherJobTest::cleanupTestCase() { std::for_each(m_filesToRemove.begin(), m_filesToRemove.end(), [](const QString & f) { QFile::remove(f); }); } -static const char s_tempServiceName[] = "processlauncherjobtest_service.desktop"; +static const char s_tempServiceName[] = "applicationlauncherjobtest_service.desktop"; static void createSrcFile(const QString path) { QFile srcFile(path); QVERIFY2(srcFile.open(QFile::WriteOnly), qPrintable(srcFile.errorString())); srcFile.write("Hello world\n"); } -void ProcessLauncherJobTest::startProcess_data() +void ApplicationLauncherJobTest::startProcess_data() { QTest::addColumn("tempFile"); QTest::addColumn("useExec"); QTest::addColumn("numFiles"); QTest::newRow("1_file_exec") << false << true << 1; QTest::newRow("1_file_waitForStarted") << false << false << 1; QTest::newRow("1_tempfile_exec") << true << true << 1; QTest::newRow("1_tempfile_waitForStarted") << true << false << 1; QTest::newRow("2_files_exec") << false << true << 2; QTest::newRow("2_files_waitForStarted") << false << false << 2; QTest::newRow("2_tempfiles_exec") << true << true << 2; QTest::newRow("2_tempfiles_waitForStarted") << true << false << 2; } -void ProcessLauncherJobTest::startProcess() +void ApplicationLauncherJobTest::startProcess() { QFETCH(bool, tempFile); QFETCH(bool, useExec); QFETCH(int, numFiles); // Given a service desktop file and a number of source files const QString path = createTempService(); QTemporaryDir tempDir; const QString srcDir = tempDir.path(); QList urls; for (int i = 0; i < numFiles; ++i) { const QString srcFile = srcDir + "/srcfile" + QString::number(i + 1); createSrcFile(srcFile); QVERIFY(QFile::exists(srcFile)); urls.append(QUrl::fromLocalFile(srcFile)); } - // When running a ProcessLauncherJob + // When running a ApplicationLauncherJob KService::Ptr servicePtr(new KService(path)); - KIO::ProcessLauncherJob *job = new KIO::ProcessLauncherJob(servicePtr, this); + KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(servicePtr, this); job->setUrls(urls); if (tempFile) { - job->setRunFlags(KIO::ProcessLauncherJob::DeleteTemporaryFiles); + job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles); } if (useExec) { QVERIFY(job->exec()); } else { job->start(); QVERIFY(job->waitForStarted()); } const QVector pids = job->pids(); // Then the service should be executed (which copies the source file to "dest") QCOMPARE(pids.count(), numFiles); QVERIFY(!pids.contains(0)); for (int i = 0; i < numFiles; ++i) { const QString dest = srcDir + "/dest_srcfile" + QString::number(i + 1); QTRY_VERIFY2(QFile::exists(dest), qPrintable(dest)); QVERIFY(QFile::exists(srcDir + "/srcfile" + QString::number(i + 1))); // if tempfile is true, kioexec will delete it... in 3 minutes. QVERIFY(QFile::remove(dest)); // cleanup } #ifdef Q_OS_UNIX // Kill the running kioexec processes for (qint64 pid : pids) { ::kill(pid, SIGTERM); } #endif // The kioexec processes that are waiting for 3 minutes and got killed above, // will now trigger KProcessRunner::slotProcessError, KProcessRunner::slotProcessExited and delete the KProcessRunner. // We wait for that to happen otherwise it gets confusing to see that output from later tests. QTRY_COMPARE(KProcessRunner::instanceCount(), 0); } -void ProcessLauncherJobTest::shouldFailOnNonExecutableDesktopFile() +void ApplicationLauncherJobTest::shouldFailOnNonExecutableDesktopFile() { // Given a .desktop file in a temporary directory (outside the trusted paths) QTemporaryDir tempDir; const QString srcDir = tempDir.path(); const QString desktopFilePath = srcDir + "/shouldfail.desktop"; writeTempServiceDesktopFile(desktopFilePath); m_filesToRemove.append(desktopFilePath); const QString srcFile = srcDir + "/srcfile"; createSrcFile(srcFile); const QList urls{QUrl::fromLocalFile(srcFile)}; KService::Ptr servicePtr(new KService(desktopFilePath)); - KIO::ProcessLauncherJob *job = new KIO::ProcessLauncherJob(servicePtr, this); + KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(servicePtr, this); job->setUrls(urls); QVERIFY(!job->exec()); QCOMPARE(job->error(), KJob::UserDefinedError); QCOMPARE(job->errorString(), QStringLiteral("You are not authorized to execute this file.")); } -void ProcessLauncherJobTest::shouldFailOnNonExistingExecutable_data() +void ApplicationLauncherJobTest::shouldFailOnNonExistingExecutable_data() { QTest::addColumn("tempFile"); QTest::newRow("file") << false; QTest::newRow("tempFile") << true; } -void ProcessLauncherJobTest::shouldFailOnNonExistingExecutable() +void ApplicationLauncherJobTest::shouldFailOnNonExistingExecutable() { QFETCH(bool, tempFile); const QString desktopFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/non_existing_executable.desktop"); KDesktopFile file(desktopFilePath); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "KRunUnittestService"); group.writeEntry("Type", "Service"); group.writeEntry("Exec", "does_not_exist %f %d/dest_%n"); file.sync(); KService::Ptr servicePtr(new KService(desktopFilePath)); - KIO::ProcessLauncherJob *job = new KIO::ProcessLauncherJob(servicePtr, this); + KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(servicePtr, this); job->setUrls({QUrl::fromLocalFile(desktopFilePath)}); // just to have one URL as argument, as the desktop file expects if (tempFile) { - job->setRunFlags(KIO::ProcessLauncherJob::DeleteTemporaryFiles); + job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles); } QVERIFY(!job->exec()); QCOMPARE(job->error(), KJob::UserDefinedError); QCOMPARE(job->errorString(), QStringLiteral("Could not find the program 'does_not_exist'")); QFile::remove(desktopFilePath); } -void ProcessLauncherJobTest::writeTempServiceDesktopFile(const QString &filePath) +void ApplicationLauncherJobTest::writeTempServiceDesktopFile(const QString &filePath) { if (!QFile::exists(filePath)) { KDesktopFile file(filePath); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "KRunUnittestService"); group.writeEntry("Type", "Service"); #ifdef Q_OS_WIN group.writeEntry("Exec", "copy.exe %f %d/dest_%n"); #else group.writeEntry("Exec", "cd %d ; cp %f %d/dest_%n"); // cd is just to show that we can't do QFile::exists(binary) #endif file.sync(); } } -QString ProcessLauncherJobTest::createTempService() +QString ApplicationLauncherJobTest::createTempService() { const QString fileName = s_tempServiceName; const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + fileName; writeTempServiceDesktopFile(fakeService); m_filesToRemove.append(fakeService); return fakeService; } diff --git a/autotests/processlauncherjobtest.h b/autotests/applicationlauncherjobtest.h similarity index 89% rename from autotests/processlauncherjobtest.h rename to autotests/applicationlauncherjobtest.h index 314525e9..c1359f88 100644 --- a/autotests/processlauncherjobtest.h +++ b/autotests/applicationlauncherjobtest.h @@ -1,53 +1,53 @@ /* This file is part of the KDE libraries Copyright (c) 2014, 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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. */ -#ifndef PROCESSLAUNCHERJOBTEST_H -#define PROCESSLAUNCHERJOBTEST_H +#ifndef APPLICATIONLAUNCHERJOBTEST_H +#define APPLICATIONLAUNCHERJOBTEST_H #include #include -class ProcessLauncherJobTest : public QObject +class ApplicationLauncherJobTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void startProcess_data(); void startProcess(); void shouldFailOnNonExecutableDesktopFile(); void shouldFailOnNonExistingExecutable_data(); void shouldFailOnNonExistingExecutable(); private: QString createTempService(); void writeTempServiceDesktopFile(const QString &filePath); QStringList m_filesToRemove; }; -#endif /* PROCESSLAUNCHERJOBTEST_H */ +#endif /* APPLICATIONLAUNCHERJOBTEST_H */ diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 3221f748..6bb3bd75 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,72 +1,72 @@ configure_file(config-kiogui.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiogui.h) set(kiogui_SRCS faviconrequestjob.cpp - processlauncherjob.cpp + applicationlauncherjob.cpp kprocessrunner.cpp ) ecm_qt_declare_logging_category(kiogui_SRCS HEADER kiogui_debug.h IDENTIFIER KIO_GUI CATEGORY_NAME kf5.kio.gui DESCRIPTION "KIOGui (KIO)" EXPORT KIO ) ecm_qt_declare_logging_category(kiogui_SRCS HEADER favicons_debug.h IDENTIFIER FAVICONS_LOG CATEGORY_NAME kf5.kio.favicons DESCRIPTION "FavIcons (KIO)" EXPORT KIO ) add_library(KF5KIOGui ${kiogui_SRCS}) generate_export_header(KF5KIOGui BASE_NAME KIOGui) add_library(KF5::KIOGui ALIAS KF5KIOGui) target_include_directories(KF5KIOGui INTERFACE "$") target_link_libraries(KF5KIOGui PUBLIC KF5::KIOCore KF5::WindowSystem Qt5::Gui PRIVATE KF5::I18n ) set_target_properties(KF5KIOGui PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOGui ) # Headers prefixed with KIO/ ecm_generate_headers(KIOGui_CamelCase_HEADERS HEADER_NAMES FavIconRequestJob - ProcessLauncherJob + ApplicationLauncherJob PREFIX KIO REQUIRED_HEADERS KIO_namespaced_gui_HEADERS ) install(FILES ${KIOGui_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOGui/KIO COMPONENT Devel) install(TARGETS KF5KIOGui EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KIO_namespaced_gui_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOGui/kio COMPONENT Devel) install(FILES ${KIOGui_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiogui_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOGui COMPONENT Devel) # make available to ecm_add_qch in parent folder set(KIOGui_QCH_SOURCES ${KIOGui_HEADERS} ${KIO_namespaced_gui_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOGui LIB_NAME KF5KIOGui DEPS "KIOCore" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOGui) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/gui/processlauncherjob.cpp b/src/gui/applicationlauncherjob.cpp similarity index 79% rename from src/gui/processlauncherjob.cpp rename to src/gui/applicationlauncherjob.cpp index e121e132..cec893a2 100644 --- a/src/gui/processlauncherjob.cpp +++ b/src/gui/applicationlauncherjob.cpp @@ -1,133 +1,133 @@ /* This file is part of the KDE libraries Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 "processlauncherjob.h" +#include "applicationlauncherjob.h" #include "kprocessrunner_p.h" #include "kiogui_debug.h" -class KIO::ProcessLauncherJobPrivate +class KIO::ApplicationLauncherJobPrivate { public: - explicit ProcessLauncherJobPrivate(const KService::Ptr &service) + explicit ApplicationLauncherJobPrivate(const KService::Ptr &service) : m_service(service) {} - void slotStarted(KIO::ProcessLauncherJob *q, KProcessRunner *processRunner) { + void slotStarted(KIO::ApplicationLauncherJob *q, KProcessRunner *processRunner) { m_pids.append(processRunner->pid()); if (--m_numProcessesPending == 0) { q->emitResult(); } } const KService::Ptr m_service; QList m_urls; - KIO::ProcessLauncherJob::RunFlags m_runFlags; + KIO::ApplicationLauncherJob::RunFlags m_runFlags; QString m_suggestedFileName; QByteArray m_startupId; QVector m_pids; QVector m_processRunners; int m_numProcessesPending = 0; }; -KIO::ProcessLauncherJob::ProcessLauncherJob(const KService::Ptr &service, QObject *parent) - : KJob(parent), d(new ProcessLauncherJobPrivate(service)) +KIO::ApplicationLauncherJob::ApplicationLauncherJob(const KService::Ptr &service, QObject *parent) + : KJob(parent), d(new ApplicationLauncherJobPrivate(service)) { } -KIO::ProcessLauncherJob::~ProcessLauncherJob() +KIO::ApplicationLauncherJob::~ApplicationLauncherJob() { // Do *NOT* delete the KProcessRunner instances here. // We need it to keep running so it can do terminate startup notification on process exit. } -void KIO::ProcessLauncherJob::setUrls(const QList &urls) +void KIO::ApplicationLauncherJob::setUrls(const QList &urls) { d->m_urls = urls; } -void KIO::ProcessLauncherJob::setRunFlags(RunFlags runFlags) +void KIO::ApplicationLauncherJob::setRunFlags(RunFlags runFlags) { d->m_runFlags = runFlags; } -void KIO::ProcessLauncherJob::setSuggestedFileName(const QString &suggestedFileName) +void KIO::ApplicationLauncherJob::setSuggestedFileName(const QString &suggestedFileName) { d->m_suggestedFileName = suggestedFileName; } -void KIO::ProcessLauncherJob::setStartupId(const QByteArray &startupId) +void KIO::ApplicationLauncherJob::setStartupId(const QByteArray &startupId) { d->m_startupId = startupId; } -void KIO::ProcessLauncherJob::start() +void KIO::ApplicationLauncherJob::start() { if (d->m_urls.count() > 1 && !d->m_service->allowMultipleFiles()) { // We need to launch the application N times. // 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. d->m_numProcessesPending = d->m_urls.count(); d->m_processRunners.reserve(d->m_numProcessesPending); for (int i = 1; i < d->m_urls.count(); ++i) { auto *processRunner = new KProcessRunner(d->m_service, { d->m_urls.at(i) }, d->m_runFlags, d->m_suggestedFileName, QByteArray()); d->m_processRunners.push_back(processRunner); connect(processRunner, &KProcessRunner::processStarted, this, [this, processRunner]() { d->slotStarted(this, processRunner); }); } d->m_urls = { d->m_urls.at(0) }; } else { d->m_numProcessesPending = 1; } auto *processRunner = new KProcessRunner(d->m_service, d->m_urls, d->m_runFlags, d->m_suggestedFileName, d->m_startupId); d->m_processRunners.push_back(processRunner); connect(processRunner, &KProcessRunner::error, this, [this](const QString &errorText) { setError(KJob::UserDefinedError); setErrorText(errorText); emitResult(); }); connect(processRunner, &KProcessRunner::processStarted, this, [this, processRunner]() { d->slotStarted(this, processRunner); }); } -bool KIO::ProcessLauncherJob::waitForStarted() +bool KIO::ApplicationLauncherJob::waitForStarted() { const bool ret = std::all_of(d->m_processRunners.cbegin(), d->m_processRunners.cend(), [](KProcessRunner *r) { return r->waitForStarted(); }); for (KProcessRunner *r : qAsConst(d->m_processRunners)) { qApp->sendPostedEvents(r); // so slotStarted gets called } return ret; } -qint64 KIO::ProcessLauncherJob::pid() const +qint64 KIO::ApplicationLauncherJob::pid() const { return d->m_pids.at(0); } -QVector KIO::ProcessLauncherJob::pids() const +QVector KIO::ApplicationLauncherJob::pids() const { return d->m_pids; } diff --git a/src/gui/processlauncherjob.h b/src/gui/applicationlauncherjob.h similarity index 87% rename from src/gui/processlauncherjob.h rename to src/gui/applicationlauncherjob.h index 41cd32d3..97b4af55 100644 --- a/src/gui/processlauncherjob.h +++ b/src/gui/applicationlauncherjob.h @@ -1,136 +1,136 @@ /* This file is part of the KDE libraries Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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. */ -#ifndef KIO_PROCESSLAUNCHERJOB_H -#define KIO_PROCESSLAUNCHERJOB_H +#ifndef KIO_APPLICATIONLAUNCHERJOB_H +#define KIO_APPLICATIONLAUNCHERJOB_H #include "kiogui_export.h" #include #include #include namespace KIO { -class ProcessLauncherJobPrivate; +class ApplicationLauncherJobPrivate; /** - * @brief ProcessLauncherJob runs a process (application) and watches it while running. + * @brief ApplicationLauncherJob runs an application and watches it while running. * * It creates a startup notification and finishes it on success or on error (for the taskbar). * It also emits an error message if necessary (e.g. "program not found"). * * Note that this class doesn't support warning the user if a desktop file or a binary * does not have the executable bit set and offering to make it so. Therefore file managers - * should use KRun::runApplication rather than using ProcessLauncherJob directly. + * should use KRun::runApplication rather than using ApplicationLauncherJob directly. * * When passing multiple URLs to an application that doesn't support opening * multiple files, the application will be launched once for each URL. * * The job finishes when the application is successfully started. At that point you can * query the PID(s). * * @since 5.69 */ -class KIOGUI_EXPORT ProcessLauncherJob : public KJob +class KIOGUI_EXPORT ApplicationLauncherJob : public KJob { public: /** - * @brief Creates a ProcessLauncherJob + * @brief Creates a ApplicationLauncherJob * @param service the service (application desktop file) to run * @param parent the parent QObject */ - explicit ProcessLauncherJob(const KService::Ptr &service, QObject *parent = nullptr); + explicit ApplicationLauncherJob(const KService::Ptr &service, QObject *parent = nullptr); /** * Destructor. * Note that jobs auto-delete themselves after emitting result. * Deleting/killing the job will not stop the started application. */ - ~ProcessLauncherJob() override; + ~ApplicationLauncherJob() override; /** * @brief setUrls specifies the URLs to be passed to the application * @param urls list of files (local or remote) to open * * Note that when passing multiple URLs to an application that doesn't support opening * multiple files, the application will be launched once for each URL. */ void setUrls(const QList &urls); enum RunFlag { DeleteTemporaryFiles = 0x1, ///< the URLs passed to the service will be deleted when it exits (if the URLs are local files) }; Q_DECLARE_FLAGS(RunFlags, RunFlag) /** * @brief setRunFlags specifies various flags * @param runFlags the flags to be set. For instance, whether the URLs are temporary files that should be deleted after execution. */ void setRunFlags(RunFlags runFlags); /** * Sets the file name to use in the case of downloading the file to a tempfile * in order to give to a non-url-aware application. Some apps rely on the extension * to determine the mimetype of the file. Usually the file name comes from the URL, * but in the case of the HTTP Content-Disposition header, we need to override the * file name. * @param suggestedFileName the file name */ void setSuggestedFileName(const QString &suggestedFileName); /** * @brief setStartupId sets the startupId of the new application * @param startupId Application startup notification id, if any (otherwise ""). */ void setStartupId(const QByteArray &startupId); /** * @brief start starts the job. You must call this, after all the setters. */ void start() override; /** * Blocks until the process has started. */ bool waitForStarted(); /** * @return the PID of the application that was started. * Convenience method for pids().at(0). You should only use this when specifying zero or one URL, * or when you are sure that the application supports opening multiple files. Otherwise use pids(). * Available after the job emits result(). */ qint64 pid() const; /** * @return the PIDs of the applications that were started. * Available after the job emits result(). */ QVector pids() const; private: - friend class ProcessLauncherJobPrivate; - QScopedPointer d; + friend class ApplicationLauncherJobPrivate; + QScopedPointer d; }; } // namespace KIO #endif diff --git a/src/gui/kprocessrunner.cpp b/src/gui/kprocessrunner.cpp index 6fcd7e89..8a613b04 100644 --- a/src/gui/kprocessrunner.cpp +++ b/src/gui/kprocessrunner.cpp @@ -1,301 +1,301 @@ /* This file is part of the KDE libraries Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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 "kprocessrunner_p.h" #include "kiogui_debug.h" #include "config-kiogui.h" #include "desktopexecparser.h" #include "krecentdocument.h" #include #include #include #include #include #include #include #include #include static int s_instanceCount = 0; // for the unittest KProcessRunner::KProcessRunner(const KService::Ptr &service, const QList &urls, - KIO::ProcessLauncherJob::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) + KIO::ApplicationLauncherJob::RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) : m_process{new KProcess}, m_executable(KIO::DesktopExecParser::executablePath(service->exec())) { ++s_instanceCount; KIO::DesktopExecParser execParser(*service, urls); const QString realExecutable = execParser.resultingArguments().at(0); if (!QFileInfo::exists(realExecutable) && QStandardPaths::findExecutable(realExecutable).isEmpty()) { emitDelayedError(i18n("Could not find the program '%1'", realExecutable)); return; } - execParser.setUrlsAreTempFiles(flags & KIO::ProcessLauncherJob::DeleteTemporaryFiles); + execParser.setUrlsAreTempFiles(flags & KIO::ApplicationLauncherJob::DeleteTemporaryFiles); execParser.setSuggestedFileName(suggestedFileName); const QStringList args = execParser.resultingArguments(); if (args.isEmpty()) { emitDelayedError(i18n("Error processing Exec field in %1", service->entryPath())); return; } //qDebug() << "KProcess args=" << args; *m_process << args; enum DiscreteGpuCheck { NotChecked, Present, Absent }; static DiscreteGpuCheck s_gpuCheck = NotChecked; if (service->runOnDiscreteGpu() && s_gpuCheck == NotChecked) { // Check whether we have a discrete gpu bool hasDiscreteGpu = false; QDBusInterface iface(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement"), QStringLiteral("org.kde.Solid.PowerManagement"), QDBusConnection::sessionBus()); if (iface.isValid()) { QDBusReply reply = iface.call(QStringLiteral("hasDualGpu")); if (reply.isValid()) { hasDiscreteGpu = reply.value(); } } s_gpuCheck = hasDiscreteGpu ? Present : Absent; } if (service->runOnDiscreteGpu() && s_gpuCheck == Present) { m_process->setEnv(QStringLiteral("DRI_PRIME"), QStringLiteral("1")); } QString workingDir(service->workingDirectory()); if (workingDir.isEmpty() && !urls.isEmpty() && urls.first().isLocalFile()) { workingDir = urls.first().adjusted(QUrl::RemoveFilename).toLocalFile(); } m_process->setWorkingDirectory(workingDir); - if ((flags & KIO::ProcessLauncherJob::DeleteTemporaryFiles) == 0) { + if ((flags & KIO::ApplicationLauncherJob::DeleteTemporaryFiles) == 0) { // Remember we opened those urls, for the "recent documents" menu in kicker for (const QUrl &url : urls) { KRecentDocument::add(url, service->desktopEntryName()); } } // m_executable can be a full shell command, so here is not 100% reliable. // E.g. it could be "cd", which isn't an existing binary. It's just a heuristic anyway. const QString bin = KIO::DesktopExecParser::executableName(m_executable); init(service, bin, service->name(), service->icon(), asn); } KProcessRunner::KProcessRunner(const QString &cmd, const QString &execName, const QString &iconName, const QByteArray &asn, const QString &workingDirectory) : m_process{new KProcess}, m_executable(execName) { ++s_instanceCount; m_process->setShellCommand(cmd); if (!workingDirectory.isEmpty()) { m_process->setWorkingDirectory(workingDirectory); } QString bin = KIO::DesktopExecParser::executableName(m_executable); KService::Ptr service = KService::serviceByDesktopName(bin); init(service, bin, execName /*user-visible name*/, iconName, asn); } void KProcessRunner::init(const KService::Ptr &service, const QString &bin, const QString &userVisibleName, const QString &iconName, const QByteArray &asn) { if (service && !service->entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath())) { qCWarning(KIO_GUI) << "No authorization to execute " << service->entryPath(); emitDelayedError(i18n("You are not authorized to execute this file.")); return; } #if HAVE_X11 static bool isX11 = QGuiApplication::platformName() == QLatin1String("xcb"); if (isX11) { bool silent; QByteArray wmclass; const bool startup_notify = (asn != "0" && KIOGuiPrivate::checkStartupNotify(service.data(), &silent, &wmclass)); if (startup_notify) { m_startupId.initId(asn); m_startupId.setupStartupEnv(); KStartupInfoData data; data.setHostname(); data.setBin(bin); if (!userVisibleName.isEmpty()) { data.setName(userVisibleName); } else if (service && !service->name().isEmpty()) { data.setName(service->name()); } data.setDescription(i18n("Launching %1", 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(KStartupInfoData::Yes); } data.setDesktop(KWindowSystem::currentDesktop()); if (service && !service->entryPath().isEmpty()) { data.setApplicationId(service->entryPath()); } KStartupInfo::sendStartup(m_startupId, data); } } #else Q_UNUSED(bin); Q_UNUSED(userVisibleName); Q_UNUSED(iconName); #endif startProcess(); } void KProcessRunner::startProcess() { connect(m_process.get(), QOverload::of(&QProcess::finished), this, &KProcessRunner::slotProcessExited); connect(m_process.get(), &QProcess::started, this, &KProcessRunner::slotProcessStarted, Qt::QueuedConnection); connect(m_process.get(), &QProcess::errorOccurred, this, &KProcessRunner::slotProcessError); m_process->start(); } bool KProcessRunner::waitForStarted() { return m_process->waitForStarted(); } void KProcessRunner::slotProcessError(QProcess::ProcessError errorCode) { // E.g. the process crashed. - // This is unlikely to happen while the ProcessLauncherJob is still connected to the KProcessRunner. + // This is unlikely to happen while the ApplicationLauncherJob is still connected to the KProcessRunner. // So the emit does nothing, this is really just for debugging. qCDebug(KIO_GUI) << m_executable << "error=" << errorCode << m_process->errorString(); Q_EMIT error(m_process->errorString()); } void KProcessRunner::slotProcessStarted() { m_pid = m_process->processId(); #if HAVE_X11 if (!m_startupId.isNull() && m_pid) { KStartupInfoData data; data.addPid(m_pid); KStartupInfo::sendChange(m_startupId, data); KStartupInfo::resetStartupEnv(); } #endif emit processStarted(); } KProcessRunner::~KProcessRunner() { // This destructor deletes m_process, since it's a unique_ptr. --s_instanceCount; } int KProcessRunner::instanceCount() { return s_instanceCount; } qint64 KProcessRunner::pid() const { return m_pid; } void KProcessRunner::terminateStartupNotification() { #if HAVE_X11 if (!m_startupId.isNull()) { KStartupInfoData data; data.addPid(m_pid); // announce this pid for the startup notification has finished data.setHostname(); KStartupInfo::sendFinish(m_startupId, data); } #endif } void KProcessRunner::emitDelayedError(const QString &errorMsg) { terminateStartupNotification(); // Use delayed invocation so the caller has time to connect to the signal QMetaObject::invokeMethod(this, [this, errorMsg]() { emit error(errorMsg); deleteLater(); }, Qt::QueuedConnection); } void KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KIO_GUI) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus; terminateStartupNotification(); deleteLater(); } // This code is also used in klauncher (and KRun). bool KIOGuiPrivate::checkStartupNotify(const KService *service, bool *silent_arg, QByteArray *wmclass_arg) { bool silent = false; QByteArray wmclass; if (service && service->property(QStringLiteral("StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("StartupWMClass")).toString().toLatin1(); } else if (service && service->property(QStringLiteral("X-KDE-StartupNotify")).isValid()) { silent = !service->property(QStringLiteral("X-KDE-StartupNotify")).toBool(); wmclass = service->property(QStringLiteral("X-KDE-WMClass")).toString().toLatin1(); } else { // non-compliant app if (service) { if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant wmclass = "0"; // krazy:exclude=doublequote_chars } 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) { *silent_arg = silent; } if (wmclass_arg) { *wmclass_arg = wmclass; } return true; } diff --git a/src/gui/kprocessrunner_p.h b/src/gui/kprocessrunner_p.h index f1e639c7..6fcf1ece 100644 --- a/src/gui/kprocessrunner_p.h +++ b/src/gui/kprocessrunner_p.h @@ -1,121 +1,121 @@ /* This file is part of the KDE libraries Copyright (c) 2020 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), 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 Lesser 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. */ #ifndef KPROCESSRUNNER_P_H #define KPROCESSRUNNER_P_H -#include "processlauncherjob.h" +#include "applicationlauncherjob.h" #include "kiogui_export.h" #include #include #include #include namespace KIOGuiPrivate { /** * @internal DO NOT USE */ bool KIOGUI_EXPORT checkStartupNotify(const KService *service, bool *silent_arg, QByteArray *wmclass_arg); } /** * @internal (exported for KRun, currently) * This class runs a KService or a shell command, using QProcess internally. * It creates a startup notification and finishes it on success or on error (for the taskbar) * It also shows an error message if necessary (e.g. "program not found"). */ class KIOGUI_EXPORT KProcessRunner : public QObject { Q_OBJECT public: /** * Run a KService (application desktop file) to open @p urls. * @param service the service to run * @param urls the list of URLs, can be empty * @param flags various flags * @param suggestedFileName see KRun::setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). */ KProcessRunner(const KService::Ptr &service, const QList &urls, - KIO::ProcessLauncherJob::RunFlags flags = {}, const QString &suggestedFileName = {}, const QByteArray &asn = {}); + KIO::ApplicationLauncherJob::RunFlags flags = {}, const QString &suggestedFileName = {}, const QByteArray &asn = {}); /** * Run a shell command * @param cmd must be a shell command. No need to append "&" to it. * @param execName the name of the executable, if known. This improves startup notification, * as well as honoring various flags coming from the desktop file for this executable, if there's one. * @param iconName icon for the startup notification * @param asn Application startup notification id, if any (otherwise ""). * @param workingDirectory the working directory for the started process. The default * (if passing an empty string) is the user's document path. * This allows a command like "kwrite file.txt" to find file.txt from the right place. */ KProcessRunner(const QString &cmd, const QString &execName, const QString &iconName, const QByteArray &asn = {}, const QString &workingDirectory = {}); /** * @return the PID of the process that was started, on success */ qint64 pid() const; bool waitForStarted(); virtual ~KProcessRunner(); static int instanceCount(); // for the unittest Q_SIGNALS: /** * @brief Emitted on error. In that case, finished() is not emitted. * @param errorString the error message */ void error(const QString &errorString); /** * @brief emitted when the process was successfully started */ void processStarted(); private Q_SLOTS: void slotProcessExited(int, QProcess::ExitStatus); void slotProcessError(QProcess::ProcessError error); void slotProcessStarted(); private: void init(const KService::Ptr &service, const QString &bin, const QString &userVisibleName, const QString &iconName, const QByteArray &asn); void startProcess(); void terminateStartupNotification(); void emitDelayedError(const QString &errorMsg); std::unique_ptr m_process; const QString m_executable; // can be a full path KStartupInfoId m_startupId; qint64 m_pid = 0; Q_DISABLE_COPY(KProcessRunner) }; #endif diff --git a/src/widgets/krun.cpp b/src/widgets/krun.cpp index a3cfdac7..1a573c60 100644 --- a/src/widgets/krun.cpp +++ b/src/widgets/krun.cpp @@ -1,1446 +1,1446 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure Copyright (C) 2009 Michael Pyne 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 "krun_p.h" #include // HAVE_X11 #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/job.h" #include "kio/global.h" #include "kio/scheduler.h" #include "kopenwithdialog.h" #include "krecentdocument.h" #include "kdesktopfileactions.h" #include #include "kprocessrunner_p.h" -#include "processlauncherjob.h" +#include "applicationlauncherjob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_X11 #include #endif #include KRun::KRunPrivate::KRunPrivate(KRun *parent) : q(parent), m_showingDialog(false) { } void KRun::KRunPrivate::startTimer() { m_timer->start(0); } // --------------------------------------------------------------------------- static KService::Ptr schemeService(const QString &protocol) { return KMimeTypeTrader::self()->preferredService(QLatin1String("x-scheme-handler/") + protocol); } static bool checkNeedPortalSupport() { return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, QLatin1String("flatpak-info")).isEmpty() || qEnvironmentVariableIsSet("SNAP"); } static qint64 runProcessRunner(KProcessRunner *processRunner, QWidget *widget) { QObject *receiver = widget ? static_cast(widget) : static_cast(qApp); QObject::connect(processRunner, &KProcessRunner::error, receiver, [widget](const QString &errorString) { QEventLoopLocker locker; KMessageBox::sorry(widget, errorString); }); processRunner->waitForStarted(); return processRunner->pid(); } -static qint64 runProcessLauncherJob(KIO::ProcessLauncherJob *job, QWidget *widget) +static qint64 runApplicationLauncherJob(KIO::ApplicationLauncherJob *job, QWidget *widget) { QObject *receiver = widget ? static_cast(widget) : static_cast(qApp); QObject::connect(job, &KJob::result, receiver, [widget](KJob *job) { if (job->error()) { QEventLoopLocker locker; KMessageBox::sorry(widget, job->errorString()); } }); job->start(); job->waitForStarted(); return job->pid(); } // --------------------------------------------------------------------------- // Helper function that returns whether a file has the execute bit set or not. static bool hasExecuteBit(const QString &fileName) { QFileInfo file(fileName); return file.isExecutable(); } bool KRun::isExecutableFile(const QUrl &url, const QString &mimetype) { if (!url.isLocalFile()) { return false; } // While isExecutable performs similar check to this one, some users depend on // this method not returning true for application/x-desktop QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimetype); if (!mimeType.inherits(QStringLiteral("application/x-executable")) && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable")) && !mimeType.inherits(QStringLiteral("application/x-executable-script")) && !mimeType.inherits(QStringLiteral("application/x-sharedlib"))) { return false; } if (!hasExecuteBit(url.toLocalFile()) && !mimeType.inherits(QStringLiteral("application/x-ms-dos-executable"))) { return false; } return true; } void KRun::handleInitError(int kioErrorCode, const QString &errorMsg) { Q_UNUSED(kioErrorCode); d->m_showingDialog = true; KMessageBox::error(d->m_window, errorMsg); d->m_showingDialog = false; } void KRun::handleError(KJob *job) { Q_ASSERT(job); if (job) { d->m_showingDialog = true; job->uiDelegate()->showErrorMessage(); d->m_showingDialog = false; } } // Simple QDialog that resizes the given text edit after being shown to more // or less fit the enclosed text. class SecureMessageDialog : public QDialog { Q_OBJECT public: SecureMessageDialog(QWidget *parent) : QDialog(parent), m_textEdit(nullptr) { } void setTextEdit(QPlainTextEdit *textEdit) { m_textEdit = textEdit; } protected: void showEvent(QShowEvent *e) override { if (e->spontaneous()) { return; } // Now that we're shown, use our width to calculate a good // bounding box for the text, and resize m_textEdit appropriately. QDialog::showEvent(e); if (!m_textEdit) { return; } QSize fudge(20, 24); // About what it sounds like :-/ // Form rect with a lot of height for bounding. Use no more than // 5 lines. QRect curRect(m_textEdit->rect()); QFontMetrics metrics(fontMetrics()); curRect.setHeight(5 * metrics.lineSpacing()); curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok? QString text(m_textEdit->toPlainText()); curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text); // Scroll bars interfere. If we don't think there's enough room, enable // the vertical scrollbar however. m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); if (curRect.height() < m_textEdit->height()) { // then we've got room m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_textEdit->setMaximumHeight(curRect.height() + fudge.height()); } m_textEdit->setMinimumSize(curRect.size() + fudge); m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); } private: QPlainTextEdit *m_textEdit; }; // Shows confirmation dialog whether an untrusted program should be run // or not, since it may be potentially dangerous. static int showUntrustedProgramWarning(const QString &programName, QWidget *window) { SecureMessageDialog *baseDialog = new SecureMessageDialog(window); baseDialog->setWindowTitle(i18nc("Warning about executing unknown program", "Warning")); QVBoxLayout *topLayout = new QVBoxLayout; baseDialog->setLayout(topLayout); // Dialog will have explanatory text with a disabled lineedit with the // Exec= to make it visually distinct. QWidget *baseWidget = new QWidget(baseDialog); QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget); QLabel *iconLabel = new QLabel(baseWidget); QPixmap warningIcon(KIconLoader::global()->loadIcon(QStringLiteral("dialog-warning"), KIconLoader::NoGroup, KIconLoader::SizeHuge)); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(warningIcon); QVBoxLayout *contentLayout = new QVBoxLayout; QString warningMessage = i18nc("program name follows in a line edit below", "This will start the program:"); QLabel *message = new QLabel(warningMessage, baseWidget); contentLayout->addWidget(message); QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget); textEdit->setPlainText(programName); textEdit->setReadOnly(true); contentLayout->addWidget(textEdit); QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel")); contentLayout->addWidget(footerLabel); contentLayout->addStretch(0); // Don't allow the text edit to expand mainLayout->addLayout(contentLayout); topLayout->addWidget(baseWidget); baseDialog->setTextEdit(textEdit); QDialogButtonBox *buttonBox = new QDialogButtonBox(baseDialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::cont()); buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true); buttonBox->button(QDialogButtonBox::Cancel)->setFocus(); QObject::connect(buttonBox, &QDialogButtonBox::accepted, baseDialog, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, baseDialog, &QDialog::reject); topLayout->addWidget(buttonBox); // Constrain maximum size. Minimum size set in // the dialog's show event. #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) const QSize screenSize = QApplication::screens().at(0)->size(); #else const QSize screenSize = baseDialog->screen()->size(); #endif baseDialog->resize(screenSize.width() / 4, 50); baseDialog->setMaximumHeight(screenSize.height() / 3); baseDialog->setMaximumWidth(screenSize.width() / 10 * 8); return baseDialog->exec(); } // Helper function that attempts to set execute bit for given file. static bool setExecuteBit(const QString &fileName, QString &errorString) { QFile file(fileName); // corresponds to owner on unix, which will have to do since if the user // isn't the owner we can't change perms anyways. if (!file.setPermissions(QFile::ExeUser | file.permissions())) { errorString = file.errorString(); qCWarning(KIO_WIDGETS) << "Unable to change permissions for" << fileName << errorString; return false; } return true; } bool KRun::runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile, bool runExecutables, const QString &suggestedFileName, const QByteArray &asn) { RunFlags flags = tempFile ? KRun::DeleteTemporaryFiles : RunFlags(); if (runExecutables) { flags |= KRun::RunExecutables; } return runUrl(url, mimetype, window, flags, suggestedFileName, asn); } // This is called by foundMimeType, since it knows the mimetype of the URL bool KRun::runUrl(const QUrl &u, const QString &_mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { const QMimeDatabase db; const bool runExecutables = flags.testFlag(KRun::RunExecutables); const bool tempFile = flags.testFlag(KRun::DeleteTemporaryFiles); bool noRun = false; bool noAuth = false; if (_mimetype == QLatin1String("inode/directory-locked")) { KMessageBox::error(window, i18n("Unable to enter %1.\nYou do not have access rights to this location.", u.toDisplayString().toHtmlEscaped())); return false; } else if (_mimetype == QLatin1String("application/x-desktop")) { if (u.isLocalFile() && runExecutables) { return KDesktopFileActions::runWithStartup(u, true, asn); } } else if (isExecutable(_mimetype)) { // Check whether file is executable script const QMimeType mime = db.mimeTypeForName(_mimetype); #ifdef Q_OS_WIN bool isNativeBinary = !mime.inherits(QStringLiteral("text/plain")); #else bool isNativeBinary = !mime.inherits(QStringLiteral("text/plain")) && !mime.inherits(QStringLiteral("application/x-ms-dos-executable")); #endif // Only run local files if (u.isLocalFile() && runExecutables) { if (KAuthorized::authorize(QStringLiteral("shell_access"))) { bool canRun = true; bool isFileExecutable = hasExecuteBit(u.toLocalFile()); // For executables that aren't scripts and without execute bit, // show prompt asking user if he wants to run the program. if (!isFileExecutable && isNativeBinary) { canRun = false; int result = showUntrustedProgramWarning(u.fileName(), window); if (result == QDialog::Accepted) { QString errorString; if (!setExecuteBit(u.toLocalFile(), errorString)) { KMessageBox::sorry( window, i18n("Unable to make file %1 executable.\n%2.", u.toLocalFile(), errorString) ); } else { canRun = true; } } } else if (!isFileExecutable && !isNativeBinary) { // Don't try to run scripts/exes without execute bit, instead // open them with default application canRun = false; } if (canRun) { return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.adjusted(QUrl::RemoveFilename).toLocalFile())); // just execute the url as a command // ## TODO implement deleting the file if tempFile==true } } else { // Show no permission warning noAuth = true; } } else if (isNativeBinary) { // Show warning for executables that aren't scripts noRun = true; } } if (noRun) { KMessageBox::sorry(window, i18n("The file %1 is an executable program. " "For safety it will not be started.", u.toDisplayString().toHtmlEscaped())); return false; } if (noAuth) { KMessageBox::error(window, i18n("You do not have permission to run %1.", u.toDisplayString().toHtmlEscaped())); return false; } QList lst; lst.append(u); KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype); if (!offer) { #ifdef Q_OS_WIN // As KDE on windows doesn't know about the windows default applications offers will be empty in nearly all cases. // So we use QDesktopServices::openUrl to let windows decide how to open the file return QDesktopServices::openUrl(u); #else // Open-with dialog // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog ! // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list... return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn); #endif } return KRun::runApplication(*offer, lst, window, flags, suggestedFileName, asn); } bool KRun::displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { if (!KAuthorized::authorizeAction(QStringLiteral("openwith"))) { KMessageBox::sorry(window, i18n("You are not authorized to select an application to open this file.")); return false; } #ifdef Q_OS_WIN KConfigGroup cfgGroup(KSharedConfig::openConfig(), QStringLiteral("KOpenWithDialog Settings")); if (cfgGroup.readEntry("Native", true)) { return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles, suggestedFileName, asn); } #endif KOpenWithDialog dialog(lst, QString(), QString(), window); dialog.setWindowModality(Qt::WindowModal); if (dialog.exec()) { KService::Ptr service = dialog.service(); if (!service) { //qDebug() << "No service set, running " << dialog.text(); service = KService::Ptr(new KService(QString() /*name*/, dialog.text(), QString() /*icon*/)); } const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags(); return KRun::runApplication(*service, lst, window, flags, suggestedFileName, asn); } return false; } void KRun::shellQuote(QString &_str) { // Credits to Walter, says Bernd G. :) if (_str.isEmpty()) { // Don't create an explicit empty parameter return; } const QChar q = QLatin1Char('\''); _str.replace(q, QLatin1String("'\\''")).prepend(q).append(q); } QStringList KRun::processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles, const QString &suggestedFileName) { KIO::DesktopExecParser parser(_service, _urls); parser.setUrlsAreTempFiles(tempFiles); parser.setSuggestedFileName(suggestedFileName); return parser.resultingArguments(); } QString KRun::binaryName(const QString &execLine, bool removePath) { return removePath ? KIO::DesktopExecParser::executableName(execLine) : KIO::DesktopExecParser::executablePath(execLine); } // This code is also used in klauncher. // TODO: port klauncher to KIOGuiPrivate::checkStartupNotify once this lands // TODO: then deprecate this method, and remove in KF6 bool KRun::checkStartupNotify(const QString & /*binName*/, const KService *service, bool *silent_arg, QByteArray *wmclass_arg) { return KIOGuiPrivate::checkStartupNotify(service, silent_arg, wmclass_arg); } // WARNING: don't call this from DesktopExecParser, since klauncher uses that too... // TODO: make this async, see the job->exec() in there... static QList resolveURLs(const QList &_urls, const KService &_service) { // Check which protocols the application supports. // This can be a list of actual protocol names, or just KIO for KDE apps. const QStringList appSupportedProtocols = KIO::DesktopExecParser::supportedProtocols(_service); QList urls(_urls); if (!appSupportedProtocols.contains(QLatin1String("KIO"))) { for (QUrl &url : urls) { bool supported = KIO::DesktopExecParser::isProtocolInSupportedList(url, appSupportedProtocols); //qDebug() << "Looking at url=" << url << " supported=" << supported; if (!supported && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { // Maybe we can resolve to a local URL? KIO::StatJob *job = KIO::mostLocalUrl(url); if (job->exec()) { // ## nasty nested event loop! const QUrl localURL = job->mostLocalUrl(); if (localURL != url) { url = localURL; //qDebug() << "Changed to" << localURL; } } } } } return urls; } // Helper function to make the given .desktop file executable by ensuring // that a #!/usr/bin/env xdg-open line is added if necessary and the file has // the +x bit set for the user. Returns false if either fails. static bool makeServiceFileExecutable(const QString &fileName, QString &errorString) { // Open the file and read the first two characters, check if it's // #!. If not, create a new file, prepend appropriate lines, and copy // over. QFile desktopFile(fileName); if (!desktopFile.open(QFile::ReadOnly)) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error opening service" << fileName << errorString; return false; } QByteArray header = desktopFile.peek(2); // First two chars of file if (header.size() == 0) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error inspecting service" << fileName << errorString; return false; // Some kind of error } if (header != "#!") { // Add header QSaveFile saveFile; saveFile.setFileName(fileName); if (!saveFile.open(QIODevice::WriteOnly)) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Unable to open replacement file for" << fileName << errorString; return false; } QByteArray shebang("#!/usr/bin/env xdg-open\n"); if (saveFile.write(shebang) != shebang.size()) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error occurred adding header for" << fileName << errorString; saveFile.cancelWriting(); return false; } // Now copy the one into the other and then close and reopen desktopFile QByteArray desktopData(desktopFile.readAll()); if (desktopData.isEmpty()) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Unable to read service" << fileName << errorString; saveFile.cancelWriting(); return false; } if (saveFile.write(desktopData) != desktopData.size()) { errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error copying service" << fileName << errorString; saveFile.cancelWriting(); return false; } desktopFile.close(); if (!saveFile.commit()) { // Figures.... errorString = saveFile.errorString(); qCWarning(KIO_WIDGETS) << "Error committing changes to service" << fileName << errorString; return false; } if (!desktopFile.open(QFile::ReadOnly)) { errorString = desktopFile.errorString(); qCWarning(KIO_WIDGETS) << "Error re-opening service" << fileName << errorString; return false; } } // Add header return setExecuteBit(fileName, errorString); } // Helper function to make a .desktop file executable if prompted by the user. // returns true if KRun::run() should continue with execution, false if user declined // to make the file executable or we failed to make it executable. static bool makeServiceExecutable(const KService &service, QWidget *window) { if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { qCWarning(KIO_WIDGETS) << "No authorization to execute " << service.entryPath(); KMessageBox::sorry(window, i18n("You are not authorized to execute this service.")); return false; // Don't circumvent the Kiosk } // We can use KStandardDirs::findExe to resolve relative pathnames // but that gets rid of the command line arguments. QString program = QFileInfo(service.exec()).canonicalFilePath(); if (program.isEmpty()) { // e.g. due to command line arguments program = service.exec(); } int result = showUntrustedProgramWarning(program, window); if (result != QDialog::Accepted) { return false; } // Assume that service is an absolute path since we're being called (relative paths // would have been allowed unless Kiosk said no, therefore we already know where the // .desktop file is. Now add a header to it if it doesn't already have one // and add the +x bit. QString errorString; if (!::makeServiceFileExecutable(service.entryPath(), errorString)) { QString serviceName = service.name(); if (serviceName.isEmpty()) { serviceName = service.genericName(); } KMessageBox::sorry( window, i18n("Unable to make the service %1 executable, aborting execution.\n%2.", serviceName, errorString) ); return false; } return true; } bool KRun::run(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { const RunFlags flags = tempFiles ? KRun::DeleteTemporaryFiles : RunFlags(); return runApplication(_service, _urls, window, flags, suggestedFileName, asn) != 0; } qint64 KRun::runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags, const QString &suggestedFileName, const QByteArray &asn) { if (!service.entryPath().isEmpty() && !KDesktopFile::isAuthorizedDesktopFile(service.entryPath()) && !::makeServiceExecutable(service, window)) { return 0; } KService::Ptr servicePtr(new KService(service)); // clone // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window) { window = window->window(); } - KIO::ProcessLauncherJob *job = new KIO::ProcessLauncherJob(servicePtr); + KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(servicePtr); job->setUrls(urls); if (flags & DeleteTemporaryFiles) { - job->setRunFlags(KIO::ProcessLauncherJob::DeleteTemporaryFiles); + job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles); } job->setSuggestedFileName(suggestedFileName); job->setStartupId(asn); - return runProcessLauncherJob(job, window); + return runApplicationLauncherJob(job, window); } qint64 KRun::runService(const KService &_service, const QList &_urls, QWidget *window, bool tempFiles, const QString &suggestedFileName, const QByteArray &asn) { return runApplication(_service, _urls, window, tempFiles ? RunFlags(DeleteTemporaryFiles) : RunFlags(), suggestedFileName, asn); } bool KRun::run(const QString &_exec, const QList &_urls, QWidget *window, const QString &_name, const QString &_icon, const QByteArray &asn) { KService::Ptr service(new KService(_name, _exec, _icon)); return runApplication(*service, _urls, window, RunFlags{}, QString(), asn); } bool KRun::runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory) { if (cmd.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command was empty, nothing to run"; return false; } const QStringList args = KShell::splitArgs(cmd); if (args.isEmpty()) { qCWarning(KIO_WIDGETS) << "Command could not be parsed."; return false; } const QString &bin = args.first(); return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn) { return runCommand(cmd, execName, iconName, window, asn, QString()); } bool KRun::runCommand(const QString &cmd, const QString &execName, const QString &iconName, QWidget *window, const QByteArray &asn, const QString &workingDirectory) { //qDebug() << "runCommand " << cmd << "," << execName; // QTBUG-59017 Calling winId() on an embedded widget will break interaction // with it on high-dpi multi-screen setups (cf. also Bug 363548), hence using // its parent window instead if (window) { window = window->window(); } auto *processRunner = new KProcessRunner(cmd, execName, iconName, asn, workingDirectory); return runProcessRunner(processRunner, window); } KRun::KRun(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) : d(new KRunPrivate(this)) { d->m_timer = new QTimer(this); d->m_timer->setObjectName(QStringLiteral("KRun::timer")); d->m_timer->setSingleShot(true); d->init(url, window, showProgressInfo, asn); } void KRun::KRunPrivate::init(const QUrl &url, QWidget *window, bool showProgressInfo, const QByteArray &asn) { m_bFault = false; m_bAutoDelete = true; m_bProgressInfo = showProgressInfo; m_bFinished = false; m_job = nullptr; m_strURL = url; m_bScanFile = false; m_bIsDirectory = false; m_runExecutables = true; m_followRedirections = true; m_window = window; m_asn = asn; q->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_bCheckPrompt = false; m_bInit = true; q->connect(m_timer, &QTimer::timeout, q, &KRun::slotTimeout); startTimer(); //qDebug() << "new KRun" << q << url << "timer=" << m_timer; } void KRun::init() { //qDebug() << "INIT called"; if (!d->m_strURL.isValid() || d->m_strURL.scheme().isEmpty()) { const QString error = !d->m_strURL.isValid() ? d->m_strURL.errorString() : d->m_strURL.toString(); handleInitError(KIO::ERR_MALFORMED_URL, i18n("Malformed URL\n%1", error)); qCWarning(KIO_WIDGETS) << "Malformed URL:" << error; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), d->m_strURL)) { QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.toDisplayString()); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } if (d->m_externalBrowserEnabled && checkNeedPortalSupport()) { // use the function from QDesktopServices as it handles portals correctly d->m_bFault = !QDesktopServices::openUrl(d->m_strURL); d->m_bFinished = true; d->startTimer(); return; } if (!d->m_externalBrowser.isEmpty() && d->m_strURL.scheme().startsWith(QLatin1String("http"))) { if (d->runExternalBrowser(d->m_externalBrowser)) { return; } } else if (d->m_strURL.isLocalFile() && (d->m_strURL.host().isEmpty() || (d->m_strURL.host() == QLatin1String("localhost")) || (d->m_strURL.host().compare(QHostInfo::localHostName(), Qt::CaseInsensitive) == 0))) { const QString localPath = d->m_strURL.toLocalFile(); if (!QFile::exists(localPath)) { handleInitError(KIO::ERR_DOES_NOT_EXIST, i18n("Unable to run the command specified. " "The file or folder %1 does not exist.", localPath.toHtmlEscaped())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); //qDebug() << "MIME TYPE is " << mime.name(); if (mime.isDefault() && !QFileInfo(localPath).isReadable()) { // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002) const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, localPath); handleInitError(KIO::ERR_ACCESS_DENIED, msg); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } else { mimeTypeDetermined(mime.name()); return; } } else if (KIO::DesktopExecParser::hasSchemeHandler(d->m_strURL)) { // looks for an application associated with x-scheme-handler/ const KService::Ptr service = schemeService(d->m_strURL.scheme()); if (service) { // if there's one... if (runApplication(*service, QList() << d->m_strURL, d->m_window, RunFlags{}, QString(), d->m_asn)) { d->m_bFinished = true; d->startTimer(); return; } } else { // fallback, look for associated helper protocol Q_ASSERT(KProtocolInfo::isHelperProtocol(d->m_strURL.scheme())); const auto exec = KProtocolInfo::exec(d->m_strURL.scheme()); if (exec.isEmpty()) { // use default mimetype opener for file mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL)); return; } else { if (run(exec, QList() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) { d->m_bFinished = true; d->startTimer(); return; } } } } #if 0 // removed for KF5 (for portability). Reintroduce a bool or flag if useful. // Did we already get the information that it is a directory ? if ((d->m_mode & QT_STAT_MASK) == QT_STAT_DIR) { mimeTypeDetermined("inode/directory"); return; } #endif // Let's see whether it is a directory if (!KProtocolManager::supportsListing(d->m_strURL)) { // No support for listing => it can't be a directory (example: http) if (!KProtocolManager::supportsReading(d->m_strURL)) { // No support for reading files either => we can't do anything (example: mailto URL, with no associated app) handleInitError(KIO::ERR_UNSUPPORTED_ACTION, i18n("Could not find any application or handler for %1", d->m_strURL.toDisplayString())); d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } scanFile(); return; } //qDebug() << "Testing directory (stating)"; // It may be a directory or a file, let's stat KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::StatJob *job = KIO::statDetails(d->m_strURL, KIO::StatJob::SourceSide, KIO::StatDetail::Basic, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, &KJob::result, this, &KRun::slotStatResult); d->m_job = job; //qDebug() << "Job" << job << "is about stating" << d->m_strURL; } KRun::~KRun() { //qDebug() << this; d->m_timer->stop(); killJob(); //qDebug() << this << "done"; delete d; } bool KRun::KRunPrivate::runExternalBrowser(const QString &_exec) { QList urls; urls.append(m_strURL); if (_exec.startsWith(QLatin1Char('!'))) { // Literal command const QString exec = _exec.midRef(1) + QLatin1String(" %u"); if (KRun::run(exec, urls, m_window, QString(), QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } else { KService::Ptr service = KService::serviceByStorageId(_exec); if (service && KRun::runApplication(*service, urls, m_window, RunFlags{}, QString(), m_asn)) { m_bFinished = true; startTimer(); return true; } } return false; } void KRun::KRunPrivate::showPrompt() { ExecutableFileOpenDialog *dialog = new ExecutableFileOpenDialog(promptMode(), q->window()); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &ExecutableFileOpenDialog::finished, q, [this, dialog](int result){ onDialogFinished(result, dialog->isDontAskAgainChecked()); }); dialog->show(); } bool KRun::KRunPrivate::isPromptNeeded() { if (m_strURL == QUrl(QStringLiteral("remote:/x-wizard_service.desktop"))) { return false; } const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); const bool isFileExecutable = (isExecutableFile(m_strURL, mime.name()) || mime.inherits(QStringLiteral("application/x-desktop"))); if (isFileExecutable) { KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); const QString value = cfgGroup.readEntry("behaviourOnLaunch", "alwaysAsk"); if (value == QLatin1String("alwaysAsk")) { return true; } else { q->setRunExecutables(value == QLatin1String("execute")); } } return false; } ExecutableFileOpenDialog::Mode KRun::KRunPrivate::promptMode() { const QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(m_strURL); if (mime.inherits(QStringLiteral("text/plain"))) { return ExecutableFileOpenDialog::OpenOrExecute; } #ifndef Q_OS_WIN if (mime.inherits(QStringLiteral("application/x-ms-dos-executable"))) { return ExecutableFileOpenDialog::OpenAsExecute; } #endif return ExecutableFileOpenDialog::OnlyExecute; } void KRun::KRunPrivate::onDialogFinished(int result, bool isDontAskAgainSet) { if (result == ExecutableFileOpenDialog::Rejected) { m_bFinished = true; m_bInit = false; startTimer(); return; } q->setRunExecutables(result == ExecutableFileOpenDialog::ExecuteFile); if (isDontAskAgainSet) { QString output = result == ExecutableFileOpenDialog::OpenFile ? QStringLiteral("open") : QStringLiteral("execute"); KConfigGroup cfgGroup(KSharedConfig::openConfig(QStringLiteral("kiorc")), "Executable scripts"); cfgGroup.writeEntry("behaviourOnLaunch", output); } startTimer(); } void KRun::scanFile() { //qDebug() << d->m_strURL; // First, let's check for well-known extensions // Not when there is a query in the URL, in any case. if (!d->m_strURL.hasQuery()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(d->m_strURL); if (!mime.isDefault() || d->m_strURL.isLocalFile()) { //qDebug() << "Scanfile: MIME TYPE is " << mime.name(); mimeTypeDetermined(mime.name()); return; } } // No mimetype found, and the URL is not local (or fast mode not allowed). // We need to apply the 'KIO' method, i.e. either asking the server or // getting some data out of the file, to know what mimetype it is. if (!KProtocolManager::supportsReading(d->m_strURL)) { qCWarning(KIO_WIDGETS) << "#### NO SUPPORT FOR READING!"; d->m_bFault = true; d->m_bFinished = true; d->startTimer(); return; } //qDebug() << this << "Scanning file" << d->m_strURL; KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags); KJobWidgets::setWindow(job, d->m_window); connect(job, &KJob::result, this, &KRun::slotScanFinished); connect(job, QOverload::of(&KIO::TransferJob::mimetype), this, &KRun::slotScanMimeType); d->m_job = job; //qDebug() << "Job" << job << "is about getting from" << d->m_strURL; } // When arriving in that method there are 6 possible states: // must_show_prompt, must_init, must_scan_file, found_dir, done+error or done+success. void KRun::slotTimeout() { if (d->m_bCheckPrompt) { d->m_bCheckPrompt = false; if (d->isPromptNeeded()) { d->showPrompt(); return; } } if (d->m_bInit) { d->m_bInit = false; init(); return; } if (d->m_bFault) { emit error(); } if (d->m_bFinished) { emit finished(); } else { if (d->m_bScanFile) { d->m_bScanFile = false; scanFile(); return; } else if (d->m_bIsDirectory) { d->m_bIsDirectory = false; mimeTypeDetermined(QStringLiteral("inode/directory")); return; } } if (d->m_bAutoDelete) { deleteLater(); return; } } void KRun::slotStatResult(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR" << job->error() << job->errorString(); handleError(job); //qDebug() << this << " KRun returning from showErrorDialog, starting timer to delete us"; d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } else { //qDebug() << "Finished"; KIO::StatJob *statJob = qobject_cast(job); if (!statJob) { qFatal("Fatal Error: job is a %s, should be a StatJob", typeid(*job).name()); } // Update our URL in case of a redirection setUrl(statJob->url()); const KIO::UDSEntry entry = statJob->statResult(); const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE); if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { d->m_bIsDirectory = true; // it's a dir } else { d->m_bScanFile = true; // it's a file } d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); // mimetype already known? (e.g. print:/manager) const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE); if (!knownMimeType.isEmpty()) { mimeTypeDetermined(knownMimeType); d->m_bFinished = true; } // We should have found something assert(d->m_bScanFile || d->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 d->startTimer(); } } void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype) { if (mimetype.isEmpty()) { qCWarning(KIO_WIDGETS) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().scheme(); } mimeTypeDetermined(mimetype); d->m_job = nullptr; } void KRun::slotScanFinished(KJob *job) { d->m_job = nullptr; const int errCode = job->error(); if (errCode) { // ERR_NO_CONTENT is not an error, but an indication no further // actions needs to be taken. if (errCode != KIO::ERR_NO_CONTENT) { qCWarning(KIO_WIDGETS) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString(); handleError(job); d->m_bFault = true; } d->m_bFinished = true; // will emit the error and autodelete this d->startTimer(); } } void KRun::mimeTypeDetermined(const QString &mimeType) { // foundMimeType reimplementations might show a dialog box; // make sure some timer doesn't kill us meanwhile (#137678, #156447) Q_ASSERT(!d->m_showingDialog); d->m_showingDialog = true; foundMimeType(mimeType); d->m_showingDialog = false; // We cannot assume that we're finished here. Some reimplementations // start a KIO job and call setFinished only later. } void KRun::foundMimeType(const QString &type) { //qDebug() << "Resulting mime type is " << type; QMimeDatabase db; KIO::TransferJob *job = qobject_cast(d->m_job); if (job) { // Update our URL in case of a redirection if (d->m_followRedirections) { setUrl(job->url()); } job->putOnHold(); KIO::Scheduler::publishSlaveOnHold(); d->m_job = nullptr; } Q_ASSERT(!d->m_bFinished); // Support for preferred service setting, see setPreferredService if (!d->m_preferredService.isEmpty()) { //qDebug() << "Attempting to open with preferred service: " << d->m_preferredService; KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService); if (serv && serv->hasMimeType(type)) { QList lst; lst.append(d->m_strURL); if (KRun::runApplication(*serv, lst, d->m_window, RunFlags{}, QString(), d->m_asn)) { setFinished(true); return; } /// Note: if that service failed, we'll go to runUrl below to /// maybe find another service, even though an error dialog box was /// already 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. QMimeType mime = db.mimeTypeForName(type); if (!mime.isValid()) { qCWarning(KIO_WIDGETS) << "Unknown mimetype " << type; } else if (mime.inherits(QStringLiteral("application/x-desktop")) && !d->m_localPath.isEmpty()) { d->m_strURL = QUrl::fromLocalFile(d->m_localPath); } KRun::RunFlags runFlags; if (d->m_runExecutables) { runFlags |= KRun::RunExecutables; } if (!KRun::runUrl(d->m_strURL, type, d->m_window, runFlags, d->m_suggestedFileName, d->m_asn)) { d->m_bFault = true; } setFinished(true); } void KRun::killJob() { if (d->m_job) { //qDebug() << this << "m_job=" << d->m_job; d->m_job->kill(); d->m_job = nullptr; } } void KRun::abort() { if (d->m_bFinished) { return; } //qDebug() << this << "m_showingDialog=" << d->m_showingDialog; 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_showingDialog) { return; } d->m_bFault = true; d->m_bFinished = true; d->m_bInit = false; d->m_bScanFile = false; // will emit the error and autodelete this d->startTimer(); } QWidget *KRun::window() const { return d->m_window; } bool KRun::hasError() const { return d->m_bFault; } bool KRun::hasFinished() const { return d->m_bFinished; } bool KRun::autoDelete() const { return d->m_bAutoDelete; } void KRun::setAutoDelete(bool b) { d->m_bAutoDelete = b; } void KRun::setEnableExternalBrowser(bool b) { d->m_externalBrowserEnabled = b; if (d->m_externalBrowserEnabled) { d->m_externalBrowser = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("BrowserApplication"); // If a default browser isn't set in kdeglobals, fall back to mimeapps.list if (!d->m_externalBrowser.isEmpty()) { return; } KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); KConfigGroup defaultApps(profile, "Default Applications"); d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/https"); if (d->m_externalBrowser.isEmpty()) { d->m_externalBrowser = defaultApps.readEntry("x-scheme-handler/http"); } } else { d->m_externalBrowser.clear(); } } void KRun::setPreferredService(const QString &desktopEntryName) { d->m_preferredService = desktopEntryName; } void KRun::setRunExecutables(bool b) { d->m_runExecutables = b; } void KRun::setSuggestedFileName(const QString &fileName) { d->m_suggestedFileName = fileName; } void KRun::setShowScriptExecutionPrompt(bool showPrompt) { d->m_bCheckPrompt = showPrompt; } void KRun::setFollowRedirections(bool followRedirections) { d->m_followRedirections = followRedirections; } QString KRun::suggestedFileName() const { return d->m_suggestedFileName; } bool KRun::isExecutable(const QString &mimeTypeName) { QMimeDatabase db; QMimeType mimeType = db.mimeTypeForName(mimeTypeName); return (mimeType.inherits(QLatin1String("application/x-desktop")) || mimeType.inherits(QLatin1String("application/x-executable")) || /* See https://bugs.freedesktop.org/show_bug.cgi?id=97226 */ mimeType.inherits(QLatin1String("application/x-sharedlib")) || mimeType.inherits(QLatin1String("application/x-ms-dos-executable")) || mimeType.inherits(QLatin1String("application/x-shellscript"))); } void KRun::setUrl(const QUrl &url) { d->m_strURL = url; } QUrl KRun::url() const { return d->m_strURL; } void KRun::setError(bool error) { d->m_bFault = error; } void KRun::setProgressInfo(bool progressInfo) { d->m_bProgressInfo = progressInfo; } bool KRun::progressInfo() const { return d->m_bProgressInfo; } void KRun::setFinished(bool finished) { d->m_bFinished = finished; if (finished) { d->startTimer(); } } void KRun::setJob(KIO::Job *job) { d->m_job = job; } KIO::Job *KRun::job() { return d->m_job; } QTimer &KRun::timer() { return *d->m_timer; } void KRun::setDoScanFile(bool scanFile) { d->m_bScanFile = scanFile; } bool KRun::doScanFile() const { return d->m_bScanFile; } void KRun::setIsDirecory(bool isDirectory) { d->m_bIsDirectory = isDirectory; } bool KRun::isDirectory() const { return d->m_bIsDirectory; } void KRun::setInitializeNextAction(bool initialize) { d->m_bInit = initialize; } bool KRun::initializeNextAction() const { return d->m_bInit; } bool KRun::isLocalFile() const { return d->m_strURL.isLocalFile(); } #include "moc_krun.cpp" #include "moc_krun_p.cpp" #include "krun.moc" diff --git a/src/widgets/krun.h b/src/widgets/krun.h index a37a1f75..255b772e 100644 --- a/src/widgets/krun.h +++ b/src/widgets/krun.h @@ -1,674 +1,674 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 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. */ #ifndef KRUN_H #define KRUN_H #include "kiowidgets_export.h" #include #include #include class KService; class KJob; class QTimer; namespace KIO { class Job; } /** * @class KRun krun.h * * To open files with their associated applications in KDE, use KRun. * * It can execute any desktop entry, as well as any file, using * the default application or another application "bound" to the file type * (or URL protocol). * * In that example, the mimetype of the file is not known by the application, * so a KRun instance must be created. It will determine the mimetype by itself. * If the mimetype is known, or if you even know the service (application) to * use for this file, use one of the static methods. * * By default KRun uses auto deletion. It causes the KRun instance to delete * itself when the it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion, otherwise it will crash. * * This respects the "shell_access", "openwith" and "run_desktop_files" Kiosk * action restrictions (see KAuthorized::authorize()). * * @short Opens files with their associated applications in KDE */ class KIOWIDGETS_EXPORT KRun : public QObject { Q_OBJECT public: /** * @param url the URL of the file or directory to 'run' * * @param window * The top-level widget of the app that invoked this object. * It is used to make sure private information like passwords * are properly handled per application. * * @param showProgressInfo * Whether to show progress information when determining the * type of the file (i.e. when using KIO::stat and KIO::mimetype) * Before you set this to false to avoid a dialog box, think about * a very slow FTP server... * It is always better to provide progress info in such cases. * * @param asn * Application startup notification id, if available (otherwise ""). * * Porting note: kdelibs 4 had mode_t mode and bool isLocalFile arguments after * window and before showProgressInfo. Removed in KF5. */ KRun(const QUrl &url, QWidget *window, bool showProgressInfo = true, const QByteArray &asn = QByteArray()); /** * Destructor. Don't call it yourself, since a KRun object auto-deletes * itself. */ virtual ~KRun(); /** * Abort this KRun. This kills any jobs launched by it, * and leads to deletion if auto-deletion is on. * This is much safer than deleting the KRun (in case it's * currently showing an error dialog box, for instance) */ void abort(); /** * Returns true if the KRun instance has an error. * @return true when an error occurred * @see error() */ bool hasError() const; /** * Returns true if the KRun instance has finished. * @return true if the KRun instance has finished * @see finished() */ bool hasFinished() const; /** * Checks whether auto delete is activated. * Auto-deletion causes the KRun instance to delete itself * when it finished its task. * By default auto deletion is on. * @return true if auto deletion is on, false otherwise */ bool autoDelete() const; /** * Enables or disabled auto deletion. * Auto deletion causes the KRun instance to delete itself * when it finished its task. If you allocate the KRun * object on the stack you must disable auto deletion. * By default auto deletion is on. * @param b true to enable auto deletion, false to disable */ void setAutoDelete(bool b); /** * Set the preferred service for opening this URL, after * its mimetype will have been found by KRun. IMPORTANT: the service is * only used if its configuration says it can handle this mimetype. * This is used for instance for the X-KDE-LastOpenedWith key in * the recent documents list, or for the app selection in * KParts::BrowserOpenOrSaveQuestion. * @param desktopEntryName the desktopEntryName of the service, e.g. "kate". */ void setPreferredService(const QString &desktopEntryName); /** * Sets whether executables, .desktop files or shell scripts should * be run by KRun. This is enabled by default. * @param b whether to run executable files or not. * @see isExecutable() */ void setRunExecutables(bool b); /** * Sets whether KRun should follow URLs redirections. * This is enabled by default * @param b whether to follow redirections or not. * @since 5.55 */ void setFollowRedirections(bool b); /** * Sets whether the external webbrowser setting should be honoured. * This is enabled by default. * This should only be disabled in webbrowser applications. * @param b whether to enable the external browser or not. */ void setEnableExternalBrowser(bool b); /** * Sets the file name to use in the case of downloading the file to a tempfile * in order to give to a non-url-aware application. Some apps rely on the extension * to determine the mimetype of the file. Usually the file name comes from the URL, * but in the case of the HTTP Content-Disposition header, we need to override the * file name. */ void setSuggestedFileName(const QString &fileName); /** * Sets whether a prompt should be shown before executing scripts or desktop files. * If enabled, KRun uses the "kiorc" configuration file to decide whether to open the * file, execute it or show a prompt. * @since 5.4 */ void setShowScriptExecutionPrompt(bool showPrompt); /** * Suggested file name given by the server (e.g. HTTP content-disposition) */ QString suggestedFileName() const; /** * Associated window, as passed to the constructor * @since 4.9.3 */ QWidget *window() const; #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 6) /** * Open a list of URLs with a certain service (application). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * * @deprecated since 5.6, use runApplication instead. No change needed on the application side, * the only difference is the return value (qint64 instead of bool). */ KIOWIDGETS_DEPRECATED_VERSION(5, 6, "Use KRun::runApplication(const KService &, const QList &, QWidget *, RunFlags, const QString &, const QByteArray &") static bool run(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); #endif /** * Open a list of URLs with a certain service (application). * * Prefer runApplication(), unless you need to wait for the application * to register to D-Bus before this method returns (but that should rather * be done with D-Bus activation). * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.6 */ static qint64 runService(const KService &service, const QList &urls, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); // TODO KF6: deprecate/remove enum RunFlag { DeleteTemporaryFiles = 0x1, ///< the URLs passed to the service will be deleted when it exits (if the URLs are local files) RunExecutables = 0x2, ///< Whether to run URLs that are executable scripts or binaries @see isExecutableFile() @since 5.31 }; Q_DECLARE_FLAGS(RunFlags, RunFlag) /** * Run an application (known from its .desktop file, i.e. as a KService) * * If you need to wait for the application to register to D-Bus, use D-Bus activation instead. * * If you don't need the prompt for asking the user whether to add the executable bit for - * desktop files or binaries that don't have it, you can use KIO::ProcessLauncherJob from KIOGui directly. + * desktop files or binaries that don't have it, you can use KIO::ApplicationLauncherJob from KIOGui directly. * * @param service the service to run * @param urls the list of URLs, can be empty (app launched * without argument) * @param window The top-level widget of the app that invoked this object. * @param flags various flags * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return 0 on error, the process ID on success * @since 5.24 */ static qint64 runApplication(const KService &service, const QList &urls, QWidget *window, RunFlags flags = RunFlags(), const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Open a list of URLs with an executable. * * @param exec the name of the executable, for example * "/usr/bin/netscape %u". * Don't forget to include the %u if you know that the applications * supports URLs. Otherwise, non-local urls will first be downloaded * to a temp file (using kioexec). * @param urls the list of URLs to open, can be empty (app launched without argument) * @param window The top-level widget of the app that invoked this object. * @param name the logical name of the application, for example * "Netscape 4.06". * @param icon the icon which should be used by the application. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool run(const QString &exec, const QList &urls, QWidget *window, const QString &name = QString(), const QString &icon = QString(), const QByteArray &asn = QByteArray()); #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 31) /** * Open the given URL. * * This function is used after the mime type * is found out. It will search for all services which can handle * the mime type and call run() afterwards. * @param url the URL to open * @param mimetype the mime type of the resource * @param window The top-level widget of the app that invoked this object. * @param tempFile if true and url is a local file, it will be deleted * when the launched application exits. * @param runExecutables if false then local .desktop files, * executables and shell scripts will not be run. * See also isExecutable(). * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @deprecated since 5.31, use runUrl() with RunFlags instead. */ KIOWIDGETS_DEPRECATED_VERSION(5, 31, "Use KRun::const QUrl &, const QString &, QWidget *, RunFlags, const QString &, const QByteArray &") static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile = false, bool runExecutables = true, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); #endif /** * Open the given URL. * * This function can be used after the mime type has been found out. * It will search for all services which can handle the mime type and call run() afterwards. * * @param url The URL to open. * @param mimetype The mime type of the resource. * @param window The top-level widget of the app that invoked this object. * @param flags Various run flags. * @param suggestedFileName See setSuggestedFileName() * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error * @since 5.31 */ static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, RunFlags flags, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); /** * Run the given shell command and notifies KDE of the starting * of the application. If the program to be called doesn't exist, * an error box will be displayed. * * Use only when you know the full command line. Otherwise use the other * static methods, or KRun's constructor. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param window The top-level widget of the app that invoked this object. * @param workingDirectory directory to use for relative paths, so that * a command like "kwrite file.txt" finds file.txt from the right place * * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, QWidget *window, const QString &workingDirectory = QString()); /** * Same as the other runCommand(), but it also takes the name of the * binary, to display an error message in case it couldn't find it. * * @param cmd must be a shell command. You must not append "&" * to it, since the function will do that for you. * @param execName the name of the executable * @param icon icon for app starting notification * @param window The top-level widget of the app that invoked this object. * @param asn Application startup notification id, if any (otherwise ""). * @return @c true on success, @c false on error */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn = QByteArray()); /** * Overload that also takes a working directory, so that a command like * "kwrite file.txt" finds file.txt from the right place. * @param workingDirectory the working directory for the started process. The default * (if passing an empty string) is the user's document path. * @since 4.4 */ static bool runCommand(const QString &cmd, const QString &execName, const QString &icon, QWidget *window, const QByteArray &asn, const QString &workingDirectory); // TODO KDE5: merge the above with 5-args runCommand, using QString() /** * Display the Open-With dialog for those URLs, and run the chosen application. * @param lst the list of applications to run * @param window The top-level widget of the app that invoked this object. * @param tempFiles if true and lst are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * @param asn Application startup notification id, if any (otherwise ""). * @return false if the dialog was canceled */ static bool displayOpenWithDialog(const QList &lst, QWidget *window, bool tempFiles = false, const QString &suggestedFileName = QString(), const QByteArray &asn = QByteArray()); // TODO deprecate and provide RunFlags() overload #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 0) /** * Quotes a string for the shell. * An empty string will @em not be quoted. * * @param str the string to quote. The quoted string will be written here * * @deprecated Since 4.0, use KShell::quoteArg() instead. @em Note that this function * behaves differently for empty arguments and returns the result * differently. */ KIOWIDGETS_DEPRECATED_VERSION(4, 0, "Use KShell::quoteArg(...)") static void shellQuote(QString &str); #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Processes a Exec= line as found in .desktop files. * @param _service the service to extract information from. * @param _urls The urls the service should open. * @param tempFiles if true and urls are local files, they will be deleted * when the application exits. * @param suggestedFileName see setSuggestedFileName * * @return a list of arguments suitable for QProcess. * @deprecated since 5.0, use KIO::DesktopExecParser */ KIOWIDGETS_DEPRECATED_VERSION(5, 0, "Use KIO::DesktopExecParser") static QStringList processDesktopExec(const KService &_service, const QList &_urls, bool tempFiles = false, const QString &suggestedFileName = QString()); #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(5, 0) /** * Given a full command line (e.g. the Exec= line from a .desktop file), * extract the name of the binary being run. * @param execLine the full command line * @param removePath if true, remove a (relative or absolute) path. E.g. /usr/bin/ls becomes ls. * @return the name of the executable to run * @deprecated since 5.0, use KIO::DesktopExecParser::executableName if removePath was true, * or KIO::DesktopExecParser::executablePath if removePath was false. */ KIOWIDGETS_DEPRECATED_VERSION(5, 0, "See API docs") static QString binaryName(const QString &execLine, bool removePath); #endif /** * Returns whether @p mimeType refers to an executable program instead * of a data file. */ static bool isExecutable(const QString &mimeType); /** * Returns whether the @p url of @p mimetype is executable. * To be executable the file must pass the following rules: * -# Must reside on the local filesystem. * -# Must be marked as executable for the user by the filesystem. * -# The mime type must inherit application/x-executable, application/x-executable-script or application/x-sharedlib. * To allow a script to run when the above rules are satisfied add the entry * @code * X-KDE-IsAlso=application/x-executable-script * @endcode * to the mimetype's desktop file. */ static bool isExecutableFile(const QUrl &url, const QString &mimetype); /** * @internal */ static bool checkStartupNotify(const QString &binName, const KService *service, bool *silent_arg, QByteArray *wmclass_arg); Q_SIGNALS: /** * Emitted when the operation finished. * This signal is emitted in all cases of completion, whether successful or with error. * @see hasFinished() */ void finished(); /** * Emitted when the operation had an error. * @see hasError() */ void error(); protected Q_SLOTS: /** * All following protected slots are used by subclasses of KRun! */ /** * This slot is called whenever the internal timer fired, * in order to move on to the next step. */ void slotTimeout(); // KDE5: rename to slotNextStep() or something like that /** * This slot is called when the scan job is finished. */ void slotScanFinished(KJob *); /** * This slot is called when the scan job has found out * the mime type. */ void slotScanMimeType(KIO::Job *, const QString &type); /** * Call this from subclasses when you have determined the mimetype. * It will call foundMimeType, but also sets up protection against deletion during message boxes. * @since 4.0.2 */ void mimeTypeDetermined(const QString &mimeType); /** * This slot is called when the 'stat' job has finished. */ virtual void slotStatResult(KJob *); protected: /** * All following protected methods are used by subclasses of KRun! */ /** * Initializes the krun object. */ virtual void init(); /** * Start scanning a file. */ virtual void scanFile(); /** * Called if the mimetype has been detected. The function runs * the application associated with this mimetype. * Reimplement this method to implement a different behavior, * like opening the component for displaying the URL embedded. * * Important: call setFinished(true) once you are done! * Usually at the end of the foundMimeType reimplementation, but if the * reimplementation is asynchronous (e.g. uses KIO jobs) then * it can be called later instead. */ virtual void foundMimeType(const QString &type); /** * Kills the file scanning job. */ virtual void killJob(); /** * Called when KRun detects an error during the init phase. * * The default implementation shows a message box. * @since 5.0 */ virtual void handleInitError(int kioErrorCode, const QString &errorMsg); /** * Called when a KIO job started by KRun gives an error. * * The default implementation shows a message box. */ virtual void handleError(KJob *job); /** * Sets the url. */ void setUrl(const QUrl &url); /** * Returns the url. */ QUrl url() const; /** * Sets whether an error has occurred. */ void setError(bool error); /** * Sets whether progress information shall be shown. */ void setProgressInfo(bool progressInfo); /** * Returns whether progress information are shown. */ bool progressInfo() const; /** * Marks this 'KRun' instance as finished. */ void setFinished(bool finished); /** * Sets the job. */ void setJob(KIO::Job *job); /** * Returns the job. */ KIO::Job *job(); #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 4) /** * Returns the timer object. * @deprecated Since 4.4. setFinished(true) now takes care of the timer().start(0), * so this can be removed. */ KIOWIDGETS_DEPRECATED_VERSION(4, 4, "See API docs") QTimer &timer(); #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Indicate that the next action is to scan the file. * @deprecated Since 4.1. Not useful in public API */ KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") void setDoScanFile(bool scanFile); #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Returns whether the file shall be scanned. * @deprecated Since 4.1. Not useful in public API */ KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") bool doScanFile() const; #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * Sets whether it is a directory. * @deprecated Since 4.1. Typo in the name, and not useful as a public method */ KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") void setIsDirecory(bool isDirectory); #endif /** * Returns whether it is a directory. */ bool isDirectory() const; #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * @deprecated Since 4.1. Not useful in public API */ KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") void setInitializeNextAction(bool initialize); #endif #if KIOWIDGETS_ENABLE_DEPRECATED_SINCE(4, 1) /** * @deprecated Since 4.1. Not useful in public API */ KIOWIDGETS_DEPRECATED_VERSION(4, 1, "Do not use") bool initializeNextAction() const; #endif /** * Returns whether it is a local file. */ bool isLocalFile() const; private: class KRunPrivate; KRunPrivate *const d; }; #endif