diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ TextWidgets Notifications Crash + WindowSystem ) find_package(KF5 ${KF5_MIN_VERSION} OPTIONAL_COMPONENTS Activities diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,7 @@ KF5::ConfigCore KF5::NewStuff KF5::Parts + KF5::WindowSystem ) if(HAVE_BALOO) diff --git a/src/dbusinterface.cpp b/src/dbusinterface.cpp --- a/src/dbusinterface.cpp +++ b/src/dbusinterface.cpp @@ -19,10 +19,13 @@ #include "dbusinterface.h" #include "global.h" +#include "dolphin_generalsettings.h" #include +#include #include +#include #include DBusInterface::DBusInterface() : @@ -41,7 +44,8 @@ if (urls.isEmpty()) { return; } - Dolphin::openNewWindow(urls); + const auto serviceName = QStringLiteral("org.kde.dolphin-%1").arg(QCoreApplication::applicationPid()); + Dolphin::attachToExistingInstance(QUrl::toStringList(urls), false, GeneralSettings::splitView(), serviceName); } void DBusInterface::ShowItems(const QStringList& uriList, const QString& startUpId) @@ -51,7 +55,8 @@ if (urls.isEmpty()) { return; } - Dolphin::openNewWindow(urls, nullptr, Dolphin::OpenNewWindowFlag::Select); + const auto serviceName = QStringLiteral("org.kde.dolphin-%1").arg(QCoreApplication::applicationPid()); + Dolphin::attachToExistingInstance(QUrl::toStringList(urls), true, GeneralSettings::splitView(), serviceName); } void DBusInterface::ShowItemProperties(const QStringList& uriList, const QString& startUpId) diff --git a/src/dolphinmainwindow.h b/src/dolphinmainwindow.h --- a/src/dolphinmainwindow.h +++ b/src/dolphinmainwindow.h @@ -71,29 +71,40 @@ */ DolphinViewContainer* activeViewContainer() const; + /** + * Returns the 'Create New...' sub menu which also can be shared + * with other menus (e. g. a context menu). + */ + KNewFileMenu* newFileMenu() const; + + void setTabsToHomeIfMountPathOpen(const QString& mountPath); + +public slots: /** * Opens each directory in \p dirs in a separate tab. If \a splitView is set, * 2 directories are collected within one tab. * \pre \a dirs must contain at least one url. */ - void openDirectories(const QList &dirs, bool splitView); + void openDirectories(const QStringList &dirs, bool splitView); /** * Opens the directories which contain the files \p files and selects all files. * If \a splitView is set, 2 directories are collected within one tab. * \pre \a files must contain at least one url. */ - void openFiles(const QList& files, bool splitView); - + void openFiles(const QStringList &files, bool splitView); + /** - * Returns the 'Create New...' sub menu which also can be shared - * with other menus (e. g. a context menu). + * Tries to raise/activate the Dolphin window. */ - KNewFileMenu* newFileMenu() const; - - void setTabsToHomeIfMountPathOpen(const QString& mountPath); - -public slots: + void activateWindow(); + + /** + * @returns true if url is currently open in a tab, false otherwise. + */ + bool isUrlOpen(const QString &url); + + /** * Pastes the clipboard data into the currently selected folder * of the active view. If not exactly one folder is selected, @@ -117,7 +128,7 @@ /** Stores all settings and quits Dolphin. */ void quit(); - + signals: /** * Is sent if the selection of the currently active view has diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -60,11 +60,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -183,14 +185,20 @@ { } -void DolphinMainWindow::openDirectories(const QList& dirs, bool splitView) +void DolphinMainWindow::openDirectories(const QStringList& dirs, bool splitView) { - m_tabWidget->openDirectories(dirs, splitView); + m_tabWidget->openDirectories(QUrl::fromStringList(dirs), splitView); } -void DolphinMainWindow::openFiles(const QList& files, bool splitView) +void DolphinMainWindow::openFiles(const QStringList& files, bool splitView) { - m_tabWidget->openFiles(files, splitView); + m_tabWidget->openFiles(QUrl::fromStringList(files), splitView); +} + +void DolphinMainWindow::activateWindow() +{ + KStartupInfo::setNewStartupId(window(), KStartupInfo::startupId()); + KWindowSystem::activateWindow(window()->effectiveWinId()); } void DolphinMainWindow::showCommand(CommandType command) @@ -1684,3 +1692,11 @@ } } +bool DolphinMainWindow::isUrlOpen(const QString& url) { + if (m_tabWidget->getIndexByURL(QUrl(url)) >= 0) { + return true; + } else { + return false; + } +} + diff --git a/src/dolphintabwidget.h b/src/dolphintabwidget.h --- a/src/dolphintabwidget.h +++ b/src/dolphintabwidget.h @@ -77,6 +77,12 @@ * the given Dolphin settings. */ void refreshViews(); + + /** + * @param url The URL that we would like + * @return index of the tab with the desired URL. returns -1 if not found + */ + int getIndexByURL(const QUrl& url); signals: /** @@ -211,6 +217,7 @@ */ QString tabName(DolphinTabPage* tabPage) const; + private: /** Caches the (negated) places panel visibility */ bool m_placesSelectorVisible; diff --git a/src/dolphintabwidget.cpp b/src/dolphintabwidget.cpp --- a/src/dolphintabwidget.cpp +++ b/src/dolphintabwidget.cpp @@ -177,11 +177,16 @@ QList::const_iterator it = dirs.constBegin(); while (it != dirs.constEnd()) { const QUrl& primaryUrl = *(it++); + const int index = getIndexByURL(primaryUrl); + if (index >= 0) { + setCurrentIndex(index); + continue; + } if (splitView && (it != dirs.constEnd())) { const QUrl& secondaryUrl = *(it++); - openNewTab(primaryUrl, secondaryUrl); + openNewActivatedTab(primaryUrl, secondaryUrl); } else { - openNewTab(primaryUrl); + openNewActivatedTab(primaryUrl); } } } @@ -281,6 +286,7 @@ args << tabPage->secondaryViewContainer()->url().url(); args << QStringLiteral("--split"); } + args << QStringLiteral("--new-window"); const QString command = QStringLiteral("dolphin %1").arg(KShell::joinArgs(args)); KRun::runCommand(command, this); @@ -365,3 +371,19 @@ // and not misinterpreted as a keyboard shortcut in QTabBar::setTabText() return name.replace('&', QLatin1String("&&")); } + +int DolphinTabWidget::getIndexByURL(const QUrl& url) +{ + const int num_tabs = count(); + for (int i = 0; i < num_tabs; i++) { + // Conversion to display string is necessary to deal with the '~' alias. + // i.e. to acknowledge that ~/ is equivalent to /home/user/ + if (url == tabPageAt(i)->activeViewContainer()->url() || + url.toDisplayString(QUrl::StripTrailingSlash) == + tabPageAt(i)->activeViewContainer()->url().toDisplayString(QUrl::StripTrailingSlash)) + { + return i; + } + } + return -1; +} diff --git a/src/global.h b/src/global.h --- a/src/global.h +++ b/src/global.h @@ -22,6 +22,7 @@ #include #include +#include namespace Dolphin { QList validateUris(const QStringList& uriList); @@ -41,6 +42,12 @@ * Opens a new Dolphin window */ void openNewWindow(const QList &urls = {}, QWidget *window = nullptr, const OpenNewWindowFlags &flags = OpenNewWindowFlag::None); + + /** + * Attaches URLs to an existing Dolphin instance if possible. + * Returns true if URLs were successfully attached + */ + bool attachToExistingInstance(const QStringList& urls, bool openFiles, bool splitView, const QString& preferredService = ""); /** * TODO: Move this somewhere global to all KDE apps, not just Dolphin diff --git a/src/global.cpp b/src/global.cpp --- a/src/global.cpp +++ b/src/global.cpp @@ -26,6 +26,9 @@ #include #include +#include +#include +#include QList Dolphin::validateUris(const QStringList& uriList) { @@ -49,15 +52,98 @@ void Dolphin::openNewWindow(const QList &urls, QWidget *window, const OpenNewWindowFlags &flags) { - QString command = QStringLiteral("dolphin"); + QString command = QStringLiteral("dolphin --new-window"); if (flags.testFlag(OpenNewWindowFlag::Select)) { command.append(QLatin1String(" --select")); } if (!urls.isEmpty()) { command.append(QLatin1String(" %U")); } + KRun::run( + command, + urls, + window, + QApplication::applicationDisplayName(), + QApplication::windowIcon().name() + ); +} + +bool Dolphin::attachToExistingInstance(const QStringList& urls, bool openFiles, bool splitView, const QString &preferredService) +{ + const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); - KRun::run(command, urls, window, qApp->applicationDisplayName(), qApp->windowIcon().name()); + struct UrlsForService { + QSharedPointer m_service; + QStringList m_urls; + UrlsForService(const QSharedPointer &service = nullptr) + :m_service(service) {} + }; + + // Don't match the service without trailing "-" (unique instance) + const QString pattern = QStringLiteral("org.kde.dolphin-"); + const QString myPid = QString::number(QCoreApplication::applicationPid()); + QVector dolphinServices; + if (preferredService != "") { + QSharedPointer preferred( + new QDBusInterface(preferredService, + QStringLiteral("/dolphin/Dolphin_1"), + QStringLiteral("org.kde.dolphin.MainWindow")) + ); + if (preferred->isValid()) { + UrlsForService urlsForInst(preferred); + dolphinServices.append(urlsForInst); + } + } + + // find all dolphin instances + for (const QString& service: services) { + if (service.startsWith(pattern) && !service.endsWith(myPid)) { + // Check if instance can handle our URLs + QSharedPointer instance( + new QDBusInterface(service, + QStringLiteral("/dolphin/Dolphin_1"), + QStringLiteral("org.kde.dolphin.MainWindow")) + ); + if (!instance->isValid()) { + continue; + } + UrlsForService urlsForInst(instance); + dolphinServices.append(urlsForInst); + } + } + + if (dolphinServices.isEmpty()) { + return false; + } + + QStringList newUrls; + + // check to see if any instances already have any of the given URLs open + for (const QString& url: urls) { + bool urlFound = false; + for (UrlsForService& service: dolphinServices) { + QDBusReply isUrlOpen = service.m_service->call(QStringLiteral("isUrlOpen"), url); + if (isUrlOpen.isValid()) { + if(isUrlOpen.value()) { + service.m_urls.append(url); + urlFound = true; + break; + } + } + } + if (!urlFound) { + newUrls.append(url); + } + } + dolphinServices.front().m_urls << newUrls; + + for (const UrlsForService& service: dolphinServices) { + if (!service.m_urls.isEmpty()) { + service.m_service->call(openFiles ? QStringLiteral("openFiles") : QStringLiteral("openDirectories"), service.m_urls, splitView); + service.m_service->call(QStringLiteral("activateWindow")); + } + } + return true; } diff --git a/src/main.cpp b/src/main.cpp --- a/src/main.cpp +++ b/src/main.cpp @@ -34,6 +34,10 @@ #include #include +#include +#include +#include +#include #ifndef Q_OS_WIN #include @@ -122,33 +126,42 @@ parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("select"), i18nc("@info:shell", "The files and folders passed as arguments " "will be selected."))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("split"), i18nc("@info:shell", "Dolphin will get started with a split view."))); + parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("new-window"), i18nc("@info:shell", "Dolphin will explicitly open in a new window."))); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("daemon"), i18nc("@info:shell", "Start Dolphin Daemon (only required for DBus Interface)"))); parser.addPositionalArgument(QStringLiteral("+[Url]"), i18nc("@info:shell", "Document to open")); parser.process(app); aboutData.processCommandLine(&parser); + const bool splitView = parser.isSet(QStringLiteral("split")) || GeneralSettings::splitView(); + const bool openFiles = parser.isSet(QStringLiteral("select")); + const QStringList args = parser.positionalArguments(); + QStringList urls = QUrl::toStringList(Dolphin::validateUris(args)); + if (parser.isSet(QStringLiteral("daemon"))) { return app.exec(); } - - const QStringList args = parser.positionalArguments(); - QList urls = Dolphin::validateUris(args); - + if (urls.isEmpty()) { // We need at least one URL to open Dolphin - urls.append(Dolphin::homeUrl()); + urls.append(Dolphin::homeUrl().toString()); } - const bool splitView = parser.isSet(QStringLiteral("split")) || GeneralSettings::splitView(); if (splitView && urls.size() < 2) { // Split view does only make sense if we have at least 2 URLs urls.append(urls.last()); } + if (!parser.isSet(QStringLiteral("new-window"))) { + if (Dolphin::attachToExistingInstance(urls, openFiles, splitView)) { + // Successfully attached to existing instance of Dolphin + return 0; + } + } + DolphinMainWindow* mainWindow = new DolphinMainWindow(); - if (parser.isSet(QStringLiteral("select"))) { + if (openFiles) { mainWindow->openFiles(urls, splitView); } else { mainWindow->openDirectories(urls, splitView); diff --git a/src/tests/dolphinmainwindowtest.cpp b/src/tests/dolphinmainwindowtest.cpp --- a/src/tests/dolphinmainwindowtest.cpp +++ b/src/tests/dolphinmainwindowtest.cpp @@ -63,7 +63,7 @@ // See https://bugs.kde.org/show_bug.cgi?id=379135 void DolphinMainWindowTest::testClosingTabsWithSearchBoxVisible() { - m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()) }, false); + m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()).toString() }, false); m_mainWindow->show(); // Without this call the searchbox doesn't get FocusIn events. QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); @@ -93,7 +93,7 @@ void DolphinMainWindowTest::testActiveViewAfterClosingSplitView() { - m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()) }, false); + m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()).toString() }, false); m_mainWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); QVERIFY(m_mainWindow->isVisible()); @@ -138,7 +138,7 @@ // Test case for bug #385111 void DolphinMainWindowTest::testUpdateWindowTitleAfterClosingSplitView() { - m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()) }, false); + m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()).toString() }, false); m_mainWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); QVERIFY(m_mainWindow->isVisible()); @@ -179,7 +179,7 @@ // Test case for bug #402641 void DolphinMainWindowTest::testUpdateWindowTitleAfterChangingSplitView() { - m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()) }, false); + m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()).toString() }, false); m_mainWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); QVERIFY(m_mainWindow->isVisible()); @@ -209,7 +209,7 @@ // Test case for bug #397910 void DolphinMainWindowTest::testOpenInNewTabTitle() { - m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()) }, false); + m_mainWindow->openDirectories({ QUrl::fromLocalFile(QDir::homePath()).toString() }, false); m_mainWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); QVERIFY(m_mainWindow->isVisible()); @@ -238,7 +238,7 @@ void DolphinMainWindowTest::testNewFileMenuEnabled() { QFETCH(QUrl, activeViewUrl); - m_mainWindow->openDirectories({ activeViewUrl }, false); + m_mainWindow->openDirectories({ activeViewUrl.toString() }, false); m_mainWindow->show(); QVERIFY(QTest::qWaitForWindowExposed(m_mainWindow.data())); QVERIFY(m_mainWindow->isVisible());