1 // Copyright 2016 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <algorithm>
6 #include <memory>
7 
8 #include <QKeyEvent>
9 #include <QMenu>
10 #include <QTimer>
11 
12 #include "common/assert.h"
13 #include "common/param_package.h"
14 #include "input_common/main.h"
15 #include "ui_configure_mouse_advanced.h"
16 #include "yuzu/configuration/config.h"
17 #include "yuzu/configuration/configure_mouse_advanced.h"
18 
GetKeyName(int key_code)19 static QString GetKeyName(int key_code) {
20     switch (key_code) {
21     case Qt::LeftButton:
22         return QObject::tr("Click 0");
23     case Qt::RightButton:
24         return QObject::tr("Click 1");
25     case Qt::MiddleButton:
26         return QObject::tr("Click 2");
27     case Qt::BackButton:
28         return QObject::tr("Click 3");
29     case Qt::ForwardButton:
30         return QObject::tr("Click 4");
31     case Qt::Key_Shift:
32         return QObject::tr("Shift");
33     case Qt::Key_Control:
34         return QObject::tr("Ctrl");
35     case Qt::Key_Alt:
36         return QObject::tr("Alt");
37     case Qt::Key_Meta:
38         return {};
39     default:
40         return QKeySequence(key_code).toString();
41     }
42 }
43 
ButtonToText(const Common::ParamPackage & param)44 static QString ButtonToText(const Common::ParamPackage& param) {
45     if (!param.Has("engine")) {
46         return QObject::tr("[not set]");
47     }
48 
49     if (param.Get("engine", "") == "keyboard") {
50         return GetKeyName(param.Get("code", 0));
51     }
52 
53     if (param.Get("engine", "") == "sdl") {
54         if (param.Has("hat")) {
55             const QString hat_str = QString::fromStdString(param.Get("hat", ""));
56             const QString direction_str = QString::fromStdString(param.Get("direction", ""));
57 
58             return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
59         }
60 
61         if (param.Has("axis")) {
62             const QString axis_str = QString::fromStdString(param.Get("axis", ""));
63             const QString direction_str = QString::fromStdString(param.Get("direction", ""));
64 
65             return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
66         }
67 
68         if (param.Has("button")) {
69             const QString button_str = QString::fromStdString(param.Get("button", ""));
70 
71             return QObject::tr("Button %1").arg(button_str);
72         }
73         return {};
74     }
75 
76     return QObject::tr("[unknown]");
77 }
78 
ConfigureMouseAdvanced(QWidget * parent,InputCommon::InputSubsystem * input_subsystem_)79 ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
80                                                InputCommon::InputSubsystem* input_subsystem_)
81     : QDialog(parent),
82       ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},
83       timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
84     ui->setupUi(this);
85     setFocusPolicy(Qt::ClickFocus);
86 
87     button_map = {
88         ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button,
89     };
90 
91     for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
92         auto* const button = button_map[button_id];
93         if (button == nullptr) {
94             continue;
95         }
96 
97         button->setContextMenuPolicy(Qt::CustomContextMenu);
__anon655fa8b90102null98         connect(button, &QPushButton::clicked, [=, this] {
99             HandleClick(
100                 button_map[button_id],
101                 [=, this](const Common::ParamPackage& params) {
102                     buttons_param[button_id] = params;
103                 },
104                 InputCommon::Polling::DeviceType::Button);
105         });
106         connect(button, &QPushButton::customContextMenuRequested,
__anon655fa8b90302(const QPoint& menu_location) 107                 [=, this](const QPoint& menu_location) {
108                     QMenu context_menu;
109                     context_menu.addAction(tr("Clear"), [&] {
110                         buttons_param[button_id].Clear();
111                         button_map[button_id]->setText(tr("[not set]"));
112                     });
113                     context_menu.addAction(tr("Restore Default"), [&] {
114                         buttons_param[button_id] =
115                             Common::ParamPackage{InputCommon::GenerateKeyboardParam(
116                                 Config::default_mouse_buttons[button_id])};
117                         button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
118                     });
119                     context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
120                 });
121     }
122 
__anon655fa8b90602null123     connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
__anon655fa8b90702null124     connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
125 
126     timeout_timer->setSingleShot(true);
__anon655fa8b90802null127     connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
128 
__anon655fa8b90902null129     connect(poll_timer.get(), &QTimer::timeout, [this] {
130         Common::ParamPackage params;
131         for (auto& poller : device_pollers) {
132             params = poller->GetNextInput();
133             if (params.Has("engine")) {
134                 SetPollingResult(params, false);
135                 return;
136             }
137         }
138     });
139 
140     LoadConfiguration();
141     resize(0, 0);
142 }
143 
144 ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default;
145 
ApplyConfiguration()146 void ConfigureMouseAdvanced::ApplyConfiguration() {
147     std::transform(buttons_param.begin(), buttons_param.end(),
148                    Settings::values.mouse_buttons.begin(),
149                    [](const Common::ParamPackage& param) { return param.Serialize(); });
150 }
151 
LoadConfiguration()152 void ConfigureMouseAdvanced::LoadConfiguration() {
153     std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
154                    buttons_param.begin(),
155                    [](const std::string& str) { return Common::ParamPackage(str); });
156     UpdateButtonLabels();
157 }
158 
changeEvent(QEvent * event)159 void ConfigureMouseAdvanced::changeEvent(QEvent* event) {
160     if (event->type() == QEvent::LanguageChange) {
161         RetranslateUI();
162     }
163 
164     QDialog::changeEvent(event);
165 }
166 
RetranslateUI()167 void ConfigureMouseAdvanced::RetranslateUI() {
168     ui->retranslateUi(this);
169 }
170 
RestoreDefaults()171 void ConfigureMouseAdvanced::RestoreDefaults() {
172     for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
173         buttons_param[button_id] = Common::ParamPackage{
174             InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
175     }
176 
177     UpdateButtonLabels();
178 }
179 
ClearAll()180 void ConfigureMouseAdvanced::ClearAll() {
181     for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
182         const auto* const button = button_map[i];
183         if (button != nullptr && button->isEnabled()) {
184             buttons_param[i].Clear();
185         }
186     }
187 
188     UpdateButtonLabels();
189 }
190 
UpdateButtonLabels()191 void ConfigureMouseAdvanced::UpdateButtonLabels() {
192     for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {
193         button_map[button]->setText(ButtonToText(buttons_param[button]));
194     }
195 }
196 
HandleClick(QPushButton * button,std::function<void (const Common::ParamPackage &)> new_input_setter,InputCommon::Polling::DeviceType type)197 void ConfigureMouseAdvanced::HandleClick(
198     QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
199     InputCommon::Polling::DeviceType type) {
200     button->setText(tr("[press key]"));
201     button->setFocus();
202 
203     // Keyboard keys or mouse buttons can only be used as button devices
204     want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
205     if (want_keyboard_mouse) {
206         const auto iter = std::find(button_map.begin(), button_map.end(), button);
207         ASSERT(iter != button_map.end());
208         const auto index = std::distance(button_map.begin(), iter);
209         ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
210     }
211 
212     input_setter = new_input_setter;
213 
214     device_pollers = input_subsystem->GetPollers(type);
215 
216     for (auto& poller : device_pollers) {
217         poller->Start();
218     }
219 
220     QWidget::grabMouse();
221     QWidget::grabKeyboard();
222 
223     timeout_timer->start(2500); // Cancel after 2.5 seconds
224     poll_timer->start(50);      // Check for new inputs every 50ms
225 }
226 
SetPollingResult(const Common::ParamPackage & params,bool abort)227 void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
228     timeout_timer->stop();
229     poll_timer->stop();
230     for (auto& poller : device_pollers) {
231         poller->Stop();
232     }
233 
234     QWidget::releaseMouse();
235     QWidget::releaseKeyboard();
236 
237     if (!abort) {
238         (*input_setter)(params);
239     }
240 
241     UpdateButtonLabels();
242     input_setter = std::nullopt;
243 }
244 
mousePressEvent(QMouseEvent * event)245 void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
246     if (!input_setter || !event) {
247         return;
248     }
249 
250     if (want_keyboard_mouse) {
251         SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
252                          false);
253     } else {
254         // We don't want any mouse buttons, so don't stop polling
255         return;
256     }
257 
258     SetPollingResult({}, true);
259 }
260 
keyPressEvent(QKeyEvent * event)261 void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
262     if (!input_setter || !event) {
263         return;
264     }
265 
266     if (event->key() != Qt::Key_Escape) {
267         if (want_keyboard_mouse) {
268             SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
269                              false);
270         } else {
271             // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
272             return;
273         }
274     }
275     SetPollingResult({}, true);
276 }
277