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