1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <QWidget>
25 #include <QList>
26 #include <QVariant>
27 #include <QStringList>
28 #include <QAction>
29 #include <QHeaderView>
30 #include <QMessageBox>
31 #include <QProcess>
32 #include <QTreeWidget>
33 #include <QKeySequence>
34 #include <QShortcut>
35 #include <QSettings>
36 #include <QCheckBox>
37 #include <QGroupBox>
38 #include <QPushButton>
39 #include <QRadioButton>
40 #include <QLabel>
41 
42 #include "core/iconloader.h"
43 #include "core/logging.h"
44 #include "core/utilities.h"
45 #include "globalshortcuts/globalshortcutgrabber.h"
46 #include "globalshortcuts/globalshortcutsmanager.h"
47 #include "settingspage.h"
48 #include "settingsdialog.h"
CoversSettingsPage(SettingsDialog * dialog,QWidget * parent)49 #include "globalshortcutssettingspage.h"
50 #include "ui_globalshortcutssettingspage.h"
51 
52 const char *GlobalShortcutsSettingsPage::kSettingsGroup = "GlobalShortcuts";
53 
54 GlobalShortcutsSettingsPage::GlobalShortcutsSettingsPage(SettingsDialog *dialog, QWidget *parent)
55     : SettingsPage(dialog, parent),
56       ui_(new Ui_GlobalShortcutsSettingsPage),
57       initialized_(false),
58       grabber_(new GlobalShortcutGrabber) {
59 
60   ui_->setupUi(this);
61   ui_->shortcut_options->setEnabled(false);
62   ui_->list->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
63   setWindowIcon(IconLoader::Load("keyboard"));
64 
65   QObject::connect(ui_->list, &QTreeWidget::currentItemChanged, this, &GlobalShortcutsSettingsPage::ItemClicked);
66   QObject::connect(ui_->radio_none, &QRadioButton::clicked, this, &GlobalShortcutsSettingsPage::NoneClicked);
67   QObject::connect(ui_->radio_default, &QRadioButton::clicked, this, &GlobalShortcutsSettingsPage::DefaultClicked);
68   QObject::connect(ui_->radio_custom, &QRadioButton::clicked, this, &GlobalShortcutsSettingsPage::ChangeClicked);
69   QObject::connect(ui_->button_change, &QPushButton::clicked, this, &GlobalShortcutsSettingsPage::ChangeClicked);
70 
71 #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
72   QObject::connect(ui_->checkbox_kde, &QCheckBox::toggled, this, &GlobalShortcutsSettingsPage::ShortcutOptionsChanged);
73   QObject::connect(ui_->checkbox_gnome, &QCheckBox::toggled, this, &GlobalShortcutsSettingsPage::ShortcutOptionsChanged);
74   QObject::connect(ui_->checkbox_mate, &QCheckBox::toggled, this, &GlobalShortcutsSettingsPage::ShortcutOptionsChanged);
~CoversSettingsPage()75   QObject::connect(ui_->button_gnome_open, &QPushButton::clicked, this, &GlobalShortcutsSettingsPage::OpenGnomeKeybindingProperties);
76   QObject::connect(ui_->button_mate_open, &QPushButton::clicked, this, &GlobalShortcutsSettingsPage::OpenMateKeybindingProperties);
Load()77 #else
78   ui_->widget_kde->hide();
79   ui_->widget_gnome->hide();
80   ui_->widget_mate->hide();
81 #endif  // defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
82 
83 #ifdef HAVE_X11_GLOBALSHORTCUTS
84   QObject::connect(ui_->checkbox_x11, &QCheckBox::toggled, this, &GlobalShortcutsSettingsPage::ShortcutOptionsChanged);
85 #else
86   ui_->widget_x11->hide();
87 #endif  // HAVE_X11_GLOBALSHORTCUTS
88 
89 #ifndef Q_OS_MACOS
90   ui_->widget_macos_access->hide();
91 #endif  // Q_OS_MACOS
92 
93 }
94 
95 GlobalShortcutsSettingsPage::~GlobalShortcutsSettingsPage() { delete ui_; }
96 
Save()97 void GlobalShortcutsSettingsPage::Load() {
98 
99   QSettings s;
100   s.beginGroup(kSettingsGroup);
101 
102   GlobalShortcutsManager *manager = dialog()->global_shortcuts_manager();
103 
104   if (!initialized_) {
105     initialized_ = true;
106 
107     de_ = Utilities::DesktopEnvironment();
108     ui_->widget_warning->hide();
109 
110 #ifdef Q_OS_MACOS
111     QObject::connect(ui_->button_macos_preferences, &QPushButton::clicked, manager, &GlobalShortcutsManager::ShowMacAccessibilityDialog);
112 #endif
113 
114 #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
115 
116     if (GlobalShortcutsManager::IsKdeAvailable()) {
117       qLog(Debug) << "KDE (KGlobalAccel) backend is available.";
118       ui_->widget_kde->show();
119     }
120     else {
121       qLog(Debug) << "KDE (KGlobalAccel) backend is unavailable.";
122       ui_->widget_kde->hide();
123     }
124 
125     if (GlobalShortcutsManager::IsGnomeAvailable()) {
126       qLog(Debug) << "Gnome (GSD) backend is available.";
127       ui_->widget_gnome->show();
128     }
129     else {
130       qLog(Debug) << "Gnome (GSD) backend is unavailable.";
131       ui_->widget_gnome->hide();
132     }
133 
134     if (GlobalShortcutsManager::IsMateAvailable()) {
135       qLog(Debug) << "MATE backend is available.";
136       ui_->widget_mate->show();
137     }
138     else {
139       qLog(Debug) << "MATE backend is unavailable.";
140       ui_->widget_mate->hide();
141     }
142 
143 #endif  // defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
144 
145 #ifdef HAVE_X11_GLOBALSHORTCUTS
146     if (GlobalShortcutsManager::IsX11Available()) {
147       qLog(Debug) << "X11 backend is available.";
148       ui_->widget_x11->show();
149     }
150     else {
151       qLog(Debug) << "X11 backend is unavailable.";
152       ui_->widget_x11->hide();
153     }
154 #endif  // HAVE_X11_GLOBALSHORTCUTS
155 
156     QList<GlobalShortcutsManager::Shortcut> shortcuts = manager->shortcuts().values();
157     for (const GlobalShortcutsManager::Shortcut &i : shortcuts) {
158       Shortcut shortcut;
159       shortcut.s = i;
160       shortcut.key = i.action->shortcut();
161       shortcut.item = new QTreeWidgetItem(ui_->list, QStringList() << i.action->text() << i.action->shortcut().toString(QKeySequence::NativeText));
162       shortcut.item->setData(0, Qt::UserRole, i.id);
163       shortcuts_[i.id] = shortcut;
164     }
165 
166     ui_->list->sortItems(0, Qt::AscendingOrder);
167     ItemClicked(ui_->list->topLevelItem(0));
168   }
169 
170   QList<Shortcut> shortcuts = shortcuts_.values();
171   for (const Shortcut &shortcut : shortcuts) {
172     SetShortcut(shortcut.s.id, shortcut.s.action->shortcut());
173   }
174 
175 #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
176 
177   if (ui_->widget_kde->isVisibleTo(this)) {
178     ui_->checkbox_kde->setChecked(s.value("use_kde", true).toBool());
179   }
180 
181   if (ui_->widget_gnome->isVisibleTo(this)) {
182     ui_->checkbox_gnome->setChecked(s.value("use_gnome", true).toBool());
183   }
184 
185   if (ui_->widget_mate->isVisibleTo(this)) {
186     ui_->checkbox_mate->setChecked(s.value("use_mate", true).toBool());
187   }
188 
189 #endif // defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
190 
191 #ifdef HAVE_X11_GLOBALSHORTCUTS
192   if (ui_->widget_x11->isVisibleTo(this)) {
193     ui_->checkbox_x11->setChecked(s.value("use_x11", false).toBool());
194   }
195 #endif  // HAVE_X11_GLOBALSHORTCUTS
196 
197 #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && (defined(HAVE_DBUS) || defined(HAVE_X11_GLOBALSHORTCUTS))
198   ShortcutOptionsChanged();
199 #endif
200 
201 #ifdef Q_OS_MACOS
202   ui_->widget_macos_access->setVisible(!GlobalShortcutsManager::IsMacAccessibilityEnabled());
203 #endif  // Q_OS_MACOS
204 
205   s.endGroup();
206 
207   Init(ui_->layout_globalshortcutssettingspage->parentWidget());
208 
209   if (!QSettings().childGroups().contains(kSettingsGroup)) set_changed();
210 
211 }
DisconnectAuthentication(CoverProvider * provider) const212 
213 void GlobalShortcutsSettingsPage::Save() {
214 
215   QSettings s;
216   s.beginGroup(kSettingsGroup);
217 
218   QList<Shortcut> shortcuts = shortcuts_.values();
219   for (const Shortcut &shortcut : shortcuts) {
220     shortcut.s.action->setShortcut(shortcut.key);
221     shortcut.s.shortcut->setKey(shortcut.key);
222     s.setValue(shortcut.s.id, shortcut.key.toString());
223   }
224 
225 #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
226   s.setValue("use_kde", ui_->checkbox_kde->isChecked());
227   s.setValue("use_gnome", ui_->checkbox_gnome->isChecked());
228   s.setValue("use_mate", ui_->checkbox_mate->isChecked());
229 #endif  // defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) && defined(HAVE_DBUS)
230 
231 #ifdef HAVE_X11_GLOBALSHORTCUTS
232   s.setValue("use_x11", ui_->checkbox_x11->isChecked());
233 #endif  // HAVE_X11_GLOBALSHORTCUTS
234 
235   s.endGroup();
236 
237   dialog()->global_shortcuts_manager()->ReloadSettings();
238 
239 }
240 
241 void GlobalShortcutsSettingsPage::ShortcutOptionsChanged() {
242 
243   bool configure_shortcuts = (ui_->widget_kde->isVisibleTo(this) && ui_->checkbox_kde->isChecked()) ||
244                              (ui_->widget_x11->isVisibleTo(this) && ui_->checkbox_x11->isChecked());
245 
246   ui_->list->setEnabled(configure_shortcuts);
247   ui_->shortcut_options->setEnabled(configure_shortcuts);
248 
249   if (ui_->widget_x11->isVisibleTo(this) && ui_->checkbox_x11->isChecked()) {
250     ui_->widget_warning->show();
251     X11Warning();
252   }
253   else {
AuthenticationSuccess()254     ui_->widget_warning->hide();
255   }
256 
257 }
258 
259 void GlobalShortcutsSettingsPage::OpenGnomeKeybindingProperties() {
260 
261   if (!QProcess::startDetached("gnome-keybinding-properties", QStringList())) {
262     if (!QProcess::startDetached("gnome-control-center", QStringList() << "keyboard")) {
263       QMessageBox::warning(this, "Error", tr("The \"%1\" command could not be started.").arg("gnome-keybinding-properties"));
264     }
265   }
266 
AuthenticationFailure(const QStringList & errors)267 }
268 
269 void GlobalShortcutsSettingsPage::OpenMateKeybindingProperties() {
270 
271   if (!QProcess::startDetached("mate-keybinding-properties", QStringList())) {
272     if (!QProcess::startDetached("mate-control-center", QStringList() << "keyboard")) {
273       QMessageBox::warning(this, "Error", tr("The \"%1\" command could not be started.").arg("mate-keybinding-properties"));
274     }
275   }
276 
277 }
278 
279 void GlobalShortcutsSettingsPage::SetShortcut(const QString &id, const QKeySequence &key) {
280 
281   Shortcut &shortcut = shortcuts_[id];
ProviderCompareOrder(CoverProvider * a,CoverProvider * b)282 
283   shortcut.key = key;
284   shortcut.item->setText(1, key.toString(QKeySequence::NativeText));
285 
286 }
287 
288 void GlobalShortcutsSettingsPage::ItemClicked(QTreeWidgetItem *item) {
289 
290   current_id_ = item->data(0, Qt::UserRole).toString();
291   Shortcut &shortcut = shortcuts_[current_id_];
292 
293   // Enable options
294   ui_->shortcut_options->setEnabled(true);
295   ui_->shortcut_options->setTitle(tr("Shortcut for %1").arg(shortcut.s.action->text()));
296 
297   if (shortcut.key == shortcut.s.default_key) {
298     ui_->radio_default->setChecked(true);
299   }
300   else if (shortcut.key.isEmpty()) {
301     ui_->radio_none->setChecked(true);
302   }
303   else {
304     ui_->radio_custom->setChecked(true);
305   }
306 
307 }
308 
309 void GlobalShortcutsSettingsPage::NoneClicked() {
310 
311   SetShortcut(current_id_, QKeySequence());
312   set_changed();
313 
314 }
315 
316 void GlobalShortcutsSettingsPage::DefaultClicked() {
317 
318   SetShortcut(current_id_, shortcuts_[current_id_].s.default_key);
319   set_changed();
320 
321 }
322 
323 void GlobalShortcutsSettingsPage::ChangeClicked() {
324 
325   GlobalShortcutsManager *manager = dialog()->global_shortcuts_manager();
326   manager->Unregister();
327   QKeySequence key = grabber_->GetKey(shortcuts_[current_id_].s.action->text());
328   manager->Register();
329 
330   if (key.isEmpty()) return;
331 
332   // Check if this key sequence is used by any other actions
333   QStringList ids = shortcuts_.keys();
334   for (const QString &id : ids) {
335     if (shortcuts_[id].key == key) SetShortcut(id, QKeySequence());
336   }
337 
338   ui_->radio_custom->setChecked(true);
339   SetShortcut(current_id_, key);
340 
341   set_changed();
342 
343 }
344 
345 void GlobalShortcutsSettingsPage::X11Warning() {
346 
347   QString de = de_.toLower();
348   if (de == "kde" || de == "gnome" || de == "x-cinnamon" || de == "mate") {
349     QString text(tr("Using X11 shortcuts on %1 is not recommended and can cause keyboard to become unresponsive!").arg(de_));
350     if (de == "kde") {
351       text += tr(" Shortcuts on %1 are usually used through MPRIS and KGlobalAccel.").arg(de_);
352     }
353     else if (de == "gnome") {
354       text += tr(" Shortcuts on %1 are usually used through Gnome Settings Daemon and should be configured in gnome-settings-daemon instead.").arg(de_);
355     }
356     else if (de == "x-cinnamon") {
357       text += tr(" Shortcuts on %1 are usually used through Gnome Settings Daemon and should be configured in cinnamon-settings-daemon instead.").arg(de_);
358     }
359     else if (de == "mate") {
360       text += tr(" Shortcuts on %1 are usually used through MATE Settings Daemon and should be configured there instead.").arg(de_);
361     }
362     ui_->label_warn_text->setText(text);
363     ui_->widget_warning->show();
364   }
365   else {
366     ui_->widget_warning->hide();
367   }
368 
369 }
370