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(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(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 @@ -77,7 +77,7 @@ * Returns view container for all tabs */ QVector viewContainers() const; - + /** * Opens each directory in \p dirs in a separate tab. If \a splitView is set, * 2 directories are collected within one tab. @@ -91,7 +91,7 @@ * \pre \a files must contain at least one url. */ void openFiles(const QList& files, bool splitView); - + /** * Returns the 'Create New...' sub menu which also can be shared * with other menus (e. g. a context menu). @@ -101,6 +101,39 @@ 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. + * + * @note this function is overloaded so that it is callable via DBus. + */ + 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. + * + * @note this is overloaded so that this function is callable via DBus. + */ + void openFiles(const QStringList &files, bool splitView); + + /** + * Tries to raise/activate the Dolphin window. + */ + void activateWindow(); + + /** + * Determines if a URL is open in any tab. + * @note Use of QString instead of QUrl is required to be callable via DBus. + * + * @param url URL to look for + * @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, diff --git a/src/dolphinmainwindow.cpp b/src/dolphinmainwindow.cpp --- a/src/dolphinmainwindow.cpp +++ b/src/dolphinmainwindow.cpp @@ -61,11 +61,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -200,11 +202,27 @@ m_tabWidget->openDirectories(dirs, splitView); } +void DolphinMainWindow::openDirectories(const QStringList& dirs, bool splitView) +{ + openDirectories(QUrl::fromStringList(dirs), splitView); +} + void DolphinMainWindow::openFiles(const QList& files, bool splitView) { m_tabWidget->openFiles(files, splitView); } +void DolphinMainWindow::openFiles(const QStringList& files, bool splitView) +{ + openFiles(QUrl::fromStringList(files), splitView); +} + +void DolphinMainWindow::activateWindow() +{ + KStartupInfo::setNewStartupId(window(), KStartupInfo::startupId()); + KWindowSystem::activateWindow(window()->effectiveWinId()); +} + void DolphinMainWindow::showCommand(CommandType command) { DolphinStatusBar* statusBar = m_activeViewContainer->statusBar(); @@ -1707,3 +1725,12 @@ } } +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) const; signals: /** diff --git a/src/dolphintabwidget.cpp b/src/dolphintabwidget.cpp --- a/src/dolphintabwidget.cpp +++ b/src/dolphintabwidget.cpp @@ -186,11 +186,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); } } } @@ -290,6 +295,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); @@ -374,3 +380,17 @@ // and not misinterpreted as a keyboard shortcut in QTabBar::setTabText() return name.replace('&', QLatin1String("&&")); } + +int DolphinTabWidget::getIndexByUrl(const QUrl& url) const +{ + for (int i = 0; i < count(); i++) { + // Conversion to display string is necessary to deal with the '~' alias. + // i.e. to acknowledge that ~/ is equivalent to /home/user/ + const QUrl tabUrl = tabPageAt(i)->activeViewContainer()->url(); + if (url == tabUrl || + url.toDisplayString(QUrl::StripTrailingSlash) == tabUrl.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 @@ -41,6 +41,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 QList& urls, bool openFiles, bool splitView, const QString& preferredService = QString()); /** * 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,8 @@ #include #include +#include +#include QList Dolphin::validateUris(const QStringList& uriList) { @@ -49,15 +51,87 @@ 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() + ); +} - KRun::run(command, urls, window, qApp->applicationDisplayName(), qApp->windowIcon().name()); +bool Dolphin::attachToExistingInstance(const QList& urls, bool openFiles, bool splitView, const QString& preferredService) +{ + const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames().value(); + + // Don't match the service without trailing "-" (unique instance) + const QString pattern = QStringLiteral("org.kde.dolphin-"); + const QString myPid = QString::number(QCoreApplication::applicationPid()); + QVector, QStringList>> dolphinServices; + if (!preferredService.isEmpty()) { + QSharedPointer preferred( + new QDBusInterface(preferredService, + QStringLiteral("/dolphin/Dolphin_1"), + QStringLiteral("org.kde.dolphin.MainWindow")) + ); + if (preferred->isValid()) { + dolphinServices.append(qMakePair(preferred, QStringList() )); + } + } + + // 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; + } + dolphinServices.append(qMakePair(instance, QStringList())); + } + } + + 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: QUrl::toStringList(urls)) { + bool urlFound = false; + for (auto& service: dolphinServices) { + QDBusReply isUrlOpen = service.first->call(QStringLiteral("isUrlOpen"), url); + if (isUrlOpen.isValid() && isUrlOpen.value()) { + service.second.append(url); + urlFound = true; + break; + } + } + if (!urlFound) { + newUrls.append(url); + } + } + dolphinServices.front().second << newUrls; + + for (const auto& service: dolphinServices) { + if (!service.second.isEmpty()) { + service.first->call(openFiles ? QStringLiteral("openFiles") : QStringLiteral("openDirectories"), service.second, splitView); + service.first->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(); + QList urls = 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()); } - 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);