Rabbit Remote Control 0.1.0-bate14
Loading...
Searching...
No Matches
ProtocolManager.cpp
1#include <QLoggingCategory>
2#include <QProcess>
3#include <QFile>
4#include <QTextStream>
5#include <QDebug>
6#include <QDir>
7#include <QSettings>
8
9#ifdef Q_OS_WIN
10#include <windows.h>
11#include <shlwapi.h>
12#endif
13
14#ifdef Q_OS_MAC
15#include <CoreFoundation/CoreFoundation.h>
16#include <ApplicationServices/ApplicationServices.h>
17#endif
18
19#include "SystemProtocolHandler.h"
20#include "ProtocolManager.h"
21
22static Q_LOGGING_CATEGORY(log, "WebBrowser.ProtocolManager")
23CProtocolManager::CProtocolManager(QObject *parent) : QObject(parent)
24{
25}
26
27QStringList CProtocolManager::getAllRegisteredProtocols()
28{
29#ifdef Q_OS_LINUX
30 return getRegisteredProtocolsLinux();
31#elif defined(Q_OS_WIN)
32 return getRegisteredProtocolsWindows();
33#elif defined(Q_OS_MAC)
34 return getRegisteredProtocolsMacOS();
35#else
36 return QStringList();
37#endif
38}
39
40// ==================== Linux 实现 ====================
41QStringList CProtocolManager::getRegisteredProtocolsLinux()
42{
43 QStringList protocols;
44
45 // 1. 扫描所有 .desktop 文件获取协议
46 scanDesktopFilesForProtocols(protocols);
47
48 // 2. 从 mimeapps.list 读取
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"
54 };
55
56 for (const QString &configPath : configPaths) {
57 QFile configFile(configPath);
58 if (configFile.exists() && configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
59 QTextStream stream(&configFile);
60 QString line;
61 bool inAddedAssociations = false;
62 bool inDefaultApplications = false;
63
64 while (stream.readLineInto(&line)) {
65 if (line.startsWith("[") && line.endsWith("]")) {
66 inAddedAssociations = (line == "[Added Associations]");
67 inDefaultApplications = (line == "[Default Applications]");
68 continue;
69 }
70
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);
79 }
80 }
81 }
82 }
83 }
84 configFile.close();
85 }
86 }
87
88 /*
89 // 3. 使用 xdg-mime 查询常见协议(修正版)
90 QStringList commonProtocols = {
91 "http", "https", "ftp", "mailto", "tel", "sms", "callto",
92 "skype", "slack", "spotify", "zoommtg", "msteams", "discord",
93 "git", "ssh", "vnc", "rdp", "rrc"
94 };
95
96 for (const QString &protocol : commonProtocols) {
97 QProcess process;
98 process.start("xdg-mime", QStringList() << "query" << "default"
99 << "x-scheme-handler/" + protocol);
100 if (process.waitForFinished(1000)) {
101 QString output = process.readAllStandardOutput().trimmed();
102 if (!output.isEmpty() && !protocols.contains(protocol)) {
103 protocols.append(protocol);
104 }
105 }
106 }//*/
107
108 // 4. 从 mimeinfo.cache 读取(系统级缓存)
109 QStringList cachePaths = {
110 "/usr/share/applications/mimeinfo.cache",
111 "/usr/local/share/applications/mimeinfo.cache",
112 QDir::homePath() + "/.local/share/applications/mimeinfo.cache"
113 };
114
115 for (const QString &cachePath : cachePaths) {
116 QFile cacheFile(cachePath);
117 if (cacheFile.exists() && cacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
118 QTextStream stream(&cacheFile);
119 QString line;
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);
128 }
129 }
130 }
131 }
132 cacheFile.close();
133 }
134 }
135
136 qDebug() << "Linux: Found" << protocols.size() << "registered protocols";
137 return protocols;
138}
139
140// ==================== 扫描 .desktop 文件 ====================
141void CProtocolManager::scanDesktopFilesForProtocols(QStringList &protocols)
142{
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"
149 };
150
151 for (const QString &path : searchPaths) {
152 QDir dir(path);
153 if (dir.exists()) {
154 QStringList desktopFiles = dir.entryList(QStringList() << "*.desktop");
155 for (const QString &file : desktopFiles) {
156 parseDesktopFile(dir.absolutePath() + QDir::separator() + file, protocols);
157 }
158 }
159 }
160}
161
162void CProtocolManager::parseDesktopFile(const QString &filePath, QStringList &protocols)
163{
164 QFile file(filePath);
165 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
166 return;
167 }
168
169 QTextStream stream(&file);
170 QString line;
171 bool inDesktopEntry = false;
172
173 while (stream.readLineInto(&line)) {
174 if (line.startsWith("[Desktop Entry]")) {
175 inDesktopEntry = true;
176 continue;
177 }
178
179 if (line.startsWith("[") && line.endsWith("]")) {
180 inDesktopEntry = false;
181 continue;
182 }
183
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);
192 }
193 }
194 }
195 break;
196 }
197 }
198 file.close();
199}
200
201// ==================== Windows 实现 ====================
202QStringList CProtocolManager::getRegisteredProtocolsWindows()
203{
204 QStringList protocols;
205
206#ifdef Q_OS_WIN
207 HKEY hKey;
208 LONG result = RegOpenKeyExW(
209 HKEY_CLASSES_ROOT,
210 NULL,
211 0,
212 KEY_READ | KEY_ENUMERATE_SUB_KEYS,
213 &hKey
214 );
215
216 if (result == ERROR_SUCCESS) {
217 DWORD index = 0;
218 WCHAR subKeyName[256];
219 DWORD nameSize = 256;
220
221 while (RegEnumKeyExW(hKey, index++, subKeyName, &nameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
222 QString keyName = QString::fromWCharArray(subKeyName);
223
224 // 检查是否包含 URL Protocol
225 HKEY subKey;
226 if (RegOpenKeyExW(hKey, subKeyName, 0, KEY_READ, &subKey) == ERROR_SUCCESS) {
227 DWORD type;
228 if (RegQueryValueExW(subKey, L"URL Protocol", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
229 if (!keyName.startsWith(".") && !keyName.isEmpty()) {
230 protocols.append(keyName);
231 }
232 }
233 RegCloseKey(subKey);
234 }
235 nameSize = 256;
236 }
237
238 RegCloseKey(hKey);
239 }
240
241 qDebug() << "Windows: Found" << protocols.size() << "registered protocols";
242#endif
243
244 return protocols;
245}
246
247// ==================== macOS 实现 ====================
248QStringList CProtocolManager::getRegisteredProtocolsMacOS()
249{
250 QStringList protocols;
251
252#ifdef Q_OS_MAC
253 // 使用 lsregister 命令
254 QProcess process;
255 process.start("/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
256 QStringList() << "-dump");
257
258 if (process.waitForFinished(3000)) {
259 QString output = process.readAllStandardOutput();
260 QStringList lines = output.split('\n');
261
262 for (const QString &line : lines) {
263 if (line.contains("scheme") && line.contains("\"")) {
264 // 解析 scheme 声明
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);
271 }
272 }
273 }
274 }
275 }
276
277 // 备选方案:使用 SwiftDefaultApps (如果安装)
278 QProcess swda;
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);
287 }
288 }
289 }
290
291 qDebug() << "macOS: Found" << protocols.size() << "registered protocols";
292#endif
293
294 return protocols;
295}
296
297// ==================== 获取默认处理程序 ====================
298QString CProtocolManager::getDefaultHandlerForProtocol(const QString &protocol)
299{
300#ifdef Q_OS_LINUX
301 return getDefaultHandlerLinux(protocol);
302#elif defined(Q_OS_WIN)
303 return getDefaultHandlerWindows(protocol);
304#elif defined(Q_OS_MAC)
305 return getDefaultHandlerMacOS(protocol);
306#else
307 return QString();
308#endif
309}
310
311QString CProtocolManager::getDefaultHandlerLinux(const QString &protocol)
312{
313 // 方法1: 使用 xdg-mime
314 QProcess process;
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()) {
319 return output;
320 }
321 }
322
323 // 方法2: 解析 mimeapps.list
324 QStringList configPaths = {
325 QDir::homePath() + "/.config/mimeapps.list",
326 "/etc/xdg/mimeapps.list"
327 };
328
329 for (const QString &path : configPaths) {
330 QFile file(path);
331 if (file.exists() && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
332 QTextStream stream(&file);
333 QString line;
334 bool inDefaultApps = false;
335
336 while (stream.readLineInto(&line)) {
337 if (line.startsWith("[Default Applications]")) {
338 inDefaultApps = true;
339 continue;
340 }
341 if (line.startsWith("[") && line.endsWith("]")) {
342 inDefaultApps = false;
343 continue;
344 }
345
346 if (inDefaultApps && line.startsWith("x-scheme-handler/" + protocol + "=")) {
347 file.close();
348 return line.section('=', 1).trimmed();
349 }
350 }
351 file.close();
352 }
353 }
354
355 return QString();
356}
357
358QString CProtocolManager::getDefaultHandlerWindows(const QString &protocol)
359{
360#ifdef Q_OS_WIN
361 QString handler;
362 HKEY hKey;
363 QString keyPath = QString("Software\\Classes\\%1\\shell\\open\\command").arg(protocol);
364
365 if (RegOpenKeyExW(HKEY_CURRENT_USER, (LPCWSTR)keyPath.utf16(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
366 WCHAR buffer[1024];
367 DWORD size = sizeof(buffer);
368 DWORD type;
369
370 if (RegQueryValueExW(hKey, NULL, NULL, &type, (LPBYTE)buffer, &size) == ERROR_SUCCESS) {
371 handler = QString::fromWCharArray(buffer);
372 }
373 RegCloseKey(hKey);
374 }
375
376 return handler;
377#else
378 return QString();
379#endif
380}
381
382QString CProtocolManager::getDefaultHandlerMacOS(const QString &protocol)
383{
384#ifdef Q_OS_MAC
385 QProcess process;
386 process.start("swda", QStringList() << "getHandler" << "--URL" << protocol);
387 if (process.waitForFinished(1000)) {
388 QString output = process.readAllStandardOutput().trimmed();
389 if (!output.isEmpty()) {
390 return output;
391 }
392 }
393#endif
394 return QString();
395}
396
397// ==================== 其他辅助方法 ====================
398bool CProtocolManager::isProtocolRegistered(const QString &protocol)
399{
400 QStringList protocols = getAllRegisteredProtocols();
401 return protocols.contains(protocol);
402}
403
404QStringList CProtocolManager::getApplicationsForProtocol(const QString &protocol)
405{
406 QStringList apps;
407
408#ifdef Q_OS_LINUX
409 // 扫描 .desktop 文件
410 QStringList desktopPaths = {
411 QDir::homePath() + "/.local/share/applications",
412 "/usr/share/applications",
413 "/usr/local/share/applications"
414 };
415
416 for (const QString &path : desktopPaths) {
417 QDir dir(path);
418 if (dir.exists()) {
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);
424 QString line;
425 while (stream.readLineInto(&line)) {
426 if (line.startsWith("MimeType=")) {
427 if (line.contains("x-scheme-handler/" + protocol)) {
428 apps.append(file);
429 break;
430 }
431 }
432 }
433 desktopFile.close();
434 }
435 }
436 }
437 }
438#endif
439
440 return apps;
441}
442
443QMap<QString, QStringList> CProtocolManager::getAllProtocolsWithApps()
444{
445 QMap<QString, QStringList> result;
446 QStringList protocols = getAllRegisteredProtocols();
447
448 for (const QString &protocol : protocols) {
449 QStringList apps = getApplicationsForProtocol(protocol);
450 if (!apps.isEmpty()) {
451 result[protocol] = apps;
452 }
453 }
454
455 return result;
456}
457
458void CProtocolManager::registerAllProcotol(QWebEngineProfile *profile)
459{
460 QStringList protocols = getAllRegisteredProtocols();
461 // 过滤掉常见的内置协议,避免冲突
462 QStringList excludeProtocols = {
463 "http", "https", "ftp", "file", "data",
464 "javascript", "about", "qrc", "blob"
465 };
466 foreach (const QString &protocol, protocols) {
467 // 跳过已排除的协议
468 if (excludeProtocols.contains(protocol)) {
469 qDebug(log) << "Skipping built-in protocol:" << protocol;
470 continue;
471 }
472 QString szHandler = getDefaultHandlerForProtocol(protocol);
473 if(szHandler.isEmpty()) {
474 qDebug(log) << "Skipping protocol with no handler:" << protocol;
475 continue;
476 }
477
478 try {
479 // 1. 注册协议方案(使用小写)
480 QString lowerProtocol = protocol.toLower();
481 // 1. 注册协议方案 - 修正1: 使用 QByteArray 而不是 QString
482 QByteArray schemeName = lowerProtocol.toLatin1();
483 QWebEngineUrlScheme scheme(schemeName);
484 scheme.setFlags(QWebEngineUrlScheme::LocalScheme |
485 QWebEngineUrlScheme::LocalAccessAllowed);
486
487 // 注意:重复注册会导致警告,可以先检查是否已注册
488 if(QWebEngineUrlScheme::schemeByName(schemeName).name().isEmpty())
489 QWebEngineUrlScheme::registerScheme(scheme);
490 else {
491 qDebug(log) << schemeName << "is registed";
492 continue;
493 }
494
495 // 2. 安装处理器
496 auto *handler = new CSystemProtocolHandler(schemeName, profile);
497 profile->installUrlSchemeHandler(schemeName, handler);
498
499 } catch (const std::exception &e) {
500 qWarning(log) << "Exception while registering protocol:" << protocol << e.what();
501 }
502 }
503}