玉兔远程控制 0.1.0-bate5
载入中...
搜索中...
未找到
DesktopShortcuts.cpp
1// Author: Kang Lin <kl222@126.com>
2
3#include <QLoggingCategory>
4#include <QStandardPaths>
5#include <QTemporaryDir>
6#include "DesktopShortcuts.h"
7
8static Q_LOGGING_CATEGORY(log, "Plugin.Hook.Desktop.Shortcut")
9
11 : QObject(parent)
12{
13 m_desktopEnv = detectDesktopEnvironment();
14 qDebug(log) << "Desktop environment:" << m_desktopEnv;
15
16 // 设置备份路径
17 QString tempDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
18 m_gnomeBackupPath = tempDir + "/gnome_shortcuts_backup.conf";
19 m_kdeBackupPath = tempDir + "/kde_shortcuts_backup.conf";
20}
21
22CDesktopShortcutManager::~CDesktopShortcutManager()
23{
24 // 确保退出时恢复快捷键
25 if (m_shortcutsDisabled) {
26 qWarning(log) << "Shortcuts is disabled, restoring ......";
27 restoreAllShortcuts();
28 }
29}
30
31QString CDesktopShortcutManager::detectDesktopEnvironment()
32{
33 QString xdgDesktop = qEnvironmentVariable("XDG_CURRENT_DESKTOP", "").toLower();
34 QString desktopSession = qEnvironmentVariable("DESKTOP_SESSION", "").toLower();
35 QString gdmSession = qEnvironmentVariable("GDMSESSION", "").toLower();
36
37 if (xdgDesktop.contains("gnome") || desktopSession.contains("gnome") || gdmSession.contains("gnome")) {
38 return "GNOME";
39 } else if (xdgDesktop.contains("kde") || desktopSession.contains("plasma") || gdmSession.contains("plasma")) {
40 return "KDE";
41 } else if (xdgDesktop.contains("xfce") || desktopSession.contains("xfce")) {
42 return "XFCE";
43 } else if (xdgDesktop.contains("mate") || desktopSession.contains("mate")) {
44 return "MATE";
45 } else if (xdgDesktop.contains("cinnamon") || desktopSession.contains("cinnamon")) {
46 return "Cinnamon";
47 } else if (xdgDesktop.contains("pantheon") || desktopSession.contains("pantheon")) {
48 return "Pantheon";
49 } else {
50 return "Unknown";
51 }
52}
53
54bool CDesktopShortcutManager::disableAllShortcuts()
55{
56 if (m_shortcutsDisabled) {
57 qDebug(log) << "Shortcuts is disabled";
58 return true;
59 }
60
61 m_shortcutsDisabled = true;
62 bool success = false;
63
64#if defined(Q_OS_LINUX)
65 if (m_desktopEnv == "GNOME") {
66 success = disableGNOMEShortcuts();
67 } else if (m_desktopEnv == "KDE") {
68 success = disableKDEShortcuts();
69 } else if (m_desktopEnv == "XFCE") {
70 // XFCE 实现类似
71 qWarning(log) << "XFCE Shortcut key disabling is not yet implemented";
72 } else {
73 qWarning(log) << "Unsupported desktop environment:" << m_desktopEnv;
74 return false;
75 }
76#endif
77
78 if (success) {
79 qDebug(log) << "All desktop shortcuts have been disabled";
80 }
81
82 return success;
83}
84
85bool CDesktopShortcutManager::restoreAllShortcuts()
86{
87 if (!m_shortcutsDisabled) {
88 qDebug(log) << "The shortcut key is not disabled, no need to restore.";
89 return true;
90 }
91
92 m_shortcutsDisabled = false;
93 bool success = false;
94
95#if defined(Q_OS_LINUX)
96 if (m_desktopEnv == "GNOME") {
97 success = restoreGNOMEShortcuts();
98 //resetGNOMEShortcuts();
99 } else if (m_desktopEnv == "KDE") {
100 success = restoreKDEShortcuts();
101 } else {
102 qWarning(log) << "Unsupported desktop environment:" << m_desktopEnv;
103 return false;
104 }
105#endif
106
107 if (success) {
108 qDebug(log) << "All desktop shortcuts have been restored";
109 }
110
111 return success;
112}
113
114#if defined(Q_OS_LINUX)
115// GNOME 快捷键管理
116bool CDesktopShortcutManager::disableGNOMEShortcuts()
117{
118 qDebug(log) << "Disable GNOME shortcuts ......";
119
120 // 备份当前设置
121 backupGNOMESettings();
122
123 // 使用正确的数据结构来存储设置
124 struct GNOMESetting {
125 QString schema;
126 QString key;
127 QString value;
128 };
129
130 QVector<GNOMESetting> disabledSettings = {
131 // Super 键
132 {"org.gnome.mutter", "overlay-key", "''"},
133
134 // 窗口管理
135 {"org.gnome.desktop.wm.keybindings", "close", "['']"},
136 {"org.gnome.desktop.wm.keybindings", "minimize", "['']"},
137 {"org.gnome.desktop.wm.keybindings", "maximize", "['']"},
138 {"org.gnome.desktop.wm.keybindings", "unmaximize", "['']"},
139 {"org.gnome.desktop.wm.keybindings", "begin-move", "['']"},
140 {"org.gnome.desktop.wm.keybindings", "begin-resize", "['']"},
141 {"org.gnome.desktop.wm.keybindings", "toggle-fullscreen", "['']"},
142 {"org.gnome.desktop.wm.keybindings", "toggle-maximized", "['']"},
143 {"org.gnome.desktop.wm.keybindings", "show-desktop", "['']"},
144
145 // 工作区
146 {"org.gnome.desktop.wm.keybindings", "switch-to-workspace-left", "['']"},
147 {"org.gnome.desktop.wm.keybindings", "switch-to-workspace-right", "['']"},
148 {"org.gnome.desktop.wm.keybindings", "switch-to-workspace-up", "['']"},
149 {"org.gnome.desktop.wm.keybindings", "switch-to-workspace-down", "['']"},
150 {"org.gnome.desktop.wm.keybindings", "switch-to-workspace-last", "['']"},
151 {"org.gnome.desktop.wm.keybindings", "move-to-workspace-left", "['']"},
152 {"org.gnome.desktop.wm.keybindings", "move-to-workspace-right", "['']"},
153 {"org.gnome.desktop.wm.keybindings", "move-to-workspace-up", "['']"},
154 {"org.gnome.desktop.wm.keybindings", "move-to-workspace-down", "['']"},
155
156 {"org.gnome.desktop.wm.keybindings", "switch-input-source", "['']"},
157 {"org.gnome.desktop.wm.keybindings", "switch-input-source-backward", "['']"},
158
159 // 面板和 Shell
160 {"org.gnome.shell.keybindings", "toggle-application-view", "['']"},
161 {"org.gnome.shell.keybindings", "toggle-message-tray", "['']"},
162 {"org.gnome.shell.keybindings", "focus-active-notification", "['']"},
163 {"org.gnome.shell.keybindings", "toggle-overview", "['']"},
164
165 {"org.gnome.shell.keybindings", "screenshot", "['']"},
166 {"org.gnome.shell.keybindings", "screenshot-window", "['']"},
167 {"org.gnome.shell.keybindings", "show-screenshot-ui", "['']"},
168 {"org.gnome.shell.keybindings", "show-screen-recording-ui", "['']"},
169
170 // 媒体键
171 {"org.gnome.settings-daemon.plugins.media-keys", "home", "['']"},
172 {"org.gnome.settings-daemon.plugins.media-keys", "email", "['']"},
173 {"org.gnome.settings-daemon.plugins.media-keys", "www", "['']"},
174 {"org.gnome.settings-daemon.plugins.media-keys", "calculator", "['']"},
175 {"org.gnome.settings-daemon.plugins.media-keys", "screensaver", "['']"},
176 {"org.gnome.settings-daemon.plugins.media-keys", "media", "['']"},
177 {"org.gnome.settings-daemon.plugins.media-keys", "volume-up", "['']"},
178 {"org.gnome.settings-daemon.plugins.media-keys", "volume-down", "['']"},
179 {"org.gnome.settings-daemon.plugins.media-keys", "volume-mute", "['']"},
180 {"org.gnome.settings-daemon.plugins.media-keys", "next", "['']"},
181 {"org.gnome.settings-daemon.plugins.media-keys", "previous", "['']"},
182 {"org.gnome.settings-daemon.plugins.media-keys", "play", "['']"},
183 {"org.gnome.settings-daemon.plugins.media-keys", "pause", "['']"},
184 {"org.gnome.settings-daemon.plugins.media-keys", "stop", "['']"},
185
186 // 自定义快捷键
187 {"org.gnome.settings-daemon.plugins.media-keys", "custom-keybindings", "['']"},
188
189 // 其他系统快捷键
190 {"org.gnome.desktop.wm.keybindings", "panel-main-menu", "['']"},
191 {"org.gnome.desktop.wm.keybindings", "panel-run-dialog", "['']"},
192 {"org.gnome.desktop.wm.keybindings", "switch-applications", "['']"},
193 {"org.gnome.desktop.wm.keybindings", "switch-applications-backward", "['']"},
194 {"org.gnome.desktop.wm.keybindings", "switch-windows", "['']"},
195 {"org.gnome.desktop.wm.keybindings", "switch-windows-backward", "['']"},
196 {"org.gnome.desktop.wm.keybindings", "switch-group", "['']"},
197 {"org.gnome.desktop.wm.keybindings", "switch-group-backward", "['']"}
198
199 };
200
201 // 应用设置
202 bool allSuccess = true;
203 int successCount = 0;
204 int totalCount = disabledSettings.size();
205
206 for (const GNOMESetting &setting : disabledSettings) {
207 if (runCommand("gsettings", {"set", setting.schema, setting.key, setting.value})) {
208 successCount++;
209 qDebug(log) << "Disabled:" << setting.schema << setting.key;
210 } else {
211 qCritical(log) << "Disable fail:" << setting.schema << setting.key;
212 allSuccess = false;
213 }
214 }
215
216 qDebug(log) << QString("GNOME is disabled: %1/%2 Success").arg(successCount).arg(totalCount);
217
218 /*/ 重启 GNOME Shell 使设置生效
219 if (allSuccess || successCount > 0) {
220 qDebug(log) << "Reboot GNOME Shell ......";
221 if (runCommand("killall", {"-3", "gnome-shell"})) {
222 qDebug(log) << "GNOME Shell reboot signal is sended";
223 } else {
224 qWarning(log) << "Reboot GNOME Shell fail, Some settings may require you to log in again to take effect.";
225 }
226 } //*/
227
228 return allSuccess;
229}
230
231bool CDesktopShortcutManager::restoreGNOMEShortcuts()
232{
233 qDebug(log) << "Restore GNOME shortcuts ......";
234
235 if (m_gnomeSettings.isEmpty()) {
236 qWarning(log) << "No backup settings are available to restore";
237 return false;
238 }
239
240 bool allSuccess = true;
241 int successCount = 0;
242 int totalCount = m_gnomeSettings.size();
243
244 // 恢复备份的设置
245 for (auto it = m_gnomeSettings.begin(); it != m_gnomeSettings.end(); ++it) {
246 QStringList parts = it.key().split("|");
247 if (parts.size() == 2) {
248 QString schema = parts[0];
249 QString key = parts[1];
250 QString value = it.value().toString();
251
252 if (runCommand("gsettings", {"set", schema, key, value})) {
253 successCount++;
254 qDebug(log) << "Restored:" << schema << key << value;
255 } else {
256 qCritical(log) << "Restore failed:" << schema << key << value;
257 allSuccess = false;
258 }
259 }
260 }
261
262 qDebug(log) << QString("GNOME shortcut restoration completed: %1/%2 successful").arg(successCount).arg(totalCount);
263
264 /*/ 重启 GNOME Shell 使设置生效
265 if (successCount > 0) {
266 qDebug(log) << "Restart GNOME Shell...";
267 if (runCommand("killall", {"-3", "gnome-shell"})) {
268 qDebug(log) << "The GNOME Shell restart signal has been sent";
269 } else {
270 qWarning(log) << "Failed to restart GNOME Shell; some settings may require logging in again to take effect.";
271 }
272 } //*/
273
274 return allSuccess;
275}
276
277bool CDesktopShortcutManager::resetGNOMEShortcuts()
278{
279 qDebug(log) << "Reset GNOME shortcuts ......";
280
281 if (m_gnomeSettings.isEmpty()) {
282 qWarning(log) << "No backup settings are available to reset";
283 return false;
284 }
285
286 bool allSuccess = true;
287 int successCount = 0;
288 int totalCount = m_gnomeSettings.size();
289
290 // 恢复备份的设置
291 for (auto it = m_gnomeSettings.begin(); it != m_gnomeSettings.end(); ++it) {
292 QStringList parts = it.key().split("|");
293 if (parts.size() == 2) {
294 QString schema = parts[0];
295 QString key = parts[1];
296
297 if (runCommand("gsettings", {"reset", schema, key})) {
298 successCount++;
299 qDebug(log) << "Reset:" << schema << key;
300 } else {
301 qCritical(log) << "Reset failed:" << schema << key;
302 allSuccess = false;
303 }
304 }
305 }
306
307 qDebug(log) << QString("GNOME shortcut reset completed: %1/%2 successful").arg(successCount).arg(totalCount);
308
309 /*/ 重启 GNOME Shell 使设置生效
310 if (successCount > 0) {
311 qDebug(log) << "Restart GNOME Shell...";
312 if (runCommand("killall", {"-3", "gnome-shell"})) {
313 qDebug(log) << "The GNOME Shell restart signal has been sent";
314 } else {
315 qWarning(log) << "Failed to restart GNOME Shell; some settings may require logging in again to take effect.";
316 }
317 } //*/
318
319 return allSuccess;
320}
321
322void CDesktopShortcutManager::backupGNOMESettings()
323{
324 qDebug(log) << "Backup GNOME settings ......";
325
326 m_gnomeSettings.clear();
327
328 // 重要的 GNOME 设置 schema
329 QVector<QString> schemas = {
330 "org.gnome.desktop.wm.keybindings",
331 "org.gnome.shell.keybindings",
332 "org.gnome.settings-daemon.plugins.media-keys",
333 "org.gnome.mutter"
334 };
335
336 for (const QString &schema : schemas) {
337 // 获取 schema 的所有键
338 QString output = getCommandOutput("gsettings", {"list-keys", schema});
339 if (output.isEmpty()) {
340 qWarning(log) << "Unable to retrieve the schema key:" << schema;
341 continue;
342 }
343
344 QStringList keys = output.split('\n', Qt::SkipEmptyParts);
345 qDebug(log) << QString("Schema %1 has %2 keys").arg(schema).arg(keys.size());
346
347 foreach (const QString &key, keys) {
348 QString value = getCommandOutput("gsettings", {"get", schema, key}).trimmed();
349 if (!value.isEmpty()) {
350 // 使用 schema + key 作为唯一标识
351 QString settingKey = schema + "|" + key;
352 m_gnomeSettings[settingKey] = value;
353 qDebug(log) << "Backup:" << settingKey << "=" << value;
354 } else {
355 qWarning(log) << "Backup fail:" << schema + "|" + key;
356 }
357 }
358 }
359
360 qDebug(log) << QString("GNOME settings backup completed, a total of %1 settings were backed up").arg(m_gnomeSettings.size());
361}
362
363// KDE 快捷键管理
364bool CDesktopShortcutManager::disableKDEShortcuts()
365{
366 qDebug(log) << "Disable KDE shortcuts ......";
367
368 backupKDESettings();
369
370 // 禁用 KDE 全局快捷键
371 bool success = true;
372
373 // 禁用 KWin 快捷键
374 QStringList kwinKeys = {
375 "Window Close", "Window Maximize", "Window Minimize",
376 "Window Fullscreen", "Window to Desktop 1", "Window to Desktop 2",
377 "Switch to Desktop 1", "Switch to Desktop 2", "Expose"
378 };
379
380 for (const QString &key : kwinKeys) {
381 if (!runCommand("kwriteconfig5", {"--file", "kglobalshortcutsrc", "--key", key, "none"})) {
382 success = false;
383 }
384 }
385
386 // 禁用修饰键快捷键
387 if (!runCommand("kwriteconfig5", {"--file", "kwinrc", "--group", "ModifierOnlyShortcuts", "--key", "Meta", ""})) {
388 success = false;
389 }
390
391 /*/ 重启 KWin 使设置生效
392 runCommand("kwin_x11", {"--replace"});//*/
393
394 return success;
395}
396
397bool CDesktopShortcutManager::restoreKDEShortcuts()
398{
399 qDebug(log) << "Restore KDE shortcuts ......";
400
401 // 恢复备份的配置文件
402 QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
403
404 if (!m_kdeSettings.isEmpty()) {
405 for (auto it = m_kdeSettings.begin(); it != m_kdeSettings.end(); ++it) {
406 QStringList parts = it.key().split("|");
407 if (parts.size() == 3) {
408 QString file = parts[0];
409 QString group = parts[1];
410 QString key = parts[2];
411
412 if (!runCommand("kwriteconfig5", {"--file", file, "--group", group, "--key", key, it.value()})) {
413 qWarning(log) << "Failed to restore KDE setting:" << file << group << key;
414 }
415 }
416 }
417 }
418
419 /*/ 重启 KWin
420 runCommand("kwin_x11", {"--replace"});//*/
421
422 return true;
423}
424
425void CDesktopShortcutManager::backupKDESettings()
426{
427 qDebug(log) << "Backup KDE settings ......";
428
429 QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
430
431 // 备份重要的 KDE 配置文件
432 QStringList configFiles = {
433 "kglobalshortcutsrc",
434 "kwinrc",
435 "khotkeysrc"
436 };
437
438 for (const QString &file : configFiles) {
439 QString sourcePath = configDir + "/" + file;
440 if (QFile::exists(sourcePath)) {
441 backupFile(sourcePath, m_kdeBackupPath + "_" + file);
442 }
443 }
444
445 // 也备份关键设置到内存
446 QStringList settings = {
447 "kglobalshortcutsrc|kwin|Window Close",
448 "kglobalshortcutsrc|kwin|Window Maximize",
449 "kglobalshortcutsrc|kwin|Window Minimize",
450 "kwinrc|ModifierOnlyShortcuts|Meta"
451 };
452
453 for (const QString &setting : settings) {
454 QStringList parts = setting.split("|");
455 if (parts.size() == 3) {
456 QString value = getCommandOutput("kreadconfig5", {"--file", parts[0], "--group", parts[1], "--key", parts[2]});
457 m_kdeSettings[setting] = value.trimmed();
458 }
459 }
460}
461#endif // #if defined(Q_OS_LINUX)
462
463// 通用工具方法
464bool CDesktopShortcutManager::runCommand(const QString &program, const QStringList &args, int timeout)
465{
466 QProcess process;
467 process.setProgram(program);
468 if(!args.isEmpty())
469 process.setArguments(args);
470
471 //qDebug(log) << "Command:" << program << args;
472
473 process.start();
474
475 if (!process.waitForFinished(timeout)) {
476 qWarning(log) << "Command timeout:" << program << args;
477 return false;
478 }
479
480 if (process.exitCode() != 0) {
481 QString errorOutput = process.readAllStandardError();
482 qWarning(log) << "Command failed:" << program << args
483 << "exit code:" << process.exitCode()
484 << "failed:" << errorOutput;
485 return false;
486 }
487
488 QString standardOutput = process.readAllStandardOutput();
489 if (!standardOutput.isEmpty()) {
490 qDebug(log) << "Command output:" << standardOutput.trimmed();
491 }
492
493 return true;
494}
495
496QString CDesktopShortcutManager::getCommandOutput(const QString &program, const QStringList &args)
497{
498 QProcess process;
499 process.start(program, args);
500
501 if (process.waitForFinished(5000) && process.exitCode() == 0) {
502 return process.readAllStandardOutput();
503 }
504
505 return "";
506}
507
508bool CDesktopShortcutManager::backupFile(const QString &sourcePath, const QString &backupPath)
509{
510 return QFile::copy(sourcePath, backupPath);
511}
512
513bool CDesktopShortcutManager::restoreFile(const QString &backupPath, const QString &targetPath)
514{
515 if (QFile::exists(targetPath)) {
516 QFile::remove(targetPath);
517 }
518 return QFile::copy(backupPath, targetPath);
519}