File indexing completed on 2025-02-02 14:16:01
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org> 0004 SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #include "kdesktopfileactions.h" 0010 0011 #include "kio_widgets_debug.h" 0012 0013 #include "kautomount.h" 0014 #include "krun.h" 0015 #include <KDialogJobUiDelegate> 0016 #include <KIO/ApplicationLauncherJob> 0017 #include <KMessageBox> 0018 #include <kdirnotify.h> 0019 #include <kmountpoint.h> 0020 0021 #include <KConfigGroup> 0022 #include <KDesktopFile> 0023 #include <KLocalizedString> 0024 #include <KService> 0025 0026 #ifndef KIO_ANDROID_STUB 0027 #include <QDBusInterface> 0028 #include <QDBusReply> 0029 #endif 0030 0031 enum BuiltinServiceType { ST_MOUNT = 0x0E1B05B0, ST_UNMOUNT = 0x0E1B05B1 }; // random numbers 0032 0033 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71) 0034 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn); 0035 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn); 0036 #endif 0037 0038 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71) 0039 bool KDesktopFileActions::run(const QUrl &u, bool _is_local) 0040 { 0041 return runWithStartup(u, _is_local, QByteArray()); 0042 } 0043 #endif 0044 0045 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71) 0046 bool KDesktopFileActions::runWithStartup(const QUrl &u, bool _is_local, const QByteArray &asn) 0047 { 0048 // It might be a security problem to run external untrusted desktop 0049 // entry files 0050 if (!_is_local) { 0051 return false; 0052 } 0053 0054 if (u.fileName() == QLatin1String(".directory")) { 0055 // We cannot execute a .directory file. Open with a text editor instead. 0056 return KRun::runUrl(u, QStringLiteral("text/plain"), nullptr, KRun::RunFlags(), QString(), asn); 0057 } 0058 0059 KDesktopFile cfg(u.toLocalFile()); 0060 if (!cfg.desktopGroup().hasKey("Type")) { 0061 KMessageBox::error(nullptr, i18n("The desktop entry file %1 has no Type=... entry.", u.toLocalFile())); 0062 return false; 0063 } 0064 0065 // qDebug() << "TYPE = " << type.data(); 0066 0067 if (cfg.hasDeviceType()) { 0068 return runFSDevice(u, cfg, asn); 0069 } else if (cfg.hasApplicationType() 0070 || (cfg.readType() == QLatin1String("Service") && !cfg.desktopGroup().readEntry("Exec").isEmpty())) { // for kio_settings 0071 KService service(u.toLocalFile()); 0072 return KRun::runApplication(service, QList<QUrl>(), nullptr /*TODO - window*/, KRun::RunFlags{}, QString(), asn); 0073 } else if (cfg.hasLinkType()) { 0074 return runLink(u, cfg, asn); 0075 } 0076 0077 QString tmp = i18n("The desktop entry of type\n%1\nis unknown.", cfg.readType()); 0078 KMessageBox::error(nullptr, tmp); 0079 0080 return false; 0081 } 0082 #endif 0083 0084 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71) 0085 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn) 0086 { 0087 bool retval = false; 0088 0089 QT_WARNING_PUSH 0090 QT_WARNING_DISABLE_DEPRECATED 0091 QString dev = cfg.readDevice(); 0092 QT_WARNING_POP 0093 0094 if (dev.isEmpty()) { 0095 QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile()); 0096 KMessageBox::error(nullptr, tmp); 0097 return retval; 0098 } 0099 0100 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev); 0101 // Is the device already mounted ? 0102 if (mp) { 0103 const QUrl mpURL = QUrl::fromLocalFile(mp->mountPoint()); 0104 // Open a new window 0105 retval = KRun::runUrl(mpURL, QStringLiteral("inode/directory"), nullptr /*TODO - window*/, KRun::RunFlags(KRun::RunExecutables), QString(), asn); 0106 } else { 0107 KConfigGroup cg = cfg.desktopGroup(); 0108 bool ro = cg.readEntry("ReadOnly", false); 0109 QString fstype = cg.readEntry("FSType"); 0110 if (fstype == QLatin1String("Default")) { // KDE-1 thing 0111 fstype.clear(); 0112 } 0113 QString point = cg.readEntry("MountPoint"); 0114 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) 0115 (void)new KAutoMount(ro, fstype.toLatin1(), dev, point, _url.toLocalFile()); 0116 #endif 0117 retval = false; 0118 } 0119 0120 return retval; 0121 } 0122 #endif 0123 0124 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71) 0125 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn) 0126 { 0127 QString u = cfg.readUrl(); 0128 if (u.isEmpty()) { 0129 QString tmp = i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", _url.toString()); 0130 KMessageBox::error(nullptr, tmp); 0131 return false; 0132 } 0133 0134 QUrl url = QUrl::fromUserInput(u); 0135 KRun *run = new KRun(url, (QWidget *)nullptr, true, asn); 0136 0137 // X-KDE-LastOpenedWith holds the service desktop entry name that 0138 // should be preferred for opening this URL if possible. 0139 // This is used by the Recent Documents menu for instance. 0140 QString lastOpenedWidth = cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith"); 0141 if (!lastOpenedWidth.isEmpty()) { 0142 run->setPreferredService(lastOpenedWidth); 0143 } 0144 0145 return false; 0146 } 0147 #endif 0148 0149 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 82) 0150 QList<KServiceAction> KDesktopFileActions::builtinServices(const QUrl &_url) 0151 { 0152 QList<KServiceAction> result; 0153 0154 if (!_url.isLocalFile()) { 0155 return result; 0156 } 0157 0158 bool offerMount = false; 0159 bool offerUnmount = false; 0160 0161 KDesktopFile cfg(_url.toLocalFile()); 0162 if (cfg.hasDeviceType()) { // url to desktop file 0163 0164 QT_WARNING_PUSH 0165 QT_WARNING_DISABLE_DEPRECATED 0166 const QString dev = cfg.readDevice(); 0167 QT_WARNING_POP 0168 0169 if (dev.isEmpty()) { 0170 QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile()); 0171 KMessageBox::error(nullptr, tmp); 0172 return result; 0173 } 0174 0175 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev); 0176 if (mp) { 0177 offerUnmount = true; 0178 } else { 0179 offerMount = true; 0180 } 0181 } 0182 0183 if (offerMount) { 0184 KServiceAction mount(QStringLiteral("mount"), i18n("Mount"), QString(), QString(), false, {}); 0185 mount.setData(QVariant(ST_MOUNT)); 0186 result.append(mount); 0187 } 0188 0189 if (offerUnmount) { 0190 KServiceAction unmount(QStringLiteral("unmount"), i18n("Unmount"), QString(), QString(), false, {}); 0191 unmount.setData(QVariant(ST_UNMOUNT)); 0192 result.append(unmount); 0193 } 0194 0195 return result; 0196 } 0197 #endif 0198 0199 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86) 0200 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const QString &path, bool bLocalFiles) 0201 { 0202 KService service(path); 0203 return userDefinedServices(service, bLocalFiles); 0204 } 0205 #endif 0206 0207 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86) 0208 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const QString &path, const KDesktopFile &cfg, bool bLocalFiles, const QList<QUrl> &file_list) 0209 { 0210 Q_UNUSED(path); // this was just for debugging; we use service.entryPath() now. 0211 KService service(&cfg); 0212 return userDefinedServices(service, bLocalFiles, file_list); 0213 } 0214 #endif 0215 0216 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const KService &service, bool bLocalFiles, const QList<QUrl> &file_list) 0217 { 0218 QList<KServiceAction> result; 0219 0220 if (!service.isValid()) { // e.g. TryExec failed 0221 return result; 0222 } 0223 0224 QStringList keys; 0225 const QString actionMenu = service.property(QStringLiteral("X-KDE-GetActionMenu"), QMetaType::QString).toString(); 0226 if (!actionMenu.isEmpty()) { 0227 const QStringList dbuscall = actionMenu.split(QLatin1Char(' ')); 0228 if (dbuscall.count() >= 4) { 0229 const QString &app = dbuscall.at(0); 0230 const QString &object = dbuscall.at(1); 0231 const QString &interface = dbuscall.at(2); 0232 const QString &function = dbuscall.at(3); 0233 0234 #ifndef KIO_ANDROID_STUB 0235 QDBusInterface remote(app, object, interface); 0236 // Do NOT use QDBus::BlockWithGui here. It runs a nested event loop, 0237 // in which timers can fire, leading to crashes like #149736. 0238 QDBusReply<QStringList> reply = remote.call(function, QUrl::toStringList(file_list)); 0239 keys = reply; // ensures that the reply was a QStringList 0240 if (keys.isEmpty()) { 0241 return result; 0242 } 0243 #endif 0244 } else { 0245 qCWarning(KIO_WIDGETS) << "The desktop file" << service.entryPath() << "has an invalid X-KDE-GetActionMenu entry." 0246 << "Syntax is: app object interface function"; 0247 } 0248 } 0249 0250 // Now, either keys is empty (all actions) or it's set to the actions we want 0251 0252 const QList<KServiceAction> list = service.actions(); 0253 for (const KServiceAction &action : list) { 0254 if (keys.isEmpty() || keys.contains(action.name())) { 0255 const QString exec = action.exec(); 0256 if (bLocalFiles || exec.contains(QLatin1String("%U")) || exec.contains(QLatin1String("%u"))) { 0257 result.append(action); 0258 } 0259 } 0260 } 0261 0262 return result; 0263 } 0264 0265 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 84) 0266 void KDesktopFileActions::executeService(const QList<QUrl> &urls, const KServiceAction &action) 0267 { 0268 // qDebug() << "EXECUTING Service " << action.name(); 0269 0270 int actionData = action.data().toInt(); 0271 if (actionData == ST_MOUNT || actionData == ST_UNMOUNT) { 0272 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 82) 0273 Q_ASSERT(urls.count() == 1); 0274 const QString path = urls.first().toLocalFile(); 0275 // qDebug() << "MOUNT&UNMOUNT"; 0276 0277 KDesktopFile cfg(path); 0278 if (cfg.hasDeviceType()) { // path to desktop file 0279 QT_WARNING_PUSH 0280 QT_WARNING_DISABLE_DEPRECATED 0281 const QString dev = cfg.readDevice(); 0282 QT_WARNING_POP 0283 0284 if (dev.isEmpty()) { 0285 QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", path); 0286 KMessageBox::error(nullptr /*TODO window*/, tmp); 0287 return; 0288 } 0289 KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev); 0290 0291 if (actionData == ST_MOUNT) { 0292 // Already mounted? Strange, but who knows ... 0293 if (mp) { 0294 // qDebug() << "ALREADY Mounted"; 0295 return; 0296 } 0297 0298 const KConfigGroup group = cfg.desktopGroup(); 0299 bool ro = group.readEntry("ReadOnly", false); 0300 QString fstype = group.readEntry("FSType"); 0301 if (fstype == QLatin1String("Default")) { // KDE-1 thing 0302 fstype.clear(); 0303 } 0304 QString point = group.readEntry("MountPoint"); 0305 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) 0306 (void)new KAutoMount(ro, fstype.toLatin1(), dev, point, path, false); 0307 #endif 0308 } else if (actionData == ST_UNMOUNT) { 0309 // Not mounted? Strange, but who knows ... 0310 if (!mp) { 0311 return; 0312 } 0313 0314 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID) 0315 (void)new KAutoUnmount(mp->mountPoint(), path); 0316 #endif 0317 } 0318 } 0319 #endif // deprecated since 5.82 0320 } else { 0321 KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(action); 0322 job->setUrls(urls); 0323 QObject::connect(job, &KJob::result, qApp, [urls]() { 0324 // The action may update the desktop file. Example: eject unmounts (#5129). 0325 #ifndef KIO_ANDROID_STUB 0326 org::kde::KDirNotify::emitFilesChanged(urls); 0327 #endif 0328 }); 0329 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr /*TODO window*/)); 0330 job->start(); 0331 } 0332 } 0333 #endif