File indexing completed on 2024-04-28 15:52:01

0001 /*
0002     SPDX-FileCopyrightText: 2002 Wilco Greven <greven@kde.org>
0003     SPDX-FileCopyrightText: 2003 Christophe Devriese <Christophe.Devriese@student.kuleuven.ac.be>
0004     SPDX-FileCopyrightText: 2003 Laurent Montel <montel@kde.org>
0005     SPDX-FileCopyrightText: 2003-2007 Albert Astals Cid <aacid@kde.org>
0006     SPDX-FileCopyrightText: 2004 Andy Goossens <andygoossens@telenet.be>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "okular_main.h"
0012 
0013 #include "aboutdata.h"
0014 #include "shell.h"
0015 #include "shellutils.h"
0016 #include <KLocalizedString>
0017 #include <KWindowSystem>
0018 #include <QApplication>
0019 #include <QMimeData>
0020 #include <QTemporaryFile>
0021 #include <QTextStream>
0022 
0023 #include "config-okular.h"
0024 #if HAVE_X11
0025 #include <KX11Extras>
0026 #include <private/qtx11extras_p.h>
0027 #endif
0028 #if HAVE_DBUS
0029 #include <QDBusConnectionInterface>
0030 #include <QDBusInterface>
0031 #endif // HAVE_DBUS
0032 
0033 #include <iostream>
0034 
0035 static QString startupId()
0036 {
0037     QString result;
0038     if (KWindowSystem::isPlatformWayland()) {
0039         result = qEnvironmentVariable("XDG_ACTIVATION_TOKEN");
0040         qunsetenv("XDG_ACTIVATION_TOKEN");
0041     } else if (KWindowSystem::isPlatformX11()) {
0042 #if HAVE_X11
0043         result = QString::fromUtf8(QX11Info::nextStartupId());
0044 #endif
0045     }
0046 
0047     return result;
0048 }
0049 
0050 static bool attachUniqueInstance(const QStringList &paths, const QString &serializedOptions)
0051 {
0052 #if HAVE_DBUS
0053     if (!ShellUtils::unique(serializedOptions) || paths.count() != 1) {
0054         return false;
0055     }
0056 
0057     QDBusInterface iface(QStringLiteral("org.kde.okular"), QStringLiteral("/okularshell"), QStringLiteral("org.kde.okular"));
0058     if (!iface.isValid()) {
0059         return false;
0060     }
0061 
0062     if (!ShellUtils::editorCmd(serializedOptions).isEmpty()) {
0063         QString message =
0064             i18n("You cannot set the editor command in an already running okular instance. Please disable the tabs and try again. Please note, that unique is also not supported when setting the editor command at the commandline.\n");
0065         std::cerr << message.toStdString();
0066         exit(1);
0067     }
0068 
0069     const QString page = ShellUtils::page(serializedOptions);
0070     iface.call(QStringLiteral("openDocument"), ShellUtils::urlFromArg(paths[0], ShellUtils::qfileExistFunc(), page).url(), serializedOptions);
0071     if (!ShellUtils::noRaise(serializedOptions)) {
0072         iface.call(QStringLiteral("tryRaise"), startupId());
0073     }
0074 
0075     return true;
0076 #else  // HAVE_DBUS
0077     return false;
0078 #endif // HAVE_DBUS
0079 }
0080 
0081 // Ask an existing non-unique instance to open new tabs
0082 static bool attachExistingInstance(const QStringList &paths, const QString &serializedOptions)
0083 {
0084 #if HAVE_DBUS
0085     if (paths.count() < 1) {
0086         return false;
0087     }
0088 
0089     // Don't try to attach to an existing instance with --print-and-exit because that would mean
0090     // we're going to exit that other instance and that's just rude
0091     if (ShellUtils::showPrintDialogAndExit(serializedOptions)) {
0092         return false;
0093     }
0094 
0095     // If DBus isn't running, we can't attach to an existing instance.
0096     auto *sessionInterface = QDBusConnection::sessionBus().interface();
0097     if (!sessionInterface) {
0098         return false;
0099     }
0100 
0101     const QStringList services = sessionInterface->registeredServiceNames().value();
0102 
0103     // Don't match the service without trailing "-" (unique instance)
0104     const QString pattern = QStringLiteral("org.kde.okular-");
0105     const QString myPid = QString::number(qApp->applicationPid());
0106     QScopedPointer<QDBusInterface> bestService;
0107 #if HAVE_X11
0108     const int desktop = KX11Extras::currentDesktop();
0109 #else
0110     const int desktop = 0;
0111 #endif
0112 
0113     // Select the first instance that isn't us (metric may change in future)
0114     for (const QString &service : services) {
0115         if (service.startsWith(pattern) && !service.endsWith(myPid)) {
0116             bestService.reset(new QDBusInterface(service, QStringLiteral("/okularshell"), QStringLiteral("org.kde.okular")));
0117 
0118             // Find a window that can handle our documents
0119             const QDBusReply<bool> reply = bestService->call(QStringLiteral("canOpenDocs"), (int)paths.count(), desktop);
0120             if (reply.isValid() && reply.value()) {
0121                 break;
0122             }
0123 
0124             bestService.reset();
0125         }
0126     }
0127 
0128     if (!bestService) {
0129         return false;
0130     }
0131 
0132     for (const QString &arg : paths) {
0133         // Copy stdin to temporary file which can be opened by the existing
0134         // window. The temp file is automatically deleted after it has been
0135         // opened. Not sure if this behavior is safe on all platforms.
0136         QScopedPointer<QTemporaryFile> tempFile;
0137         QString path;
0138         if (arg == QLatin1String("-")) {
0139             tempFile.reset(new QTemporaryFile);
0140             QFile stdinFile;
0141             if (!tempFile->open() || !stdinFile.open(stdin, QIODevice::ReadOnly)) {
0142                 return false;
0143             }
0144 
0145             const size_t bufSize = 1024 * 1024;
0146             QScopedPointer<char, QScopedPointerArrayDeleter<char>> buf(new char[bufSize]);
0147             size_t bytes;
0148             do {
0149                 bytes = stdinFile.read(buf.data(), bufSize);
0150                 tempFile->write(buf.data(), bytes);
0151             } while (bytes != 0);
0152 
0153             path = tempFile->fileName();
0154         } else {
0155             // Page only makes sense if we are opening one file
0156             const QString page = ShellUtils::page(serializedOptions);
0157             path = ShellUtils::urlFromArg(arg, ShellUtils::qfileExistFunc(), page).url();
0158         }
0159 
0160         // Returns false if it can't fit another document
0161         const QDBusReply<bool> reply = bestService->call(QStringLiteral("openDocument"), path, serializedOptions);
0162         if (!reply.isValid() || !reply.value()) {
0163             return false;
0164         }
0165     }
0166 
0167     if (!ShellUtils::editorCmd(serializedOptions).isEmpty()) {
0168         QString message(
0169             i18n("You cannot set the editor command in an already running okular instance. Please disable the tabs and try again. Please note, that unique is also not supported when setting the editor command at the commandline.\n"));
0170         std::cerr << message.toStdString();
0171         exit(1);
0172     }
0173 
0174     bestService->call(QStringLiteral("tryRaise"), startupId());
0175 
0176     return true;
0177 #else  // HAVE_DBUS
0178     return false;
0179 #endif // HAVE_DBUS
0180 }
0181 
0182 namespace Okular
0183 {
0184 Status main(const QStringList &paths, const QString &serializedOptions)
0185 {
0186     if (ShellUtils::unique(serializedOptions) && paths.count() > 1) {
0187         QTextStream stream(stderr);
0188         stream << i18n("Error: Can't open more than one document with the --unique switch") << '\n';
0189         return Error;
0190     }
0191 
0192     if (ShellUtils::startInPresentation(serializedOptions) && paths.count() > 1) {
0193         QTextStream stream(stderr);
0194         stream << i18n("Error: Can't open more than one document with the --presentation switch") << '\n';
0195         return Error;
0196     }
0197 
0198     if (ShellUtils::showPrintDialog(serializedOptions) && paths.count() > 1) {
0199         QTextStream stream(stderr);
0200         stream << i18n("Error: Can't open more than one document with the --print switch") << '\n';
0201         return Error;
0202     }
0203 
0204     if (!ShellUtils::page(serializedOptions).isEmpty() && paths.count() > 1) {
0205         QTextStream stream(stderr);
0206         stream << i18n("Error: Can't open more than one document with the --page switch") << '\n';
0207         return Error;
0208     }
0209 
0210     if (!ShellUtils::find(serializedOptions).isEmpty() && paths.count() > 1) {
0211         QTextStream stream(stderr);
0212         stream << i18n("Error: Can't open more than one document with the --find switch") << '\n';
0213         return Error;
0214     }
0215 
0216     // try to attach to existing session, unique or not
0217     if (attachUniqueInstance(paths, serializedOptions) || attachExistingInstance(paths, serializedOptions)) {
0218         return AttachedOtherProcess;
0219     }
0220 
0221     Shell *shell = new Shell(serializedOptions);
0222     if (!shell->isValid()) {
0223         return Error;
0224     }
0225 
0226     shell->show();
0227     for (int i = 0; i < paths.count();) {
0228         // Page only makes sense if we are opening one file
0229         const QString page = ShellUtils::page(serializedOptions);
0230         const QUrl url = ShellUtils::urlFromArg(paths[i], ShellUtils::qfileExistFunc(), page);
0231         if (shell->openDocument(url, serializedOptions)) {
0232             ++i;
0233         } else {
0234             shell = new Shell(serializedOptions);
0235             if (!shell->isValid()) {
0236                 return Error;
0237             }
0238             shell->show();
0239         }
0240     }
0241 
0242     return Success;
0243 }
0244 
0245 }
0246 
0247 /* kate: replace-tabs on; indent-width 4; */