1#include <QLoggingCategory>
15#include <CoreFoundation/CoreFoundation.h>
16#include <ApplicationServices/ApplicationServices.h>
19#include "SystemProtocolHandler.h"
20#include "ProtocolManager.h"
22static Q_LOGGING_CATEGORY(log,
"WebBrowser.ProtocolManager")
27QStringList CProtocolManager::getAllRegisteredProtocols()
30 return getRegisteredProtocolsLinux();
31#elif defined(Q_OS_WIN)
32 return getRegisteredProtocolsWindows();
33#elif defined(Q_OS_MAC)
34 return getRegisteredProtocolsMacOS();
41QStringList CProtocolManager::getRegisteredProtocolsLinux()
43 QStringList protocols;
46 scanDesktopFilesForProtocols(protocols);
49 QStringList configPaths = {
50 QDir::homePath() +
"/.config/mimeapps.list",
51 QDir::homePath() +
"/.local/share/applications/mimeapps.list",
52 "/etc/xdg/mimeapps.list",
53 "/usr/share/applications/mimeapps.list"
56 for (
const QString &configPath : configPaths) {
57 QFile configFile(configPath);
58 if (configFile.exists() && configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
59 QTextStream stream(&configFile);
61 bool inAddedAssociations =
false;
62 bool inDefaultApplications =
false;
64 while (stream.readLineInto(&line)) {
65 if (line.startsWith(
"[") && line.endsWith(
"]")) {
66 inAddedAssociations = (line ==
"[Added Associations]");
67 inDefaultApplications = (line ==
"[Default Applications]");
71 if ((inAddedAssociations || inDefaultApplications) && line.contains(
"=")) {
72 QStringList parts = line.split(
'=', Qt::SkipEmptyParts);
73 if (parts.size() >= 2) {
74 QString key = parts[0].trimmed();
75 if (key.startsWith(
"x-scheme-handler/")) {
76 QString protocol = key.mid(17);
77 if (!protocols.contains(protocol)) {
78 protocols.append(protocol);
109 QStringList cachePaths = {
110 "/usr/share/applications/mimeinfo.cache",
111 "/usr/local/share/applications/mimeinfo.cache",
112 QDir::homePath() +
"/.local/share/applications/mimeinfo.cache"
115 for (
const QString &cachePath : cachePaths) {
116 QFile cacheFile(cachePath);
117 if (cacheFile.exists() && cacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
118 QTextStream stream(&cacheFile);
120 while (stream.readLineInto(&line)) {
121 if (line.startsWith(
"x-scheme-handler/")) {
122 QStringList parts = line.split(
'=', Qt::SkipEmptyParts);
123 if (parts.size() >= 2) {
124 QString key = parts[0].trimmed();
125 QString protocol = key.mid(17);
126 if (!protocols.contains(protocol)) {
127 protocols.append(protocol);
136 qDebug() <<
"Linux: Found" << protocols.size() <<
"registered protocols";
141void CProtocolManager::scanDesktopFilesForProtocols(QStringList &protocols)
143 QStringList searchPaths = {
144 QDir::homePath() +
"/.local/share/applications",
145 "/usr/share/applications",
146 "/usr/local/share/applications",
147 "/var/lib/flatpak/exports/share/applications",
148 QDir::homePath() +
"/.local/share/flatpak/exports/share/applications"
151 for (
const QString &path : searchPaths) {
154 QStringList desktopFiles = dir.entryList(QStringList() <<
"*.desktop");
155 for (
const QString &file : desktopFiles) {
156 parseDesktopFile(dir.absolutePath() + QDir::separator() + file, protocols);
162void CProtocolManager::parseDesktopFile(
const QString &filePath, QStringList &protocols)
164 QFile file(filePath);
165 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
169 QTextStream stream(&file);
171 bool inDesktopEntry =
false;
173 while (stream.readLineInto(&line)) {
174 if (line.startsWith(
"[Desktop Entry]")) {
175 inDesktopEntry =
true;
179 if (line.startsWith(
"[") && line.endsWith(
"]")) {
180 inDesktopEntry =
false;
184 if (inDesktopEntry && line.startsWith(
"MimeType=")) {
185 QString mimeTypes = line.mid(9);
186 QStringList types = mimeTypes.split(
';', Qt::SkipEmptyParts);
187 for (
const QString &type : types) {
188 if (type.startsWith(
"x-scheme-handler/")) {
189 QString protocol = type.mid(17);
190 if (!protocols.contains(protocol)) {
191 protocols.append(protocol);
202QStringList CProtocolManager::getRegisteredProtocolsWindows()
204 QStringList protocols;
208 LONG result = RegOpenKeyExW(
212 KEY_READ | KEY_ENUMERATE_SUB_KEYS,
216 if (result == ERROR_SUCCESS) {
218 WCHAR subKeyName[256];
219 DWORD nameSize = 256;
221 while (RegEnumKeyExW(hKey, index++, subKeyName, &nameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
222 QString keyName = QString::fromWCharArray(subKeyName);
226 if (RegOpenKeyExW(hKey, subKeyName, 0, KEY_READ, &subKey) == ERROR_SUCCESS) {
228 if (RegQueryValueExW(subKey, L
"URL Protocol", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
229 if (!keyName.startsWith(
".") && !keyName.isEmpty()) {
230 protocols.append(keyName);
241 qDebug() <<
"Windows: Found" << protocols.size() <<
"registered protocols";
248QStringList CProtocolManager::getRegisteredProtocolsMacOS()
250 QStringList protocols;
255 process.start(
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
256 QStringList() <<
"-dump");
258 if (process.waitForFinished(3000)) {
259 QString output = process.readAllStandardOutput();
260 QStringList lines = output.split(
'\n');
262 for (
const QString &line : lines) {
263 if (line.contains(
"scheme") && line.contains(
"\"")) {
265 int start = line.indexOf(
'"');
266 int end = line.indexOf(
'"', start + 1);
267 if (start != -1 && end != -1) {
268 QString protocol = line.mid(start + 1, end - start - 1);
269 if (!protocol.isEmpty() && !protocols.contains(protocol)) {
270 protocols.append(protocol);
279 swda.start(
"swda", QStringList() <<
"getSchemes");
280 if (swda.waitForFinished(2000)) {
281 QString output = swda.readAllStandardOutput();
282 QStringList lines = output.split(
'\n', Qt::SkipEmptyParts);
283 for (
const QString &line : lines) {
284 QString protocol = line.trimmed();
285 if (!protocol.isEmpty() && !protocols.contains(protocol)) {
286 protocols.append(protocol);
291 qDebug() <<
"macOS: Found" << protocols.size() <<
"registered protocols";
298QString CProtocolManager::getDefaultHandlerForProtocol(
const QString &protocol)
301 return getDefaultHandlerLinux(protocol);
302#elif defined(Q_OS_WIN)
303 return getDefaultHandlerWindows(protocol);
304#elif defined(Q_OS_MAC)
305 return getDefaultHandlerMacOS(protocol);
311QString CProtocolManager::getDefaultHandlerLinux(
const QString &protocol)
315 process.start(
"xdg-mime", QStringList() <<
"query" <<
"default" <<
"x-scheme-handler/" + protocol);
316 if (process.waitForFinished(1000)) {
317 QString output = process.readAllStandardOutput().trimmed();
318 if (!output.isEmpty()) {
324 QStringList configPaths = {
325 QDir::homePath() +
"/.config/mimeapps.list",
326 "/etc/xdg/mimeapps.list"
329 for (
const QString &path : configPaths) {
331 if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
332 QTextStream stream(&file);
334 bool inDefaultApps =
false;
336 while (stream.readLineInto(&line)) {
337 if (line.startsWith(
"[Default Applications]")) {
338 inDefaultApps =
true;
341 if (line.startsWith(
"[") && line.endsWith(
"]")) {
342 inDefaultApps =
false;
346 if (inDefaultApps && line.startsWith(
"x-scheme-handler/" + protocol +
"=")) {
348 return line.section(
'=', 1).trimmed();
358QString CProtocolManager::getDefaultHandlerWindows(
const QString &protocol)
363 QString keyPath = QString(
"Software\\Classes\\%1\\shell\\open\\command").arg(protocol);
365 if (RegOpenKeyExW(HKEY_CURRENT_USER, (LPCWSTR)keyPath.utf16(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
367 DWORD size =
sizeof(buffer);
370 if (RegQueryValueExW(hKey, NULL, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS) {
371 handler = QString::fromWCharArray(buffer);
382QString CProtocolManager::getDefaultHandlerMacOS(
const QString &protocol)
386 process.start(
"swda", QStringList() <<
"getHandler" <<
"--URL" << protocol);
387 if (process.waitForFinished(1000)) {
388 QString output = process.readAllStandardOutput().trimmed();
389 if (!output.isEmpty()) {
398bool CProtocolManager::isProtocolRegistered(
const QString &protocol)
400 QStringList protocols = getAllRegisteredProtocols();
401 return protocols.contains(protocol);
404QStringList CProtocolManager::getApplicationsForProtocol(
const QString &protocol)
410 QStringList desktopPaths = {
411 QDir::homePath() +
"/.local/share/applications",
412 "/usr/share/applications",
413 "/usr/local/share/applications"
416 for (
const QString &path : desktopPaths) {
419 QStringList desktopFiles = dir.entryList(QStringList() <<
"*.desktop");
420 for (
const QString &file : desktopFiles) {
421 QFile desktopFile(dir.absolutePath() +
"/" + file);
422 if (desktopFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
423 QTextStream stream(&desktopFile);
425 while (stream.readLineInto(&line)) {
426 if (line.startsWith(
"MimeType=")) {
427 if (line.contains(
"x-scheme-handler/" + protocol)) {
443QMap<QString, QStringList> CProtocolManager::getAllProtocolsWithApps()
445 QMap<QString, QStringList> result;
446 QStringList protocols = getAllRegisteredProtocols();
448 for (
const QString &protocol : protocols) {
449 QStringList apps = getApplicationsForProtocol(protocol);
450 if (!apps.isEmpty()) {
451 result[protocol] = apps;
458void CProtocolManager::registerAllProcotol(QWebEngineProfile *profile)
460 QStringList protocols = getAllRegisteredProtocols();
462 QStringList excludeProtocols = {
463 "http",
"https",
"ftp",
"file",
"data",
464 "javascript",
"about",
"qrc",
"blob"
466 foreach (
const QString &protocol, protocols) {
468 if (excludeProtocols.contains(protocol)) {
469 qDebug(log) <<
"Skipping built-in protocol:" << protocol;
472 QString szHandler = getDefaultHandlerForProtocol(protocol);
473 if(szHandler.isEmpty()) {
474 qDebug(log) <<
"Skipping protocol with no handler:" << protocol;
480 QString lowerProtocol = protocol.toLower();
482 QByteArray schemeName = lowerProtocol.toLatin1();
483 QWebEngineUrlScheme scheme(schemeName);
484 scheme.setFlags(QWebEngineUrlScheme::LocalScheme |
485 QWebEngineUrlScheme::LocalAccessAllowed);
488 if(QWebEngineUrlScheme::schemeByName(schemeName).name().isEmpty())
489 QWebEngineUrlScheme::registerScheme(scheme);
491 qDebug(log) << schemeName <<
"is registed";
496 auto *handler =
new CSystemProtocolHandler(schemeName, profile);
497 profile->installUrlSchemeHandler(schemeName, handler);
499 }
catch (
const std::exception &e) {
500 qWarning(log) <<
"Exception while registering protocol:" << protocol << e.what();