File indexing completed on 2024-05-19 05:38:00
0001 /* 0002 SPDX-FileCopyrightText: 2020 Méven Car <meven.car@enioka.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "autostartmodel.h" 0008 #include "kcm_autostart_debug.h" 0009 0010 #include <KConfigGroup> 0011 #include <KDesktopFile> 0012 #include <KSharedConfig> 0013 #include <KShell> 0014 #include <QDebug> 0015 #include <QQuickItem> 0016 #include <QQuickRenderControl> 0017 #include <QStandardPaths> 0018 #include <QWindow> 0019 0020 #include <QDBusMessage> 0021 #include <QDirIterator> 0022 #include <QFileIconProvider> 0023 #include <QFileInfo> 0024 #include <QMimeDatabase> 0025 #include <QRegularExpression> 0026 0027 #include <KFileItem> 0028 #include <KFileUtils> 0029 #include <KIO/CopyJob> 0030 #include <KIO/DeleteJob> 0031 #include <KLocalizedString> 0032 #include <KOpenWithDialog> 0033 #include <KPropertiesDialog> 0034 #include <autostartscriptdesktopfile.h> 0035 0036 using namespace Qt::StringLiterals; 0037 0038 // FDO user autostart directories are 0039 // .config/autostart which has .desktop files executed by klaunch or systemd, some of which might be scripts 0040 0041 // Then we have Plasma-specific locations which run scripts 0042 // .config/autostart-scripts which has scripts executed by plasma_session (now migrated to .desktop files) 0043 // .config/plasma-workspace/shutdown which has scripts executed by plasma-shutdown 0044 // .config/plasma-workspace/env which has scripts executed by startplasma 0045 0046 // in the case of pre-startup they have to end in .sh 0047 // everywhere else it doesn't matter 0048 0049 // the comment above describes how autostart *currently* works, it is not definitive documentation on how autostart *should* work 0050 0051 // share/autostart shouldn't be an option as this should be reserved for global autostart entries 0052 0053 std::optional<AutostartEntry> AutostartModel::loadDesktopEntry(const QString &fileName) 0054 { 0055 KDesktopFile config(fileName); 0056 const KConfigGroup grp = config.desktopGroup(); 0057 const auto name = config.readName(); 0058 const bool hidden = grp.readEntry("Hidden", false); 0059 0060 if (hidden) { 0061 return {}; 0062 } 0063 0064 const QStringList notShowList = grp.readXdgListEntry("NotShowIn"); 0065 const QStringList onlyShowList = grp.readXdgListEntry("OnlyShowIn"); 0066 const bool enabled = !(notShowList.contains(QLatin1String("KDE")) || (!onlyShowList.isEmpty() && !onlyShowList.contains(QLatin1String("KDE")))); 0067 0068 if (!enabled) { 0069 return {}; 0070 } 0071 0072 const auto lstEntry = grp.readXdgListEntry("OnlyShowIn"); 0073 const bool onlyInPlasma = lstEntry.contains(QLatin1String("KDE")); 0074 const QString iconName = !config.readIcon().isEmpty() ? config.readIcon() : QStringLiteral("dialog-scripts"); 0075 const auto kind = AutostartScriptDesktopFile::isAutostartScript(config) ? XdgScripts : XdgAutoStart; // .config/autostart load desktop at startup 0076 const QString tryCommand = grp.readEntry("TryExec"); 0077 0078 // Try to filter out entries that point to nonexistant programs 0079 // If TryExec is either found in $PATH or is an absolute file path that exists 0080 // This doesn't detect uninstalled Flatpaks for example though 0081 if (!tryCommand.isEmpty() && QStandardPaths::findExecutable(tryCommand).isEmpty() && !QFile::exists(tryCommand)) { 0082 return {}; 0083 } 0084 0085 if (kind == XdgScripts) { 0086 const QString targetScriptPath = grp.readEntry("Exec"); 0087 const QString targetFileName = QUrl::fromLocalFile(targetScriptPath).fileName(); 0088 const QString targetScriptDir = QFileInfo(targetScriptPath).absoluteDir().path(); 0089 0090 return AutostartEntry{targetFileName, targetScriptDir, kind, enabled, fileName, onlyInPlasma, iconName}; 0091 } 0092 return AutostartEntry{name, name, kind, enabled, fileName, onlyInPlasma, iconName}; 0093 } 0094 0095 static const QString FALLBACK_ICON = QStringLiteral("application-x-executable-script"); 0096 0097 AutostartModel::AutostartModel(QObject *parent) 0098 : QAbstractListModel(parent) 0099 , m_xdgConfigPath(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)) 0100 , m_xdgAutoStartPath(m_xdgConfigPath.filePath(QStringLiteral("autostart"))) 0101 { 0102 auto message = QDBusMessage::createMethodCall("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Subscribe"); 0103 QDBusConnection::sessionBus().send(message); 0104 } 0105 0106 AutostartModel::~AutostartModel() 0107 { 0108 } 0109 0110 void AutostartModel::load() 0111 { 0112 beginResetModel(); 0113 0114 m_entries.clear(); 0115 0116 // Creates if doesn't already exist 0117 m_xdgAutoStartPath.mkpath(QStringLiteral(".")); 0118 0119 // Needed to add all script entries after application entries 0120 QList<AutostartEntry> scriptEntries; 0121 const auto filesInfo = m_xdgAutoStartPath.entryInfoList(QDir::Files); 0122 for (const QFileInfo &fi : filesInfo) { 0123 if (!KDesktopFile::isDesktopFile(fi.fileName())) { 0124 continue; 0125 } 0126 0127 const std::optional<AutostartEntry> entry = loadDesktopEntry(fi.absoluteFilePath()); 0128 0129 if (!entry) { 0130 continue; 0131 } 0132 0133 if (entry->source == XdgScripts) { 0134 scriptEntries.push_back(entry.value()); 0135 } else { 0136 m_entries.push_back(entry.value()); 0137 } 0138 } 0139 0140 m_entries.append(scriptEntries); 0141 0142 loadScriptsFromDir(QStringLiteral("plasma-workspace/env/"), AutostartModel::AutostartEntrySource::PlasmaEnvScripts); 0143 0144 loadScriptsFromDir(QStringLiteral("plasma-workspace/shutdown/"), AutostartModel::AutostartEntrySource::PlasmaShutdown); 0145 0146 // Add unit objects for entries and set id to them 0147 for (AutostartEntry &entry : m_entries) { 0148 if (entry.source == AutostartModel::AutostartEntrySource::PlasmaShutdown || entry.source == AutostartModel::AutostartEntrySource::PlasmaEnvScripts) { 0149 continue; 0150 } 0151 0152 const QUrl url{entry.fileName}; 0153 QString actualName = url.fileName(); 0154 // Remove .desktop part 0155 actualName.chop(8); 0156 const QString serviceName = QStringLiteral("app-") + systemdEscape(actualName) + QStringLiteral("@autostart.service"); 0157 auto unit = new Unit(this); 0158 // To show errors that occur when loading unit data in main page 0159 connect(unit, &Unit::error, this, &AutostartModel::error); 0160 unit->setId(serviceName); 0161 entry.systemdUnit = unit; 0162 } 0163 endResetModel(); 0164 } 0165 0166 // Returns if systemd is available and systemdBoot is enabled. It used to determine if the autostart entries should be clickable in qml 0167 bool AutostartModel::usingSystemdBoot() const 0168 { 0169 if (!haveSystemd) { 0170 return false; 0171 } 0172 const KSharedConfig::Ptr config = KSharedConfig::openConfig(u"startkderc"_s); 0173 const KConfigGroup generalGroup(config, u"General"_s); 0174 return generalGroup.readEntry("systemdBoot", true); 0175 } 0176 0177 QString AutostartModel::systemdEscape(const QString &name) const 0178 { 0179 QString newName = name; 0180 newName.replace(QLatin1Char('-'), QLatin1String("\\x2d")); 0181 newName.replace(QLatin1Char('/'), QLatin1String("\\xe2\\x81\\x84")); 0182 return newName; 0183 } 0184 0185 void AutostartModel::loadScriptsFromDir(const QString &subDir, AutostartModel::AutostartEntrySource kind) 0186 { 0187 QDir dir(m_xdgConfigPath.filePath(subDir)); 0188 // Creates if doesn't already exist 0189 dir.mkpath(QStringLiteral(".")); 0190 0191 const auto autostartDirFilesInfo = dir.entryInfoList(QDir::Files); 0192 for (const QFileInfo &fi : autostartDirFilesInfo) { 0193 QString targetFileDir = fi.absoluteDir().path(); 0194 QString fileName = fi.fileName(); 0195 QString iconName; 0196 const bool isSymlink = fi.isSymLink(); 0197 0198 // logout scripts are saved as symlinks 0199 if (isSymlink) { 0200 QFileInfo symLinkTarget(fi.symLinkTarget()); 0201 iconName = m_iconProvider.icon(symLinkTarget).name(); 0202 targetFileDir = symLinkTarget.absoluteDir().path(); 0203 fileName = symLinkTarget.fileName(); 0204 } else { 0205 iconName = m_iconProvider.icon(fi).name(); 0206 } 0207 0208 iconName = iconName == QString("text-plain") ? FALLBACK_ICON : iconName; 0209 m_entries.push_back({fileName, targetFileDir, kind, true, fi.absoluteFilePath(), false, iconName}); 0210 } 0211 } 0212 0213 int AutostartModel::rowCount(const QModelIndex &parent) const 0214 { 0215 if (parent.isValid()) { 0216 return 0; 0217 } 0218 0219 return m_entries.count(); 0220 } 0221 0222 bool AutostartModel::reloadEntry(const QModelIndex &index, const QString &fileName) 0223 { 0224 if (!checkIndex(index)) { 0225 return false; 0226 } 0227 0228 const std::optional<AutostartEntry> newEntry = loadDesktopEntry(fileName); 0229 0230 if (!newEntry) { 0231 return false; 0232 } 0233 0234 m_entries.replace(index.row(), newEntry.value()); 0235 Q_EMIT dataChanged(index, index); 0236 return true; 0237 } 0238 0239 QVariant AutostartModel::data(const QModelIndex &index, int role) const 0240 { 0241 if (!checkIndex(index)) { 0242 return QVariant(); 0243 } 0244 0245 const auto &entry = m_entries.at(index.row()); 0246 0247 switch (role) { 0248 case Name: 0249 return entry.name; 0250 case Enabled: 0251 return entry.enabled; 0252 case Source: 0253 return entry.source; 0254 case FileName: 0255 return entry.fileName; 0256 case OnlyInPlasma: 0257 return entry.onlyInPlasma; 0258 case IconName: 0259 return entry.iconName; 0260 case TargetFileDirPath: 0261 return entry.targetFileDirPath; 0262 case SystemdUnit: 0263 return QVariant::fromValue(entry.systemdUnit); 0264 } 0265 0266 return QVariant(); 0267 } 0268 0269 void AutostartModel::addApplication(const KService::Ptr &service) 0270 { 0271 QString desktopPath; 0272 // It is important to ensure that we make an exact copy of an existing 0273 // desktop file (if selected) to enable users to override global autostarts. 0274 // Also see 0275 // https://bugs.launchpad.net/ubuntu/+source/kde-workspace/+bug/923360 0276 if (service->desktopEntryName().isEmpty() || service->entryPath().isEmpty()) { 0277 // create a new desktop file in s_desktopPath 0278 desktopPath = m_xdgAutoStartPath.filePath(service->name() + QStringLiteral(".desktop")); 0279 0280 if (QFileInfo::exists(desktopPath)) { 0281 QUrl baseUrl = QUrl::fromLocalFile(m_xdgAutoStartPath.path()); 0282 QString newName = suggestName(baseUrl, service->name() + QStringLiteral(".desktop")); 0283 desktopPath = m_xdgAutoStartPath.filePath(newName); 0284 } 0285 0286 KDesktopFile desktopFile(desktopPath); 0287 KConfigGroup kcg = desktopFile.desktopGroup(); 0288 kcg.writeEntry("Name", service->name()); 0289 kcg.writeEntry("Exec", service->exec()); 0290 kcg.writeEntry("Icon", service->icon()); 0291 kcg.writeEntry("Path", ""); 0292 kcg.writeEntry("Terminal", service->terminal() ? "True" : "False"); 0293 kcg.writeEntry("Type", "Application"); 0294 desktopFile.sync(); 0295 0296 } else { 0297 desktopPath = m_xdgAutoStartPath.filePath(service->storageId()); 0298 0299 KDesktopFile desktopFile(service->entryPath()); 0300 0301 if (QFileInfo::exists(desktopPath)) { 0302 QUrl baseUrl = QUrl::fromLocalFile(m_xdgAutoStartPath.path()); 0303 QString newName = suggestName(baseUrl, service->storageId()); 0304 desktopPath = m_xdgAutoStartPath.filePath(newName); 0305 } 0306 0307 // copy original desktop file to new path 0308 auto newDesktopFile = desktopFile.copyTo(desktopPath); 0309 newDesktopFile->sync(); 0310 } 0311 0312 const QString iconName = !service->icon().isEmpty() ? service->icon() : FALLBACK_ICON; 0313 Unit *unit = new Unit(this, true); 0314 const auto entry = AutostartEntry{service->name(), 0315 service->name(), 0316 AutostartModel::AutostartEntrySource::XdgAutoStart, // .config/autostart load desktop at startup 0317 true, 0318 desktopPath, 0319 false, 0320 iconName, 0321 unit}; 0322 0323 int lastApplication = -1; 0324 for (const AutostartEntry &e : std::as_const(m_entries)) { 0325 if (e.source == AutostartModel::AutostartEntrySource::XdgScripts) { 0326 break; 0327 } 0328 ++lastApplication; 0329 } 0330 0331 // push before the script items 0332 const int index = lastApplication + 1; 0333 0334 beginInsertRows(QModelIndex(), index, index); 0335 0336 m_entries.insert(index, entry); 0337 0338 endInsertRows(); 0339 } 0340 0341 void AutostartModel::showApplicationDialog(QQuickItem *context) 0342 { 0343 KOpenWithDialog *owdlg = new KOpenWithDialog(); 0344 owdlg->setAttribute(Qt::WA_DeleteOnClose); 0345 0346 if (context && context->window()) { 0347 if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(context->window())) { 0348 owdlg->winId(); // so it creates windowHandle 0349 owdlg->windowHandle()->setTransientParent(actualWindow); 0350 owdlg->setModal(true); 0351 } 0352 } 0353 0354 connect(owdlg, &QDialog::finished, this, [this, owdlg](int result) { 0355 if (result != QDialog::Accepted) { 0356 return; 0357 } 0358 0359 const KService::Ptr service = owdlg->service(); 0360 0361 Q_ASSERT(service); 0362 if (!service) { 0363 return; // Don't crash if KOpenWith wasn't able to create service. 0364 } 0365 0366 addApplication(service); 0367 }); 0368 owdlg->open(); 0369 } 0370 0371 void AutostartModel::addScript(const QUrl &url, AutostartModel::AutostartEntrySource kind) 0372 { 0373 const QFileInfo file(url.toLocalFile()); 0374 0375 if (!file.isAbsolute()) { 0376 Q_EMIT error(i18n("\"%1\" is not an absolute url.", url.toLocalFile())); 0377 return; 0378 } else if (!file.exists()) { 0379 Q_EMIT error(i18n("\"%1\" does not exist.", url.toLocalFile())); 0380 return; 0381 } else if (!file.isFile()) { 0382 Q_EMIT error(i18n("\"%1\" is not a file.", url.toLocalFile())); 0383 return; 0384 } else if (!file.isReadable()) { 0385 Q_EMIT error(i18n("\"%1\" is not readable.", url.toLocalFile())); 0386 return; 0387 } 0388 0389 QFile scriptFile(url.toLocalFile()); 0390 0391 if (!(scriptFile.permissions() & QFile::ExeUser)) { 0392 Q_EMIT nonExecutableScript(url.toLocalFile(), kind); 0393 } 0394 0395 QString fileName = url.fileName(); 0396 0397 if (kind == AutostartModel::AutostartEntrySource::XdgScripts) { 0398 int lastLoginScript = -1; 0399 for (const AutostartEntry &e : std::as_const(m_entries)) { 0400 if (e.source == AutostartModel::AutostartEntrySource::PlasmaShutdown) { 0401 break; 0402 } 0403 ++lastLoginScript; 0404 } 0405 0406 // path of the desktop file that is about to be created 0407 const QString newFilePath = m_xdgAutoStartPath.absoluteFilePath(fileName + QStringLiteral(".desktop")); 0408 0409 QIcon icon = m_iconProvider.icon(file); 0410 QString iconName = icon.name() == QString("text-plain") ? FALLBACK_ICON : icon.name(); 0411 if (QFileInfo::exists(newFilePath)) { 0412 const QUrl baseUrl = QUrl::fromLocalFile(m_xdgAutoStartPath.path()); 0413 fileName = suggestName(baseUrl, fileName + QStringLiteral(".desktop")); 0414 0415 // remove the .desktop part from String 0416 fileName.chop(8); 0417 } 0418 AutostartScriptDesktopFile desktopFile(fileName, KShell::quoteArg(file.filePath()), iconName); 0419 insertScriptEntry(lastLoginScript + 1, file.fileName(), file.absoluteDir().path(), desktopFile.fileName(), kind); 0420 0421 } else if (kind == AutostartModel::AutostartEntrySource::PlasmaShutdown) { 0422 const QUrl destinationScript = QUrl::fromLocalFile(QDir(m_xdgConfigPath.filePath(QStringLiteral("plasma-workspace/shutdown/"))).filePath(fileName)); 0423 KIO::CopyJob *job = KIO::link(url, destinationScript, KIO::HideProgressInfo); 0424 job->setAutoRename(true); 0425 job->setProperty("finalUrl", destinationScript); 0426 0427 connect(job, &KIO::CopyJob::renamed, this, [](KIO::Job *job, const QUrl &from, const QUrl &to) { 0428 Q_UNUSED(from) 0429 // in case the destination filename had to be renamed 0430 job->setProperty("finalUrl", to); 0431 }); 0432 0433 connect(job, &KJob::finished, this, [this, url, kind](KJob *theJob) { 0434 if (theJob->error()) { 0435 qCWarning(KCM_AUTOSTART_DEBUG) << "Could not add script entry" << theJob->errorString(); 0436 return; 0437 } 0438 const QUrl dest = theJob->property("finalUrl").toUrl(); 0439 const QFileInfo destFile(dest.path()); 0440 const QString symLinkFileName = QUrl::fromLocalFile(destFile.symLinkTarget()).fileName(); 0441 const QFileInfo symLinkTarget{destFile.symLinkTarget()}; 0442 const QString symLinkTargetDir = symLinkTarget.absoluteDir().path(); 0443 insertScriptEntry(m_entries.size(), symLinkFileName, symLinkTargetDir, dest.path(), kind); 0444 }); 0445 0446 job->start(); 0447 } else { 0448 Q_ASSERT(0); 0449 } 0450 } 0451 0452 void AutostartModel::insertScriptEntry(int index, const QString &name, const QString &targetFileDirPath, const QString &path, AutostartEntrySource kind) 0453 { 0454 beginInsertRows(QModelIndex(), index, index); 0455 QFileInfo targetFile{QDir(targetFileDirPath).filePath(name)}; 0456 const QIcon icon = m_iconProvider.icon(targetFile); 0457 const QString iconName = icon.name() == QString("text-plain") ? FALLBACK_ICON : icon.name(); 0458 0459 Unit *unit = new Unit(this, true); 0460 0461 // Plasma shutdown and Plasma env scripts don't have units 0462 if (kind == AutostartModel::AutostartEntrySource::PlasmaShutdown || kind == AutostartModel::AutostartEntrySource::PlasmaEnvScripts) { 0463 delete unit; 0464 unit = nullptr; 0465 } 0466 0467 AutostartEntry entry = AutostartEntry{name, targetFileDirPath, kind, true, path, false, iconName, unit}; 0468 0469 m_entries.insert(index, entry); 0470 0471 endInsertRows(); 0472 } 0473 0474 void AutostartModel::removeEntry(int row) 0475 { 0476 const auto entry = m_entries.at(row); 0477 0478 KIO::DeleteJob *job = KIO::del(QUrl::fromLocalFile(entry.fileName), KIO::HideProgressInfo); 0479 0480 connect(job, &KJob::finished, this, [this, row, entry](KJob *theJob) { 0481 if (theJob->error()) { 0482 qCWarning(KCM_AUTOSTART_DEBUG) << "Could not remove entry" << theJob->errorString(); 0483 return; 0484 } 0485 0486 beginRemoveRows(QModelIndex(), row, row); 0487 m_entries.remove(row); 0488 delete entry.systemdUnit; 0489 0490 endRemoveRows(); 0491 }); 0492 0493 job->start(); 0494 } 0495 0496 QHash<int, QByteArray> AutostartModel::roleNames() const 0497 { 0498 QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames(); 0499 0500 roleNames.insert(Name, QByteArrayLiteral("name")); 0501 roleNames.insert(Enabled, QByteArrayLiteral("enabled")); 0502 roleNames.insert(Source, QByteArrayLiteral("source")); 0503 roleNames.insert(FileName, QByteArrayLiteral("fileName")); 0504 roleNames.insert(OnlyInPlasma, QByteArrayLiteral("onlyInPlasma")); 0505 roleNames.insert(IconName, QByteArrayLiteral("iconName")); 0506 roleNames.insert(TargetFileDirPath, QByteArrayLiteral("targetFileDirPath")); 0507 roleNames.insert(SystemdUnit, QByteArrayLiteral("systemdUnit")); 0508 0509 return roleNames; 0510 } 0511 0512 void AutostartModel::editApplication(int row, QQuickItem *context) 0513 { 0514 const QModelIndex idx = index(row, 0); 0515 0516 const QString fileName = data(idx, AutostartModel::Roles::FileName).toString(); 0517 KFileItem kfi(QUrl::fromLocalFile(fileName)); 0518 kfi.setDelayedMimeTypes(true); 0519 0520 KPropertiesDialog *dlg = new KPropertiesDialog(kfi, nullptr); 0521 dlg->setAttribute(Qt::WA_DeleteOnClose); 0522 0523 if (context && context->window()) { 0524 if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(context->window())) { 0525 dlg->winId(); // so it creates windowHandle 0526 dlg->windowHandle()->setTransientParent(actualWindow); 0527 dlg->setModal(true); 0528 } 0529 } 0530 0531 connect(dlg, &QDialog::finished, this, [this, idx, dlg](int result) { 0532 if (result == QDialog::Accepted) { 0533 reloadEntry(idx, dlg->item().localPath()); 0534 } 0535 }); 0536 dlg->open(); 0537 } 0538 0539 void AutostartModel::makeFileExecutable(const QString &fileName) 0540 { 0541 QFile file(fileName); 0542 0543 file.setPermissions(file.permissions() | QFile::ExeUser); 0544 } 0545 0546 // Use slightly modified code copied from frameworks KFileUtils because desktop filenames cannot contain '(' or ' '. 0547 QString AutostartModel::makeSuggestedName(const QString &oldName) 0548 { 0549 QString basename; 0550 0551 // Extract the original file extension from the filename 0552 QMimeDatabase db; 0553 QString nameSuffix = db.suffixForFileName(oldName); 0554 0555 if (oldName.lastIndexOf(QLatin1Char('.')) == 0) { 0556 basename = QStringLiteral("."); 0557 nameSuffix = oldName; 0558 } else if (nameSuffix.isEmpty()) { 0559 const int lastDot = oldName.lastIndexOf(QLatin1Char('.')); 0560 if (lastDot == -1) { 0561 basename = oldName; 0562 } else { 0563 basename = oldName.left(lastDot); 0564 nameSuffix = oldName.mid(lastDot); 0565 } 0566 } else { 0567 nameSuffix.prepend(QLatin1Char('.')); 0568 basename = oldName.left(oldName.length() - nameSuffix.length()); 0569 } 0570 0571 // check if (number) exists at the end of the oldName and increment that number 0572 const QRegularExpression re(QStringLiteral("_(\\d+)_")); 0573 QRegularExpressionMatch rmatch; 0574 (void)oldName.lastIndexOf(re, -1, &rmatch); 0575 if (rmatch.hasMatch()) { 0576 const int currentNum = rmatch.captured(1).toInt(); 0577 const QString number = QString::number(currentNum + 1); 0578 basename.replace(rmatch.capturedStart(1), rmatch.capturedLength(1), number); 0579 } else { 0580 // number does not exist, so just append " _1_" to filename 0581 basename += QLatin1String("_1_"); 0582 } 0583 0584 return basename + nameSuffix; 0585 } 0586 0587 QString AutostartModel::suggestName(const QUrl &baseURL, const QString &oldName) 0588 { 0589 QString suggestedName = makeSuggestedName(oldName); 0590 0591 if (baseURL.isLocalFile()) { 0592 const QString basePath = baseURL.toLocalFile() + QLatin1Char('/'); 0593 while (QFileInfo::exists(basePath + suggestedName)) { 0594 suggestedName = makeSuggestedName(suggestedName); 0595 } 0596 } 0597 0598 return suggestedName; 0599 }