434 lines
13 KiB
C++
434 lines
13 KiB
C++
|
/*
|
||
|
* Copyright (C) 2021 CutefishOS Team.
|
||
|
*
|
||
|
* Author: Reion Wong <reionwong@gmail.com>
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
#include "mimeappmanager.h"
|
||
|
|
||
|
#include <QDirIterator>
|
||
|
#include <QUrl>
|
||
|
#include <QDir>
|
||
|
|
||
|
#include <QMimeDatabase>
|
||
|
#include <QMimeType>
|
||
|
#include <QProcess>
|
||
|
#include <QSettings>
|
||
|
#include <QDateTime>
|
||
|
#include <QThread>
|
||
|
#include <QDebug>
|
||
|
#include <QSet>
|
||
|
|
||
|
static MimeAppManager *SELF = nullptr;
|
||
|
|
||
|
MimeAppManager *MimeAppManager::self()
|
||
|
{
|
||
|
if (!SELF)
|
||
|
SELF = new MimeAppManager;
|
||
|
|
||
|
return SELF;
|
||
|
}
|
||
|
|
||
|
MimeAppManager::MimeAppManager(QObject *parent)
|
||
|
: QObject(parent),
|
||
|
m_fileSystemWatcher(new QFileSystemWatcher),
|
||
|
m_updateTimer(new QTimer(this))
|
||
|
{
|
||
|
m_updateTimer->setInterval(100);
|
||
|
m_updateTimer->setSingleShot(true);
|
||
|
|
||
|
m_fileSystemWatcher->addPaths(desktopPaths());
|
||
|
|
||
|
connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &MimeAppManager::onFileChanged);
|
||
|
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &MimeAppManager::onFileChanged);
|
||
|
connect(m_updateTimer, &QTimer::timeout, this, &MimeAppManager::initApplications);
|
||
|
|
||
|
m_updateTimer->start();
|
||
|
}
|
||
|
|
||
|
QStringList MimeAppManager::desktopPaths()
|
||
|
{
|
||
|
QStringList folders;
|
||
|
folders << QString("/usr/share/applications")
|
||
|
<< QString("/usr/local/share/applications/")
|
||
|
<< QDir::homePath() + QString("/.local/share/applications");
|
||
|
|
||
|
return folders;
|
||
|
}
|
||
|
|
||
|
QString MimeAppManager::mimeAppsListFilePath()
|
||
|
{
|
||
|
return QString("%1/.config/mimeapps.list").arg(QDir::homePath());
|
||
|
}
|
||
|
|
||
|
void MimeAppManager::initApplications()
|
||
|
{
|
||
|
m_desktopFiles.clear();
|
||
|
m_desktopObjects.clear();
|
||
|
|
||
|
QMap<QString, QSet<QString>> mimeAppsSet;
|
||
|
|
||
|
for (const QString &folder : desktopPaths()) {
|
||
|
QDirIterator itor(folder, QStringList("*.desktop"), QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories);
|
||
|
while (itor.hasNext()) {
|
||
|
itor.next();
|
||
|
QString filePath = itor.filePath();
|
||
|
XdgDesktopFile desktopFile(filePath);
|
||
|
|
||
|
if (!desktopFile.valid())
|
||
|
continue;
|
||
|
|
||
|
m_desktopFiles.append(filePath);
|
||
|
m_desktopObjects.insert(filePath, desktopFile);
|
||
|
|
||
|
// Load terminal
|
||
|
QStringList categories = desktopFile.value("Categories").toString().split(";");
|
||
|
if (categories.contains("TerminalEmulator")) {
|
||
|
m_terminalApps.append(desktopFile);
|
||
|
}
|
||
|
|
||
|
QStringList mimeTypes = desktopFile.value("MimeType").toString().trimmed().split(";");
|
||
|
for (const QString &mimeType : mimeTypes) {
|
||
|
if (!mimeType.isEmpty()) {
|
||
|
QSet<QString> apps;
|
||
|
if (mimeAppsSet.contains(mimeType)) {
|
||
|
apps = mimeAppsSet.value(mimeType);
|
||
|
apps.insert(filePath);
|
||
|
} else {
|
||
|
apps.insert(filePath);
|
||
|
}
|
||
|
|
||
|
mimeAppsSet.insert(mimeType, apps);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const QString &key : mimeAppsSet.keys()) {
|
||
|
QSet<QString> apps = mimeAppsSet.value(key);
|
||
|
QStringList orderApps;
|
||
|
|
||
|
if (apps.count() > 1) {
|
||
|
QFileInfoList fileInfos;
|
||
|
for (const QString &app : apps) {
|
||
|
QFileInfo info(app);
|
||
|
fileInfos.append(info);
|
||
|
}
|
||
|
|
||
|
std::sort(fileInfos.begin(), fileInfos.end(), [=] (const QFileInfo &f1, const QFileInfo &f2) {
|
||
|
return f1.birthTime() < f2.birthTime();
|
||
|
});
|
||
|
|
||
|
for (QFileInfo info : fileInfos) {
|
||
|
orderApps.append(info.absoluteFilePath());
|
||
|
}
|
||
|
} else {
|
||
|
orderApps.append(apps.values());
|
||
|
}
|
||
|
|
||
|
m_mimeApps.insert(key, orderApps);
|
||
|
}
|
||
|
|
||
|
// Check from cache.
|
||
|
// ref: https://specifications.freedesktop.org/desktop-entry-spec/0.9.5/ar01s07.html
|
||
|
|
||
|
QFile file("/usr/share/applications/mimeinfo.cache");
|
||
|
if (!file.open(QIODevice::ReadOnly))
|
||
|
return;
|
||
|
|
||
|
QStringList audioDesktopList;
|
||
|
QStringList imageDeksopList;
|
||
|
QStringList textDekstopList;
|
||
|
QStringList videoDesktopList;
|
||
|
|
||
|
while (!file.atEnd()) {
|
||
|
QString line = file.readLine();
|
||
|
QString mimeType = line.split("=").first();
|
||
|
QString _desktops = line.split("=").last();
|
||
|
QStringList desktops = _desktops.split(";");
|
||
|
|
||
|
for (const QString &desktop : desktops) {
|
||
|
if (desktop.isEmpty() || audioDesktopList.contains(desktop))
|
||
|
continue;
|
||
|
|
||
|
if (mimeType.startsWith("audio")) {
|
||
|
if (!audioDesktopList.contains(desktop))
|
||
|
audioDesktopList.append(desktop);
|
||
|
} else if (mimeType.startsWith("image")) {
|
||
|
if (!imageDeksopList.contains(desktop))
|
||
|
imageDeksopList.append(desktop);
|
||
|
} else if (mimeType.startsWith("text")) {
|
||
|
if (!textDekstopList.contains(desktop))
|
||
|
textDekstopList.append(desktop);
|
||
|
} else if (mimeType.startsWith("video")) {
|
||
|
if (!videoDesktopList.contains(desktop))
|
||
|
videoDesktopList.append(desktop);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
file.close();
|
||
|
|
||
|
const QString mimeInfoCacheRootPath = "/usr/share/applications";
|
||
|
for (const QString &desktop : audioDesktopList) {
|
||
|
const QString &path = QString("%1/%2").arg(mimeInfoCacheRootPath, desktop);
|
||
|
if (!QFile::exists(path))
|
||
|
continue;
|
||
|
|
||
|
XdgDesktopFile desktopFile(path);
|
||
|
if (desktopFile.valid())
|
||
|
m_audioMimeApps.insert(path, desktopFile);
|
||
|
}
|
||
|
|
||
|
for (const QString &desktop : imageDeksopList) {
|
||
|
const QString &path = QString("%1/%2").arg(mimeInfoCacheRootPath, desktop);
|
||
|
if (!QFile::exists(path))
|
||
|
continue;
|
||
|
|
||
|
XdgDesktopFile desktopFile(path);
|
||
|
if (desktopFile.valid())
|
||
|
m_imageMimeApps.insert(path, desktopFile);
|
||
|
}
|
||
|
|
||
|
for (const QString &desktop : textDekstopList) {
|
||
|
const QString &path = QString("%1/%2").arg(mimeInfoCacheRootPath, desktop);
|
||
|
if (!QFile::exists(path))
|
||
|
continue;
|
||
|
|
||
|
XdgDesktopFile desktopFile(path);
|
||
|
if (desktopFile.valid())
|
||
|
m_textMimeApps.insert(path, desktopFile);
|
||
|
}
|
||
|
|
||
|
for (const QString &desktop : videoDesktopList) {
|
||
|
const QString &path = QString("%1/%2").arg(mimeInfoCacheRootPath, desktop);
|
||
|
if (!QFile::exists(path))
|
||
|
continue;
|
||
|
|
||
|
XdgDesktopFile desktopFile(path);
|
||
|
if (desktopFile.valid())
|
||
|
m_videoMimeApps.insert(path, desktopFile);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QString MimeAppManager::getDefaultAppByFilePath(const QString &filePath)
|
||
|
{
|
||
|
return getDefaultAppByMimeType(QMimeDatabase().mimeTypeForFile(filePath));
|
||
|
}
|
||
|
|
||
|
QString MimeAppManager::getDefaultAppByMimeType(const QMimeType &mimeType)
|
||
|
{
|
||
|
QString mimeappsFile = mimeAppsListFilePath();
|
||
|
|
||
|
if (!QFile::exists(mimeappsFile))
|
||
|
return QString();
|
||
|
|
||
|
QSettings settings(mimeappsFile, QSettings::IniFormat);
|
||
|
|
||
|
// for (const QString group : settings.childGroups()) {
|
||
|
// settings.beginGroup(group);
|
||
|
// for (const QString &key : settings.allKeys()) {
|
||
|
// if (key == mimeType.name())
|
||
|
// return settings.value(key).toString();
|
||
|
// }
|
||
|
// settings.endGroup();
|
||
|
// }
|
||
|
|
||
|
settings.beginGroup("Default Applications");
|
||
|
// TODO: User applications directory?
|
||
|
if (settings.contains(mimeType.name()))
|
||
|
return QString("/usr/share/applications/%1").arg(settings.value(mimeType.name()).toString());
|
||
|
|
||
|
settings.endGroup();
|
||
|
|
||
|
settings.beginGroup("Added Associations");
|
||
|
if (settings.contains(mimeType.name()))
|
||
|
return QString("/usr/share/applications/%1").arg(settings.value(mimeType.name()).toString());
|
||
|
|
||
|
return QString();
|
||
|
}
|
||
|
|
||
|
QString MimeAppManager::getDefaultAppDesktopByMimeType(const QString &mimeType)
|
||
|
{
|
||
|
return getDefaultAppByMimeType(QMimeDatabase().mimeTypeForName(mimeType));
|
||
|
}
|
||
|
|
||
|
bool MimeAppManager::setDefaultAppForType(const QString &mimeType, const QString &app)
|
||
|
{
|
||
|
// ref: https://specifications.freedesktop.org/mime-apps-spec/1.0.1/ar01s03.html
|
||
|
|
||
|
QString mimeappsFile = mimeAppsListFilePath();
|
||
|
QString desktop = app;
|
||
|
|
||
|
if (QFile::exists(desktop)) {
|
||
|
QFileInfo info(desktop);
|
||
|
desktop = info.completeBaseName();
|
||
|
}
|
||
|
|
||
|
// QSettings settings(mimeappsFile, QSettings::IniFormat);
|
||
|
// settings.setIniCodec("UTF-8");
|
||
|
|
||
|
// if (!settings.isWritable())
|
||
|
// return false;
|
||
|
|
||
|
// settings.beginGroup("Default Applications");
|
||
|
// settings.setValue(mimeType, desktop);
|
||
|
// settings.sync();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool MimeAppManager::setDefaultAppForFile(const QString &filePath, const QString &desktop)
|
||
|
{
|
||
|
// ref: https://specifications.freedesktop.org/mime-apps-spec/1.0.1/ar01s03.html
|
||
|
|
||
|
QString mimeappsFile = mimeAppsListFilePath();
|
||
|
QMimeType mimeType;
|
||
|
QString value = desktop;
|
||
|
|
||
|
if (!QFile::exists(filePath))
|
||
|
return false;
|
||
|
else
|
||
|
mimeType = QMimeDatabase().mimeTypeForFile(filePath);
|
||
|
|
||
|
if (QFile::exists(value)) {
|
||
|
QFileInfo info(value);
|
||
|
value = info.fileName();
|
||
|
}
|
||
|
|
||
|
// QSettings settings(mimeappsFile, QSettings::IniFormat);
|
||
|
// settings.setIniCodec("UTF-8");
|
||
|
|
||
|
// if (!settings.isWritable())
|
||
|
// return false;
|
||
|
|
||
|
// settings.beginGroup("Default Applications"); // Added Associations
|
||
|
// settings.setValue(mimeType.name(), value);
|
||
|
// settings.sync();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
QStringList MimeAppManager::getRecommendedAppsByFilePath(const QString &filePath)
|
||
|
{
|
||
|
return getRecommendedAppsByMimeType(QMimeDatabase().mimeTypeForFile(filePath));
|
||
|
}
|
||
|
|
||
|
QStringList MimeAppManager::getRecommendedAppsByMimeType(const QMimeType &mimeType)
|
||
|
{
|
||
|
QStringList recommendApps;
|
||
|
QList<QMimeType> mimeTypeList;
|
||
|
QMimeDatabase mimeDatabase;
|
||
|
|
||
|
mimeTypeList.append(mimeType);
|
||
|
|
||
|
while (recommendApps.isEmpty()) {
|
||
|
for (const QMimeType &type : mimeTypeList) {
|
||
|
QStringList typeNameList;
|
||
|
|
||
|
typeNameList.append(type.name());
|
||
|
typeNameList.append(type.aliases());
|
||
|
|
||
|
for (const QString &name : typeNameList) {
|
||
|
for (const QString &app : m_mimeApps.value(name)) {
|
||
|
bool exists = false;
|
||
|
|
||
|
for (const QString &other : recommendApps) {
|
||
|
const XdgDesktopFile &appDesktop = m_desktopObjects.value(app);
|
||
|
const XdgDesktopFile &otherDesktop = m_desktopObjects.value(other);
|
||
|
|
||
|
if (appDesktop.value("Exec").toString() == otherDesktop.value("Exec").toString() &&
|
||
|
appDesktop.localeName() == otherDesktop.localeName()) {
|
||
|
exists = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if desktop file was not existed do not recommend!!
|
||
|
if (!QFileInfo::exists(app)) {
|
||
|
qWarning() << app << "not exist anymore";
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!exists)
|
||
|
recommendApps.append(app);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!recommendApps.isEmpty())
|
||
|
break;
|
||
|
|
||
|
QList<QMimeType> newMimeTypeList;
|
||
|
for (const QMimeType &type : mimeTypeList) {
|
||
|
for (const QString &name : type.parentMimeTypes())
|
||
|
newMimeTypeList.append(mimeDatabase.mimeTypeForName(name));
|
||
|
}
|
||
|
|
||
|
mimeTypeList = newMimeTypeList;
|
||
|
|
||
|
if (mimeTypeList.isEmpty())
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return recommendApps;
|
||
|
}
|
||
|
|
||
|
QVariantList MimeAppManager::recommendedApps(const QUrl &url)
|
||
|
{
|
||
|
QVariantList list;
|
||
|
|
||
|
if (url.isValid()) {
|
||
|
const QString &filePath = url.toString();
|
||
|
|
||
|
for (const QString &path : getRecommendedAppsByFilePath(filePath)) {
|
||
|
XdgDesktopFile desktop(path);
|
||
|
|
||
|
if (desktop.valid())
|
||
|
continue;
|
||
|
|
||
|
QVariantMap item;
|
||
|
item["icon"] = desktop.value("IconName").toString();
|
||
|
item["name"] = desktop.localeName();
|
||
|
item["desktopFile"] = path;
|
||
|
|
||
|
list << item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return list;
|
||
|
}
|
||
|
|
||
|
void MimeAppManager::launchTerminal(const QString &path)
|
||
|
{
|
||
|
if (m_terminalApps.isEmpty())
|
||
|
return;
|
||
|
|
||
|
QString command = m_terminalApps.first().value("Exec").toString();
|
||
|
QProcess process;
|
||
|
process.setProgram(command);
|
||
|
// Launch terminal with working directory set.
|
||
|
process.setWorkingDirectory(path);
|
||
|
process.startDetached();
|
||
|
}
|
||
|
|
||
|
void MimeAppManager::onFileChanged(const QString &path)
|
||
|
{
|
||
|
Q_UNUSED(path);
|
||
|
|
||
|
m_updateTimer->start();
|
||
|
}
|