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