Lomiri
Loading...
Searching...
No Matches
xdgwatcher.cpp
1/*
2 * Copyright (C) 2019 UBports Foundation
3 * Author(s): Marius Gripsgard <marius@ubports.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "xdgwatcher.h"
19
20#include <QDebug>
21#include <QDir>
22#include <QFile>
23#include <QStandardPaths>
24#include <QTextStream>
25
26#include <gio/gdesktopappinfo.h>
27
28XdgWatcher::XdgWatcher(QObject* parent)
29 : QObject(parent),
30 m_watcher(new QFileSystemWatcher(this))
31{
32 connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &XdgWatcher::onDirectoryChanged);
33 connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &XdgWatcher::onFileChanged);
34
35 const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
36 for (const auto &path: paths) {
37 const auto qdir = QDir(path);
38 if (!qdir.exists()) {
39 continue;
40 }
41
42 // Add the path itself to watch for newly added apps
43 m_watcher->addPath(path);
44
45 // Add watcher for eatch app to watch for changes
46 const auto files = qdir.entryInfoList(QDir::Files);
47 for (const auto &file: files) {
48 if (file.suffix() == "desktop") {
49 const auto path = file.absoluteFilePath();
50 m_watcher->addPath(path);
51 m_registry.insert(path, getAppId(file));
52 }
53 }
54 }
55}
56
57// "Lomiri style" appID is filename without versionNumber after last "_"
58const QString XdgWatcher::stripAppIdVersion(const QString rawAppID) const {
59 auto appIdComponents = rawAppID.split("_");
60 appIdComponents.removeLast();
61 return appIdComponents.join("_");
62}
63
64// Standard appID see:
65// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id
66const QString XdgWatcher::toStandardAppId(const QFileInfo fileInfo) const {
67 const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
68 for (const auto &path: paths) {
69 if (fileInfo.absolutePath() == path) {
70 break;
71 }
72 if (fileInfo.absolutePath().contains(path)) {
73 auto fileStr = fileInfo.absoluteFilePath();
74 fileStr.replace(path, "");
75 fileStr.replace("/", "-");
76 fileStr.replace(".desktop", "");
77 return fileStr;
78 }
79 }
80 return fileInfo.completeBaseName();
81}
82
83const QString XdgWatcher::getAppId(const QFileInfo fileInfo) const {
84 // We need to open the file to check if its and Ual application
85 // as we cant just rely on the app name as "normal" apps can also
86 // contain 3 _ causing us to belive its an lomiri app
87 // Example kde_org_kate would become kde_org
88 QFile qFile(fileInfo.absoluteFilePath());
89 qFile.open(QIODevice::ReadOnly);
90 QTextStream fileStream(&qFile);
91 QString line;
92 while (fileStream.readLineInto(&line)) {
93 if (line.startsWith("X-Lomiri-Application-ID=")) {
94 auto rawAppID = line.replace("X-Lomiri-Application-ID=", "");
95 qFile.close();
96 return stripAppIdVersion(rawAppID);
97 }
98 }
99 qFile.close();
100
101 // If it's not an "Lomiri" appID, we follow freedesktop standard
102 return toStandardAppId(fileInfo);
103}
104
105// Returns true if the desktop file should be shown (check for NoDisplay)
106bool XdgWatcher::shouldShow(const QFileInfo &file) const {
107 GDesktopAppInfo *appinfo = g_desktop_app_info_new(file.fileName().toUtf8().constData());
108 if (appinfo) {
109 bool show = g_app_info_should_show(G_APP_INFO(appinfo));
110 g_object_unref(appinfo);
111 return show;
112 } else {
113 qWarning() << "XdgWatcher: appinfo is NULL for" << file.absoluteFilePath();
114 return true; // Show the app if we can't determine its visibility
115 }
116}
117
118// Watch for newly added apps
119void XdgWatcher::onDirectoryChanged(const QString &path) {
120 const auto files = QDir(path).entryInfoList(QDir::Files);
121 const auto watchedFiles = m_watcher->files();
122 for (const auto &file: files) {
123 const auto appPath = file.absoluteFilePath();
124 if (file.suffix() == "desktop" && !watchedFiles.contains(appPath) && shouldShow(file)) {
125 m_watcher->addPath(appPath);
126
127 const auto appId = getAppId(file);
128 m_registry.insert(appPath, appId);
129 Q_EMIT appAdded(appId);
130 }
131 }
132}
133
134void XdgWatcher::onFileChanged(const QString &path) {
135 const auto watchedFiles = m_watcher->files();
136 if (watchedFiles.contains(path)) {
137 // The file is still watched, this must be an modify event
138 Q_EMIT appInfoChanged(m_registry.value(path));
139 } else {
140 // File is not watched anymore, this is a remove event.
141 // onDirectoryChanged will handle rename event
142 Q_EMIT appRemoved(m_registry.take(path));
143 }
144}