File indexing completed on 2024-05-19 04:56:13
0001 /** 0002 * \file coreplatformtools.cpp 0003 * Core platform specific tools for Qt. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 10 Aug 2013 0008 * 0009 * Copyright (C) 2013-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "coreplatformtools.h" 0028 #include <QFileInfo> 0029 #include <QSettings> 0030 #include <QCoreApplication> 0031 #include "config.h" 0032 #include "kid3settings.h" 0033 #include "coretaggedfileiconprovider.h" 0034 0035 /** 0036 * Constructor. 0037 */ 0038 CorePlatformTools::CorePlatformTools() 0039 : m_settings(nullptr) 0040 { 0041 } 0042 0043 /** 0044 * Destructor. 0045 */ 0046 CorePlatformTools::~CorePlatformTools() 0047 { 0048 // Must not be inline because of forwared declared QScopedPointer. 0049 } 0050 0051 /** 0052 * Get application settings. 0053 * @return settings instance. 0054 */ 0055 ISettings* CorePlatformTools::applicationSettings() 0056 { 0057 if (!m_config) { 0058 if (QByteArray configPath = qgetenv("KID3_CONFIG_FILE"); 0059 configPath.isNull()) { 0060 m_settings = new QSettings( 0061 QSettings::UserScope, QLatin1String("Kid3"), 0062 QLatin1String("Kid3"), qApp); 0063 } else { 0064 m_settings = new QSettings( 0065 QFile::decodeName(configPath), QSettings::IniFormat, qApp); 0066 } 0067 m_config.reset(new Kid3Settings(m_settings)); 0068 } 0069 return m_config.data(); 0070 } 0071 0072 /** 0073 * Get icon provider for tagged files. 0074 * @return icon provider. 0075 */ 0076 CoreTaggedFileIconProvider* CorePlatformTools::iconProvider() 0077 { 0078 if (!m_iconProvider) { 0079 m_iconProvider.reset(new CoreTaggedFileIconProvider); 0080 } 0081 return m_iconProvider.data(); 0082 } 0083 0084 /** 0085 * Write text to clipboard. 0086 * @param text text to write 0087 * @return true if operation is supported. 0088 */ 0089 bool CorePlatformTools::writeToClipboard(const QString& text) const 0090 { 0091 Q_UNUSED(text) 0092 return false; 0093 } 0094 0095 /** 0096 * Read text from clipboard. 0097 * @return text, null if operation not supported. 0098 */ 0099 QString CorePlatformTools::readFromClipboard() const 0100 { 0101 return QString(); 0102 } 0103 0104 /** 0105 * Create an audio player instance. 0106 * @param app application context 0107 * @param dbusEnabled true to enable MPRIS D-Bus interface 0108 * @return audio player, nullptr if not supported. 0109 */ 0110 QObject* CorePlatformTools::createAudioPlayer(Kid3Application* app, 0111 bool dbusEnabled) const 0112 { 0113 Q_UNUSED(app) 0114 Q_UNUSED(dbusEnabled) 0115 return nullptr; 0116 } 0117 0118 #ifdef Q_OS_WIN32 0119 0120 #include <QVector> 0121 #ifdef Q_CC_MSVC 0122 #include <windows.h> 0123 #else 0124 #include <windef.h> 0125 #include <winbase.h> 0126 #include <shellapi.h> 0127 #endif 0128 0129 bool CorePlatformTools::moveToTrash(const QString& path) const 0130 { 0131 typedef int (WINAPI *SHFileOperationW_t)(LPSHFILEOPSTRUCTW); 0132 HMODULE hshell32 = GetModuleHandleA("shell32.dll"); 0133 SHFileOperationW_t pSHFileOperationW = reinterpret_cast<SHFileOperationW_t>( 0134 GetProcAddress(hshell32, "SHFileOperationW")); 0135 if (!pSHFileOperationW) { 0136 // SHFileOperationW is only available since Windows XP. 0137 return false; 0138 } 0139 0140 QFileInfo fi(path); 0141 const QString absPath(fi.absoluteFilePath()); 0142 0143 QVector<WCHAR> from(absPath.length() + 2); 0144 int i; 0145 for (i = 0; i < absPath.length(); i++) { 0146 from[i] = absPath.at(i).unicode(); 0147 } 0148 from[i++] = 0; 0149 from[i++] = 0; 0150 0151 SHFILEOPSTRUCTW fileOp; 0152 fileOp.hwnd = 0; 0153 fileOp.wFunc = FO_DELETE; 0154 fileOp.pFrom = from.data(); 0155 fileOp.pTo = 0; 0156 fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; 0157 fileOp.fAnyOperationsAborted = 0; 0158 fileOp.hNameMappings = 0; 0159 fileOp.lpszProgressTitle = 0; 0160 return pSHFileOperationW(&fileOp) == 0; 0161 } 0162 0163 #elif defined Q_OS_MAC 0164 0165 #include <CoreServices/CoreServices.h> 0166 0167 bool CorePlatformTools::moveToTrash(const QString& path) const 0168 { 0169 QFileInfo fi(path); 0170 const QString absPath(fi.absoluteFilePath()); 0171 FSRef fsRef; 0172 OSErr err = FSPathMakeRefWithOptions( 0173 reinterpret_cast<const UInt8*>( 0174 QFile::encodeName(absPath).constData()), 0175 kFSPathMakeRefDoNotFollowLeafSymlink, &fsRef, 0); 0176 if (err != noErr) 0177 return false; 0178 0179 return FSMoveObjectToTrashSync(&fsRef, 0, kFSFileOperationDefaultOptions) == noErr; 0180 } 0181 0182 #else 0183 0184 /* 0185 * Implemented according to Desktop Trash Can Specification at 0186 * http://www.freedesktop.org/wiki/Specifications/trash-spec 0187 */ 0188 0189 #include <QDir> 0190 #include <QDateTime> 0191 #include <QTextStream> 0192 #include <QUrl> 0193 #include <cstdlib> 0194 #include <cstdio> 0195 #include <sys/types.h> 0196 #include <sys/stat.h> 0197 #include <unistd.h> 0198 #ifdef HAVE_MNTENT_H 0199 #include <mntent.h> 0200 #endif 0201 0202 namespace { 0203 0204 bool moveToTrashDir(const QFileInfo& fi, const QString& trashDir) 0205 { 0206 QString absPath(fi.absoluteFilePath()); 0207 QString fileName(fi.fileName()); 0208 QString filesPath(trashDir + QLatin1String("/files")); 0209 QString infoPath(trashDir + QLatin1String("/info")); 0210 QString baseName(fi.baseName()); 0211 QString suffix(fi.completeSuffix()); 0212 QString destName(fileName); 0213 int counter = 1; 0214 while (QFile::exists(filesPath + QLatin1Char('/') + destName) || 0215 QFile::exists(infoPath + QLatin1Char('/') + destName + 0216 QLatin1String(".trashinfo"))) { 0217 ++counter; 0218 destName = QString(QLatin1String("%1.%2.%3")) 0219 .arg(baseName).arg(counter).arg(suffix); 0220 } 0221 if (!(QDir(filesPath).exists() || 0222 QDir().mkpath(filesPath)) || 0223 !(QDir(infoPath).exists() || 0224 QDir().mkpath(infoPath))) 0225 return false; 0226 0227 QFile file(infoPath + QLatin1Char('/') + destName + QLatin1String(".trashinfo")); 0228 if (!file.open(QIODevice::WriteOnly)) 0229 return false; 0230 QTextStream stream(&file); 0231 stream << QString(QLatin1String("[Trash Info]\nPath=%1\nDeletionDate=%2\n")) 0232 .arg(QString::fromLatin1(QUrl(absPath).toEncoded()), 0233 QDateTime::currentDateTime().toString(Qt::ISODate)); 0234 file.close(); 0235 return QDir().rename(absPath, filesPath + QLatin1Char('/') + destName); 0236 } 0237 0238 bool findMountPoint(dev_t dev, QString& mountPoint) 0239 { 0240 #if defined HAVE_MNTENT_H && !defined Q_OS_ANDROID 0241 if (FILE* fp = ::setmntent("/proc/mounts", "r")) { 0242 struct stat st; 0243 struct mntent* mnt; 0244 while ((mnt = ::getmntent(fp)) != nullptr) { 0245 if (::stat(mnt->mnt_dir, &st) != 0) { 0246 continue; 0247 } 0248 0249 if (st.st_dev == dev) { 0250 ::endmntent(fp); 0251 mountPoint = QString::fromLatin1(mnt->mnt_dir); 0252 return true; 0253 } 0254 } 0255 ::endmntent(fp); 0256 } 0257 #else 0258 Q_UNUSED(dev) 0259 Q_UNUSED(mountPoint) 0260 #endif 0261 return false; 0262 } 0263 0264 bool findExtVolumeTrash(const QString& volumeRoot, QString& trashDir) 0265 { 0266 struct stat st; 0267 trashDir = volumeRoot + QLatin1String("/.Trash"); 0268 uid_t uid = ::getuid(); 0269 if (QDir(trashDir).exists() && 0270 ::lstat(trashDir.toLocal8Bit().data(), &st) == 0 && 0271 (S_ISDIR(st.st_mode) && !S_ISLNK(st.st_mode) && (st.st_mode & S_ISVTX))) { 0272 trashDir += QString(QLatin1String("/%1")).arg(uid); 0273 } else { 0274 trashDir += QString(QLatin1String("-%1")).arg(uid); 0275 } 0276 return QDir(trashDir).exists() || QDir().mkpath(trashDir); 0277 } 0278 0279 } // anonymous namespace 0280 0281 /** 0282 * Move file or directory to trash. 0283 * 0284 * @param path path to file or directory 0285 * 0286 * @return true if ok. 0287 */ 0288 bool CorePlatformTools::moveFileToTrash(const QString& path) 0289 { 0290 QFileInfo fi(path); 0291 const QString absPath(fi.absoluteFilePath()); 0292 0293 if (!fi.exists() || !fi.isWritable()) 0294 return false; 0295 0296 struct stat pathStat; 0297 struct stat trashStat; 0298 if (::lstat(QFile::encodeName(absPath).constData(), &pathStat) != 0 || 0299 ::lstat(QFile::encodeName(QDir::homePath()).constData(), &trashStat) != 0) 0300 return false; 0301 0302 QString topDir; 0303 QString trashDir; 0304 if (pathStat.st_dev == trashStat.st_dev) { 0305 QByteArray xdhEnv = qgetenv("XDG_DATA_HOME"); 0306 topDir = !xdhEnv.isEmpty() ? QString::fromLatin1(xdhEnv) 0307 : QDir::homePath() + QLatin1String("/.local/share"); 0308 trashDir = topDir + QLatin1String("/Trash"); 0309 } else if (!(findMountPoint(pathStat.st_dev, topDir) && 0310 findExtVolumeTrash(topDir, trashDir))) { 0311 return false; 0312 } 0313 return moveToTrashDir(fi, trashDir); 0314 } 0315 0316 bool CorePlatformTools::moveToTrash(const QString& path) const 0317 { 0318 return moveFileToTrash(path); 0319 } 0320 0321 #endif 0322 0323 /** 0324 * Construct a name filter string suitable for file dialogs. 0325 * @param nameFilters list of description, filter pairs, e.g. 0326 * [("Images", "*.jpg *.jpeg *.png *.webp"), ("All Files", "*")]. 0327 * @return name filter string. 0328 */ 0329 QString CorePlatformTools::fileDialogNameFilter( 0330 const QList<QPair<QString, QString> >& nameFilters) const 0331 { 0332 return ICorePlatformTools::qtFileDialogNameFilter(nameFilters); 0333 } 0334 0335 /** 0336 * Get file pattern part of m_nameFilter. 0337 * @param nameFilter name filter string 0338 * @return file patterns, e.g. "*.mp3". 0339 */ 0340 QString CorePlatformTools::getNameFilterPatterns(const QString& nameFilter) const 0341 { 0342 return ICorePlatformTools::qtNameFilterPatterns(nameFilter); 0343 }