summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/thread.cpp12
-rw-r--r--src/common/thread.h9
-rw-r--r--src/core/arm/cpu_interrupt_handler.cpp6
-rw-r--r--src/core/arm/cpu_interrupt_handler.h3
-rw-r--r--src/core/cpu_manager.cpp2
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/core/hle/kernel/kernel.cpp3
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp12
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h1
-rw-r--r--src/core/settings.h12
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/main.cpp261
-rw-r--r--src/input_common/main.h144
-rw-r--r--src/input_common/sdl/sdl_impl.cpp116
-rw-r--r--src/input_common/settings.h4
-rw-r--r--src/input_common/touch_from_button.cpp50
-rw-r--r--src/input_common/touch_from_button.h23
-rw-r--r--src/video_core/renderer_vulkan/vk_device.cpp8
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/bootmanager.cpp25
-rw-r--r--src/yuzu/bootmanager.h9
-rw-r--r--src/yuzu/configuration/config.cpp115
-rw-r--r--src/yuzu/configuration/config.h5
-rw-r--r--src/yuzu/configuration/configure_debug_controller.cpp5
-rw-r--r--src/yuzu/configuration/configure_debug_controller.h7
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp5
-rw-r--r--src/yuzu/configuration/configure_dialog.h7
-rw-r--r--src/yuzu/configuration/configure_input.cpp37
-rw-r--r--src/yuzu/configuration/configure_input.h9
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.h1
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp42
-rw-r--r--src/yuzu/configuration/configure_input_player.h10
-rw-r--r--src/yuzu/configuration/configure_motion_touch.cpp314
-rw-r--r--src/yuzu/configuration/configure_motion_touch.h90
-rw-r--r--src/yuzu/configuration/configure_motion_touch.ui327
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.cpp8
-rw-r--r--src/yuzu/configuration/configure_mouse_advanced.h10
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.cpp623
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.h92
-rw-r--r--src/yuzu/configuration/configure_touch_from_button.ui231
-rw-r--r--src/yuzu/configuration/configure_touch_widget.h62
-rw-r--r--src/yuzu/main.cpp11
-rw-r--r--src/yuzu/main.h17
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp18
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h10
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h7
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp5
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h7
-rw-r--r--src/yuzu_cmd/yuzu.cpp9
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp5
-rw-r--r--src/yuzu_tester/emu_window/emu_window_sdl2_hide.h6
53 files changed, 2474 insertions, 338 deletions
diff --git a/src/common/thread.cpp b/src/common/thread.cpp
index 8e5935e6a..d2c1ac60d 100644
--- a/src/common/thread.cpp
+++ b/src/common/thread.cpp
@@ -2,6 +2,8 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/common_funcs.h"
6#include "common/logging/log.h"
5#include "common/thread.h" 7#include "common/thread.h"
6#ifdef __APPLE__ 8#ifdef __APPLE__
7#include <mach/mach.h> 9#include <mach/mach.h>
@@ -19,6 +21,8 @@
19#include <unistd.h> 21#include <unistd.h>
20#endif 22#endif
21 23
24#include <string>
25
22#ifdef __FreeBSD__ 26#ifdef __FreeBSD__
23#define cpu_set_t cpuset_t 27#define cpu_set_t cpuset_t
24#endif 28#endif
@@ -110,6 +114,14 @@ void SetCurrentThreadName(const char* name) {
110 pthread_set_name_np(pthread_self(), name); 114 pthread_set_name_np(pthread_self(), name);
111#elif defined(__NetBSD__) 115#elif defined(__NetBSD__)
112 pthread_setname_np(pthread_self(), "%s", (void*)name); 116 pthread_setname_np(pthread_self(), "%s", (void*)name);
117#elif defined(__linux__)
118 // Linux limits thread names to 15 characters and will outright reject any
119 // attempt to set a longer name with ERANGE.
120 std::string truncated(name, std::min(strlen(name), static_cast<size_t>(15)));
121 if (int e = pthread_setname_np(pthread_self(), truncated.c_str())) {
122 errno = e;
123 LOG_ERROR(Common, "Failed to set thread name to '{}': {}", truncated, GetLastErrorMsg());
124 }
113#else 125#else
114 pthread_setname_np(pthread_self(), name); 126 pthread_setname_np(pthread_self(), name);
115#endif 127#endif
diff --git a/src/common/thread.h b/src/common/thread.h
index 52b359413..a8c17c71a 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <atomic>
7#include <chrono> 8#include <chrono>
8#include <condition_variable> 9#include <condition_variable>
9#include <cstddef> 10#include <cstddef>
@@ -25,13 +26,13 @@ public:
25 26
26 void Wait() { 27 void Wait() {
27 std::unique_lock lk{mutex}; 28 std::unique_lock lk{mutex};
28 condvar.wait(lk, [&] { return is_set; }); 29 condvar.wait(lk, [&] { return is_set.load(); });
29 is_set = false; 30 is_set = false;
30 } 31 }
31 32
32 bool WaitFor(const std::chrono::nanoseconds& time) { 33 bool WaitFor(const std::chrono::nanoseconds& time) {
33 std::unique_lock lk{mutex}; 34 std::unique_lock lk{mutex};
34 if (!condvar.wait_for(lk, time, [this] { return is_set; })) 35 if (!condvar.wait_for(lk, time, [this] { return is_set.load(); }))
35 return false; 36 return false;
36 is_set = false; 37 is_set = false;
37 return true; 38 return true;
@@ -40,7 +41,7 @@ public:
40 template <class Clock, class Duration> 41 template <class Clock, class Duration>
41 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { 42 bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
42 std::unique_lock lk{mutex}; 43 std::unique_lock lk{mutex};
43 if (!condvar.wait_until(lk, time, [this] { return is_set; })) 44 if (!condvar.wait_until(lk, time, [this] { return is_set.load(); }))
44 return false; 45 return false;
45 is_set = false; 46 is_set = false;
46 return true; 47 return true;
@@ -54,9 +55,9 @@ public:
54 } 55 }
55 56
56private: 57private:
57 bool is_set = false;
58 std::condition_variable condvar; 58 std::condition_variable condvar;
59 std::mutex mutex; 59 std::mutex mutex;
60 std::atomic_bool is_set{false};
60}; 61};
61 62
62class Barrier { 63class Barrier {
diff --git a/src/core/arm/cpu_interrupt_handler.cpp b/src/core/arm/cpu_interrupt_handler.cpp
index df0350881..9c8898700 100644
--- a/src/core/arm/cpu_interrupt_handler.cpp
+++ b/src/core/arm/cpu_interrupt_handler.cpp
@@ -7,9 +7,7 @@
7 7
8namespace Core { 8namespace Core {
9 9
10CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} { 10CPUInterruptHandler::CPUInterruptHandler() : interrupt_event{std::make_unique<Common::Event>()} {}
11 interrupt_event = std::make_unique<Common::Event>();
12}
13 11
14CPUInterruptHandler::~CPUInterruptHandler() = default; 12CPUInterruptHandler::~CPUInterruptHandler() = default;
15 13
@@ -17,7 +15,7 @@ void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
17 if (is_interrupted_) { 15 if (is_interrupted_) {
18 interrupt_event->Set(); 16 interrupt_event->Set();
19 } 17 }
20 this->is_interrupted = is_interrupted_; 18 is_interrupted = is_interrupted_;
21} 19}
22 20
23void CPUInterruptHandler::AwaitInterrupt() { 21void CPUInterruptHandler::AwaitInterrupt() {
diff --git a/src/core/arm/cpu_interrupt_handler.h b/src/core/arm/cpu_interrupt_handler.h
index 3d062d326..71e582f79 100644
--- a/src/core/arm/cpu_interrupt_handler.h
+++ b/src/core/arm/cpu_interrupt_handler.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <atomic>
7#include <memory> 8#include <memory>
8 9
9namespace Common { 10namespace Common {
@@ -32,8 +33,8 @@ public:
32 void AwaitInterrupt(); 33 void AwaitInterrupt();
33 34
34private: 35private:
35 bool is_interrupted{};
36 std::unique_ptr<Common::Event> interrupt_event; 36 std::unique_ptr<Common::Event> interrupt_event;
37 std::atomic_bool is_interrupted{false};
37}; 38};
38 39
39} // namespace Core 40} // namespace Core
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index ef0bae556..688b99eba 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -328,7 +328,7 @@ void CpuManager::RunThread(std::size_t core) {
328 system.RegisterCoreThread(core); 328 system.RegisterCoreThread(core);
329 std::string name; 329 std::string name;
330 if (is_multicore) { 330 if (is_multicore) {
331 name = "yuzu:CoreCPUThread_" + std::to_string(core); 331 name = "yuzu:CPUCore_" + std::to_string(core);
332 } else { 332 } else {
333 name = "yuzu:CPUThread"; 333 name = "yuzu:CPUThread";
334 } 334 }
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 91ecc30ab..e2e3bbbb3 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -4,6 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "common/common_types.h"
7#include "common/math_util.h" 8#include "common/math_util.h"
8 9
9namespace Layout { 10namespace Layout {
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index cabe8d418..f2b0fe2fd 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -219,6 +219,7 @@ struct KernelCore::Impl {
219 return static_cast<u32>(system.GetCpuManager().CurrentCore()); 219 return static_cast<u32>(system.GetCpuManager().CurrentCore());
220 } 220 }
221 } 221 }
222 std::unique_lock lock{register_thread_mutex};
222 const auto it = host_thread_ids.find(this_id); 223 const auto it = host_thread_ids.find(this_id);
223 if (it == host_thread_ids.end()) { 224 if (it == host_thread_ids.end()) {
224 return Core::INVALID_HOST_THREAD_ID; 225 return Core::INVALID_HOST_THREAD_ID;
@@ -324,7 +325,7 @@ struct KernelCore::Impl {
324 std::unordered_map<std::thread::id, u32> host_thread_ids; 325 std::unordered_map<std::thread::id, u32> host_thread_ids;
325 u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES}; 326 u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES};
326 std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads; 327 std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads;
327 std::mutex register_thread_mutex; 328 mutable std::mutex register_thread_mutex;
328 329
329 // Kernel memory management 330 // Kernel memory management
330 std::unique_ptr<Memory::MemoryManager> memory_manager; 331 std::unique_ptr<Memory::MemoryManager> memory_manager;
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
index e326f8f5c..0df395e85 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.cpp
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -40,9 +40,14 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
40 cur_entry.sampling_number = last_entry.sampling_number + 1; 40 cur_entry.sampling_number = last_entry.sampling_number + 1;
41 cur_entry.sampling_number2 = cur_entry.sampling_number; 41 cur_entry.sampling_number2 = cur_entry.sampling_number;
42 42
43 const auto [x, y, pressed] = touch_device->GetStatus(); 43 bool pressed = false;
44 float x, y;
45 std::tie(x, y, pressed) = touch_device->GetStatus();
44 auto& touch_entry = cur_entry.states[0]; 46 auto& touch_entry = cur_entry.states[0];
45 touch_entry.attribute.raw = 0; 47 touch_entry.attribute.raw = 0;
48 if (!pressed && touch_btn_device) {
49 std::tie(x, y, pressed) = touch_btn_device->GetStatus();
50 }
46 if (pressed && Settings::values.touchscreen.enabled) { 51 if (pressed && Settings::values.touchscreen.enabled) {
47 touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); 52 touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
48 touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); 53 touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
@@ -63,5 +68,10 @@ void Controller_Touchscreen::OnUpdate(const Core::Timing::CoreTiming& core_timin
63 68
64void Controller_Touchscreen::OnLoadInputDevices() { 69void Controller_Touchscreen::OnLoadInputDevices() {
65 touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); 70 touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device);
71 if (Settings::values.use_touch_from_button) {
72 touch_btn_device = Input::CreateDevice<Input::TouchDevice>("engine:touch_from_button");
73 } else {
74 touch_btn_device.reset();
75 }
66} 76}
67} // namespace Service::HID 77} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index a1d97269e..4d9042adc 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -68,6 +68,7 @@ private:
68 "TouchScreenSharedMemory is an invalid size"); 68 "TouchScreenSharedMemory is an invalid size");
69 TouchScreenSharedMemory shared_memory{}; 69 TouchScreenSharedMemory shared_memory{};
70 std::unique_ptr<Input::TouchDevice> touch_device; 70 std::unique_ptr<Input::TouchDevice> touch_device;
71 std::unique_ptr<Input::TouchDevice> touch_btn_device;
71 s64_le last_touch{}; 72 s64_le last_touch{};
72}; 73};
73} // namespace Service::HID 74} // namespace Service::HID
diff --git a/src/core/settings.h b/src/core/settings.h
index 732c6a894..80f0d95a7 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -67,6 +67,11 @@ private:
67 Type local{}; 67 Type local{};
68}; 68};
69 69
70struct TouchFromButtonMap {
71 std::string name;
72 std::vector<std::string> buttons;
73};
74
70struct Values { 75struct Values {
71 // Audio 76 // Audio
72 std::string audio_device_id; 77 std::string audio_device_id;
@@ -145,15 +150,18 @@ struct Values {
145 ButtonsRaw debug_pad_buttons; 150 ButtonsRaw debug_pad_buttons;
146 AnalogsRaw debug_pad_analogs; 151 AnalogsRaw debug_pad_analogs;
147 152
148 std::string motion_device;
149
150 bool vibration_enabled; 153 bool vibration_enabled;
151 154
155 std::string motion_device;
156 std::string touch_device;
152 TouchscreenInput touchscreen; 157 TouchscreenInput touchscreen;
153 std::atomic_bool is_device_reload_pending{true}; 158 std::atomic_bool is_device_reload_pending{true};
159 bool use_touch_from_button;
160 int touch_from_button_map_index;
154 std::string udp_input_address; 161 std::string udp_input_address;
155 u16 udp_input_port; 162 u16 udp_input_port;
156 u8 udp_pad_index; 163 u8 udp_pad_index;
164 std::vector<TouchFromButtonMap> touch_from_button_maps;
157 165
158 // Data Storage 166 // Data Storage
159 bool use_virtual_sd; 167 bool use_virtual_sd;
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index c673b8389..09361e37e 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -11,6 +11,8 @@ add_library(input_common STATIC
11 motion_input.h 11 motion_input.h
12 settings.cpp 12 settings.cpp
13 settings.h 13 settings.h
14 touch_from_button.cpp
15 touch_from_button.h
14 gcadapter/gc_adapter.cpp 16 gcadapter/gc_adapter.cpp
15 gcadapter/gc_adapter.h 17 gcadapter/gc_adapter.h
16 gcadapter/gc_poller.cpp 18 gcadapter/gc_poller.cpp
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 8e67a7437..ea1a1cee6 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,7 @@
11#include "input_common/keyboard.h" 11#include "input_common/keyboard.h"
12#include "input_common/main.h" 12#include "input_common/main.h"
13#include "input_common/motion_emu.h" 13#include "input_common/motion_emu.h"
14#include "input_common/touch_from_button.h"
14#include "input_common/udp/udp.h" 15#include "input_common/udp/udp.h"
15#ifdef HAVE_SDL2 16#ifdef HAVE_SDL2
16#include "input_common/sdl/sdl.h" 17#include "input_common/sdl/sdl.h"
@@ -18,66 +19,176 @@
18 19
19namespace InputCommon { 20namespace InputCommon {
20 21
21static std::shared_ptr<Keyboard> keyboard; 22struct InputSubsystem::Impl {
22static std::shared_ptr<MotionEmu> motion_emu; 23 void Initialize() {
24 auto gcadapter = std::make_shared<GCAdapter::Adapter>();
25 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
26 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
27 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
28 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
29
30 keyboard = std::make_shared<Keyboard>();
31 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
32 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
33 std::make_shared<AnalogFromButton>());
34 motion_emu = std::make_shared<MotionEmu>();
35 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
36 Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
37 std::make_shared<TouchFromButtonFactory>());
38
23#ifdef HAVE_SDL2 39#ifdef HAVE_SDL2
24static std::unique_ptr<SDL::State> sdl; 40 sdl = SDL::Init();
25#endif 41#endif
26static std::unique_ptr<CemuhookUDP::State> udp;
27static std::shared_ptr<GCButtonFactory> gcbuttons;
28static std::shared_ptr<GCAnalogFactory> gcanalog;
29
30void Init() {
31 auto gcadapter = std::make_shared<GCAdapter::Adapter>();
32 gcbuttons = std::make_shared<GCButtonFactory>(gcadapter);
33 Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
34 gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
35 Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
36
37 keyboard = std::make_shared<Keyboard>();
38 Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
39 Input::RegisterFactory<Input::AnalogDevice>("analog_from_button",
40 std::make_shared<AnalogFromButton>());
41 motion_emu = std::make_shared<MotionEmu>();
42 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
43 42
43 udp = CemuhookUDP::Init();
44 }
45
46 void Shutdown() {
47 Input::UnregisterFactory<Input::ButtonDevice>("keyboard");
48 keyboard.reset();
49 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
50 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
51 motion_emu.reset();
52 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
44#ifdef HAVE_SDL2 53#ifdef HAVE_SDL2
45 sdl = SDL::Init(); 54 sdl.reset();
46#endif 55#endif
47 udp = CemuhookUDP::Init(); 56 udp.reset();
48} 57 Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
58 Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
59
60 gcbuttons.reset();
61 gcanalog.reset();
62 }
63
64 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
65 std::vector<Common::ParamPackage> devices = {
66 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
67 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
68 };
69#ifdef HAVE_SDL2
70 auto sdl_devices = sdl->GetInputDevices();
71 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
72#endif
73 auto udp_devices = udp->GetInputDevices();
74 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
75 return devices;
76 }
77
78 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
79 const Common::ParamPackage& params) const {
80 if (!params.Has("class") || params.Get("class", "") == "any") {
81 return {};
82 }
83 if (params.Get("class", "") == "key") {
84 // TODO consider returning the SDL key codes for the default keybindings
85 return {};
86 }
87#ifdef HAVE_SDL2
88 if (params.Get("class", "") == "sdl") {
89 return sdl->GetAnalogMappingForDevice(params);
90 }
91#endif
92 return {};
93 }
94
95 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
96 const Common::ParamPackage& params) const {
97 if (!params.Has("class") || params.Get("class", "") == "any") {
98 return {};
99 }
100 if (params.Get("class", "") == "key") {
101 // TODO consider returning the SDL key codes for the default keybindings
102 return {};
103 }
104#ifdef HAVE_SDL2
105 if (params.Get("class", "") == "sdl") {
106 return sdl->GetButtonMappingForDevice(params);
107 }
108#endif
109 return {};
110 }
49 111
50void Shutdown() { 112 std::shared_ptr<Keyboard> keyboard;
51 Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); 113 std::shared_ptr<MotionEmu> motion_emu;
52 keyboard.reset();
53 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
54 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
55 motion_emu.reset();
56#ifdef HAVE_SDL2 114#ifdef HAVE_SDL2
57 sdl.reset(); 115 std::unique_ptr<SDL::State> sdl;
58#endif 116#endif
59 udp.reset(); 117 std::unique_ptr<CemuhookUDP::State> udp;
60 Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); 118 std::shared_ptr<GCButtonFactory> gcbuttons;
61 Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); 119 std::shared_ptr<GCAnalogFactory> gcanalog;
120};
62 121
63 gcbuttons.reset(); 122InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
64 gcanalog.reset(); 123
124InputSubsystem::~InputSubsystem() = default;
125
126void InputSubsystem::Initialize() {
127 impl->Initialize();
128}
129
130void InputSubsystem::Shutdown() {
131 impl->Shutdown();
132}
133
134Keyboard* InputSubsystem::GetKeyboard() {
135 return impl->keyboard.get();
136}
137
138const Keyboard* InputSubsystem::GetKeyboard() const {
139 return impl->keyboard.get();
140}
141
142MotionEmu* InputSubsystem::GetMotionEmu() {
143 return impl->motion_emu.get();
65} 144}
66 145
67Keyboard* GetKeyboard() { 146const MotionEmu* InputSubsystem::GetMotionEmu() const {
68 return keyboard.get(); 147 return impl->motion_emu.get();
69} 148}
70 149
71MotionEmu* GetMotionEmu() { 150std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
72 return motion_emu.get(); 151 return impl->GetInputDevices();
73} 152}
74 153
75GCButtonFactory* GetGCButtons() { 154AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const {
76 return gcbuttons.get(); 155 return impl->GetAnalogMappingForDevice(device);
77} 156}
78 157
79GCAnalogFactory* GetGCAnalogs() { 158ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const {
80 return gcanalog.get(); 159 return impl->GetButtonMappingForDevice(device);
160}
161
162GCAnalogFactory* InputSubsystem::GetGCAnalogs() {
163 return impl->gcanalog.get();
164}
165
166const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const {
167 return impl->gcanalog.get();
168}
169
170GCButtonFactory* InputSubsystem::GetGCButtons() {
171 return impl->gcbuttons.get();
172}
173
174const GCButtonFactory* InputSubsystem::GetGCButtons() const {
175 return impl->gcbuttons.get();
176}
177
178void InputSubsystem::ReloadInputDevices() {
179 if (!impl->udp) {
180 return;
181 }
182 impl->udp->ReloadUDPClient();
183}
184
185std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
186 Polling::DeviceType type) const {
187#ifdef HAVE_SDL2
188 return impl->sdl->GetPollers(type);
189#else
190 return {};
191#endif
81} 192}
82 193
83std::string GenerateKeyboardParam(int key_code) { 194std::string GenerateKeyboardParam(int key_code) {
@@ -101,68 +212,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
101 }; 212 };
102 return circle_pad_param.Serialize(); 213 return circle_pad_param.Serialize();
103} 214}
104
105std::vector<Common::ParamPackage> GetInputDevices() {
106 std::vector<Common::ParamPackage> devices = {
107 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
108 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
109 };
110#ifdef HAVE_SDL2
111 auto sdl_devices = sdl->GetInputDevices();
112 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
113#endif
114 auto udp_devices = udp->GetInputDevices();
115 devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
116 return devices;
117}
118
119std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage> GetButtonMappingForDevice(
120 const Common::ParamPackage& params) {
121 std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage> mappings;
122 if (!params.Has("class") || params.Get("class", "") == "any") {
123 return {};
124 }
125 if (params.Get("class", "") == "key") {
126 // TODO consider returning the SDL key codes for the default keybindings
127 return {};
128 }
129#ifdef HAVE_SDL2
130 if (params.Get("class", "") == "sdl") {
131 return sdl->GetButtonMappingForDevice(params);
132 }
133#endif
134 return {};
135}
136
137std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage> GetAnalogMappingForDevice(
138 const Common::ParamPackage& params) {
139 std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage> mappings;
140 if (!params.Has("class") || params.Get("class", "") == "any") {
141 return {};
142 }
143 if (params.Get("class", "") == "key") {
144 // TODO consider returning the SDL key codes for the default keybindings
145 return {};
146 }
147#ifdef HAVE_SDL2
148 if (params.Get("class", "") == "sdl") {
149 return sdl->GetAnalogMappingForDevice(params);
150 }
151#endif
152 return {};
153}
154
155namespace Polling {
156
157std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
158 std::vector<std::unique_ptr<DevicePoller>> pollers;
159
160#ifdef HAVE_SDL2
161 pollers = sdl->GetPollers(type);
162#endif
163
164 return pollers;
165}
166
167} // namespace Polling
168} // namespace InputCommon 215} // namespace InputCommon
diff --git a/src/input_common/main.h b/src/input_common/main.h
index e706c3750..f3fbf696e 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -8,60 +8,20 @@
8#include <string> 8#include <string>
9#include <unordered_map> 9#include <unordered_map>
10#include <vector> 10#include <vector>
11#include "input_common/gcadapter/gc_poller.h"
12#include "input_common/settings.h"
13 11
14namespace Common { 12namespace Common {
15class ParamPackage; 13class ParamPackage;
16} 14}
17 15
18namespace InputCommon { 16namespace Settings::NativeAnalog {
19 17enum Values : int;
20/// Initializes and registers all built-in input device factories. 18}
21void Init();
22
23/// Deregisters all built-in input device factories and shuts them down.
24void Shutdown();
25
26class Keyboard;
27
28/// Gets the keyboard button device factory.
29Keyboard* GetKeyboard();
30
31class MotionEmu;
32
33/// Gets the motion emulation factory.
34MotionEmu* GetMotionEmu();
35
36GCButtonFactory* GetGCButtons();
37
38GCAnalogFactory* GetGCAnalogs();
39
40/// Generates a serialized param package for creating a keyboard button device
41std::string GenerateKeyboardParam(int key_code);
42
43/// Generates a serialized param package for creating an analog device taking input from keyboard
44std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
45 int key_modifier, float modifier_scale);
46
47/**
48 * Return a list of available input devices that this Factory can create a new device with.
49 * Each returned Parampackage should have a `display` field used for display, a class field for
50 * backends to determine if this backend is meant to service the request and any other information
51 * needed to identify this in the backend later.
52 */
53std::vector<Common::ParamPackage> GetInputDevices();
54
55/**
56 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
57 * mapping for the device. This is currently only implemented for the sdl backend devices.
58 */
59using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
60using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
61 19
62ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&); 20namespace Settings::NativeButton {
63AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&); 21enum Values : int;
22}
64 23
24namespace InputCommon {
65namespace Polling { 25namespace Polling {
66 26
67enum class DeviceType { Button, AnalogPreferred }; 27enum class DeviceType { Button, AnalogPreferred };
@@ -86,8 +46,92 @@ public:
86 */ 46 */
87 virtual Common::ParamPackage GetNextInput() = 0; 47 virtual Common::ParamPackage GetNextInput() = 0;
88}; 48};
89
90// Get all DevicePoller from all backends for a specific device type
91std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type);
92} // namespace Polling 49} // namespace Polling
50
51class GCAnalogFactory;
52class GCButtonFactory;
53class Keyboard;
54class MotionEmu;
55
56/**
57 * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default
58 * mapping for the device. This is currently only implemented for the SDL backend devices.
59 */
60using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>;
61using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>;
62
63class InputSubsystem {
64public:
65 explicit InputSubsystem();
66 ~InputSubsystem();
67
68 InputSubsystem(const InputSubsystem&) = delete;
69 InputSubsystem& operator=(const InputSubsystem&) = delete;
70
71 InputSubsystem(InputSubsystem&&) = delete;
72 InputSubsystem& operator=(InputSubsystem&&) = delete;
73
74 /// Initializes and registers all built-in input device factories.
75 void Initialize();
76
77 /// Unregisters all built-in input device factories and shuts them down.
78 void Shutdown();
79
80 /// Retrieves the underlying keyboard device.
81 [[nodiscard]] Keyboard* GetKeyboard();
82
83 /// Retrieves the underlying keyboard device.
84 [[nodiscard]] const Keyboard* GetKeyboard() const;
85
86 /// Retrieves the underlying motion emulation factory.
87 [[nodiscard]] MotionEmu* GetMotionEmu();
88
89 /// Retrieves the underlying motion emulation factory.
90 [[nodiscard]] const MotionEmu* GetMotionEmu() const;
91
92 /**
93 * Returns all available input devices that this Factory can create a new device with.
94 * Each returned ParamPackage should have a `display` field used for display, a class field for
95 * backends to determine if this backend is meant to service the request and any other
96 * information needed to identify this in the backend later.
97 */
98 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const;
99
100 /// Retrieves the analog mappings for the given device.
101 [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const;
102
103 /// Retrieves the button mappings for the given device.
104 [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const;
105
106 /// Retrieves the underlying GameCube analog handler.
107 [[nodiscard]] GCAnalogFactory* GetGCAnalogs();
108
109 /// Retrieves the underlying GameCube analog handler.
110 [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const;
111
112 /// Retrieves the underlying GameCube button handler.
113 [[nodiscard]] GCButtonFactory* GetGCButtons();
114
115 /// Retrieves the underlying GameCube button handler.
116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
117
118 /// Reloads the input devices
119 void ReloadInputDevices();
120
121 /// Get all DevicePoller from all backends for a specific device type
122 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
123 Polling::DeviceType type) const;
124
125private:
126 struct Impl;
127 std::unique_ptr<Impl> impl;
128};
129
130/// Generates a serialized param package for creating a keyboard button device
131std::string GenerateKeyboardParam(int key_code);
132
133/// Generates a serialized param package for creating an analog device taking input from keyboard
134std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
135 int key_modifier, float modifier_scale);
136
93} // namespace InputCommon 137} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 7605c884d..a9e676f4b 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -3,6 +3,7 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <algorithm> 5#include <algorithm>
6#include <array>
6#include <atomic> 7#include <atomic>
7#include <cmath> 8#include <cmath>
8#include <functional> 9#include <functional>
@@ -17,11 +18,11 @@
17#include <vector> 18#include <vector>
18#include <SDL.h> 19#include <SDL.h>
19#include "common/logging/log.h" 20#include "common/logging/log.h"
20#include "common/math_util.h"
21#include "common/param_package.h" 21#include "common/param_package.h"
22#include "common/threadsafe_queue.h" 22#include "common/threadsafe_queue.h"
23#include "core/frontend/input.h" 23#include "core/frontend/input.h"
24#include "input_common/sdl/sdl_impl.h" 24#include "input_common/sdl/sdl_impl.h"
25#include "input_common/settings.h"
25 26
26namespace InputCommon::SDL { 27namespace InputCommon::SDL {
27 28
@@ -358,7 +359,7 @@ public:
358 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), 359 return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
359 y / r * (r - deadzone) / (1 - deadzone)); 360 y / r * (r - deadzone) / (1 - deadzone));
360 } 361 }
361 return std::make_tuple<float, float>(0.0f, 0.0f); 362 return {};
362 } 363 }
363 364
364 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { 365 bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override {
@@ -574,10 +575,10 @@ std::vector<Common::ParamPackage> SDLState::GetInputDevices() {
574 575
575namespace { 576namespace {
576Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis, 577Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis,
577 float value = 0.1) { 578 float value = 0.1f) {
578 Common::ParamPackage params({{"engine", "sdl"}}); 579 Common::ParamPackage params({{"engine", "sdl"}});
579 params.Set("port", port); 580 params.Set("port", port);
580 params.Set("guid", guid); 581 params.Set("guid", std::move(guid));
581 params.Set("axis", axis); 582 params.Set("axis", axis);
582 if (value > 0) { 583 if (value > 0) {
583 params.Set("direction", "+"); 584 params.Set("direction", "+");
@@ -592,7 +593,7 @@ Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid
592Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) { 593Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) {
593 Common::ParamPackage params({{"engine", "sdl"}}); 594 Common::ParamPackage params({{"engine", "sdl"}});
594 params.Set("port", port); 595 params.Set("port", port);
595 params.Set("guid", guid); 596 params.Set("guid", std::move(guid));
596 params.Set("button", button); 597 params.Set("button", button);
597 return params; 598 return params;
598} 599}
@@ -601,7 +602,7 @@ Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u
601 Common::ParamPackage params({{"engine", "sdl"}}); 602 Common::ParamPackage params({{"engine", "sdl"}});
602 603
603 params.Set("port", port); 604 params.Set("port", port);
604 params.Set("guid", guid); 605 params.Set("guid", std::move(guid));
605 params.Set("hat", hat); 606 params.Set("hat", hat);
606 switch (value) { 607 switch (value) {
607 case SDL_HAT_UP: 608 case SDL_HAT_UP:
@@ -670,55 +671,62 @@ Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& gui
670} // Anonymous namespace 671} // Anonymous namespace
671 672
672ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { 673ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) {
673 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
674 // We will add those afterwards
675 // This list also excludes Screenshot since theres not really a mapping for that
676 std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerButton>
677 switch_to_sdl_button = {
678 {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
679 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
680 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
681 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
682 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
683 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
684 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
685 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
686 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
687 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
688 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
689 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
690 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
691 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
692 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
693 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
694 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
695 };
696 if (!params.Has("guid") || !params.Has("port")) { 674 if (!params.Has("guid") || !params.Has("port")) {
697 return {}; 675 return {};
698 } 676 }
699 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); 677 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
700 auto controller = joystick->GetSDLGameController(); 678 auto* controller = joystick->GetSDLGameController();
701 if (!controller) { 679 if (controller == nullptr) {
702 return {}; 680 return {};
703 } 681 }
704 682
705 ButtonMapping mapping{}; 683 // This list is missing ZL/ZR since those are not considered buttons in SDL GameController.
684 // We will add those afterwards
685 // This list also excludes Screenshot since theres not really a mapping for that
686 using ButtonBindings =
687 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
688 static constexpr ButtonBindings switch_to_sdl_button{{
689 {Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B},
690 {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A},
691 {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y},
692 {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X},
693 {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK},
694 {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK},
695 {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
696 {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
697 {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START},
698 {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK},
699 {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT},
700 {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP},
701 {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT},
702 {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN},
703 {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
704 {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
705 {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
706 }};
707
708 // Add the missing bindings for ZL/ZR
709 using ZBindings =
710 std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;
711 static constexpr ZBindings switch_to_sdl_axis{{
712 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
713 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
714 }};
715
716 ButtonMapping mapping;
717 mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size());
718
706 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { 719 for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) {
707 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); 720 const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button);
708 mapping[switch_button] = 721 mapping.insert_or_assign(
709 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding); 722 switch_button,
723 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
710 } 724 }
711
712 // Add the missing bindings for ZL/ZR
713 std::unordered_map<Settings::NativeButton::Values, SDL_GameControllerAxis> switch_to_sdl_axis =
714 {
715 {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT},
716 {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT},
717 };
718 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { 725 for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) {
719 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); 726 const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis);
720 mapping[switch_button] = 727 mapping.insert_or_assign(
721 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding); 728 switch_button,
729 BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding));
722 } 730 }
723 731
724 return mapping; 732 return mapping;
@@ -729,8 +737,8 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
729 return {}; 737 return {};
730 } 738 }
731 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); 739 const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0));
732 auto controller = joystick->GetSDLGameController(); 740 auto* controller = joystick->GetSDLGameController();
733 if (!controller) { 741 if (controller == nullptr) {
734 return {}; 742 return {};
735 } 743 }
736 744
@@ -739,16 +747,18 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
739 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); 747 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
740 const auto& binding_left_y = 748 const auto& binding_left_y =
741 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); 749 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
742 mapping[Settings::NativeAnalog::LStick] = 750 mapping.insert_or_assign(Settings::NativeAnalog::LStick,
743 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), 751 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
744 binding_left_x.value.axis, binding_left_y.value.axis); 752 binding_left_x.value.axis,
753 binding_left_y.value.axis));
745 const auto& binding_right_x = 754 const auto& binding_right_x =
746 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); 755 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
747 const auto& binding_right_y = 756 const auto& binding_right_y =
748 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); 757 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
749 mapping[Settings::NativeAnalog::RStick] = 758 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
750 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), 759 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
751 binding_right_x.value.axis, binding_right_y.value.axis); 760 binding_right_x.value.axis,
761 binding_right_y.value.axis));
752 return mapping; 762 return mapping;
753} 763}
754 764
@@ -784,7 +794,7 @@ public:
784 } 794 }
785 return {}; 795 return {};
786 } 796 }
787 std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) { 797 [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
788 switch (event.type) { 798 switch (event.type) {
789 case SDL_JOYAXISMOTION: 799 case SDL_JOYAXISMOTION:
790 if (std::abs(event.jaxis.value / 32767.0) < 0.5) { 800 if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
@@ -795,7 +805,7 @@ public:
795 case SDL_JOYHATMOTION: 805 case SDL_JOYHATMOTION:
796 return {SDLEventToButtonParamPackage(state, event)}; 806 return {SDLEventToButtonParamPackage(state, event)};
797 } 807 }
798 return {}; 808 return std::nullopt;
799 } 809 }
800}; 810};
801 811
diff --git a/src/input_common/settings.h b/src/input_common/settings.h
index 8e481a7fe..2d258960b 100644
--- a/src/input_common/settings.h
+++ b/src/input_common/settings.h
@@ -10,7 +10,7 @@
10 10
11namespace Settings { 11namespace Settings {
12namespace NativeButton { 12namespace NativeButton {
13enum Values { 13enum Values : int {
14 A, 14 A,
15 B, 15 B,
16 X, 16 X,
@@ -52,7 +52,7 @@ extern const std::array<const char*, NumButtons> mapping;
52} // namespace NativeButton 52} // namespace NativeButton
53 53
54namespace NativeAnalog { 54namespace NativeAnalog {
55enum Values { 55enum Values : int {
56 LStick, 56 LStick,
57 RStick, 57 RStick,
58 58
diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp
new file mode 100644
index 000000000..98da0ef1a
--- /dev/null
+++ b/src/input_common/touch_from_button.cpp
@@ -0,0 +1,50 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/frontend/framebuffer_layout.h"
6#include "core/settings.h"
7#include "input_common/touch_from_button.h"
8
9namespace InputCommon {
10
11class TouchFromButtonDevice final : public Input::TouchDevice {
12public:
13 TouchFromButtonDevice() {
14 for (const auto& config_entry :
15 Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index]
16 .buttons) {
17 const Common::ParamPackage package{config_entry};
18 map.emplace_back(
19 Input::CreateDevice<Input::ButtonDevice>(config_entry),
20 std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)),
21 std::clamp(package.Get("y", 0), 0,
22 static_cast<int>(Layout::ScreenUndocked::Height)));
23 }
24 }
25
26 std::tuple<float, float, bool> GetStatus() const override {
27 for (const auto& m : map) {
28 const bool state = std::get<0>(m)->GetStatus();
29 if (state) {
30 const float x = static_cast<float>(std::get<1>(m)) /
31 static_cast<int>(Layout::ScreenUndocked::Width);
32 const float y = static_cast<float>(std::get<2>(m)) /
33 static_cast<int>(Layout::ScreenUndocked::Height);
34 return {x, y, true};
35 }
36 }
37 return {};
38 }
39
40private:
41 // A vector of the mapped button, its x and its y-coordinate
42 std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map;
43};
44
45std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(
46 const Common::ParamPackage& params) {
47 return std::make_unique<TouchFromButtonDevice>();
48}
49
50} // namespace InputCommon
diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h
new file mode 100644
index 000000000..8b4d1aa96
--- /dev/null
+++ b/src/input_common/touch_from_button.h
@@ -0,0 +1,23 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9
10namespace InputCommon {
11
12/**
13 * A touch device factory that takes a list of button devices and combines them into a touch device.
14 */
15class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> {
16public:
17 /**
18 * Creates a touch device from a list of button devices
19 */
20 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
21};
22
23} // namespace InputCommon
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index ebcfaa0e3..4205bd573 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -380,6 +380,14 @@ bool VKDevice::Create() {
380 380
381 CollectTelemetryParameters(); 381 CollectTelemetryParameters();
382 382
383 if (ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_AMD_PROPRIETARY_KHR) {
384 // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but the <stride> field
385 // seems to be bugged. Blacklisting it for now.
386 LOG_WARNING(Render_Vulkan,
387 "Blacklisting AMD proprietary from VK_EXT_extended_dynamic_state");
388 ext_extended_dynamic_state = false;
389 }
390
383 graphics_queue = logical.GetQueue(graphics_family); 391 graphics_queue = logical.GetQueue(graphics_family);
384 present_queue = logical.GetQueue(present_family); 392 present_queue = logical.GetQueue(present_family);
385 393
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 6987e85e1..3ea4e5601 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -68,6 +68,9 @@ add_executable(yuzu
68 configuration/configure_input_advanced.cpp 68 configuration/configure_input_advanced.cpp
69 configuration/configure_input_advanced.h 69 configuration/configure_input_advanced.h
70 configuration/configure_input_advanced.ui 70 configuration/configure_input_advanced.ui
71 configuration/configure_motion_touch.cpp
72 configuration/configure_motion_touch.h
73 configuration/configure_motion_touch.ui
71 configuration/configure_mouse_advanced.cpp 74 configuration/configure_mouse_advanced.cpp
72 configuration/configure_mouse_advanced.h 75 configuration/configure_mouse_advanced.h
73 configuration/configure_mouse_advanced.ui 76 configuration/configure_mouse_advanced.ui
@@ -86,9 +89,13 @@ add_executable(yuzu
86 configuration/configure_system.cpp 89 configuration/configure_system.cpp
87 configuration/configure_system.h 90 configuration/configure_system.h
88 configuration/configure_system.ui 91 configuration/configure_system.ui
92 configuration/configure_touch_from_button.cpp
93 configuration/configure_touch_from_button.h
94 configuration/configure_touch_from_button.ui
89 configuration/configure_touchscreen_advanced.cpp 95 configuration/configure_touchscreen_advanced.cpp
90 configuration/configure_touchscreen_advanced.h 96 configuration/configure_touchscreen_advanced.h
91 configuration/configure_touchscreen_advanced.ui 97 configuration/configure_touchscreen_advanced.ui
98 configuration/configure_touch_widget.h
92 configuration/configure_ui.cpp 99 configuration/configure_ui.cpp
93 configuration/configure_ui.h 100 configuration/configure_ui.h
94 configuration/configure_ui.ui 101 configuration/configure_ui.ui
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 8fc322b30..21707e451 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -304,8 +304,9 @@ static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow*
304 return wsi; 304 return wsi;
305} 305}
306 306
307GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_) 307GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
308 : QWidget(parent_), emu_thread(emu_thread_) { 308 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
309 : QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} {
309 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") 310 setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
310 .arg(QString::fromUtf8(Common::g_build_name), 311 .arg(QString::fromUtf8(Common::g_build_name),
311 QString::fromUtf8(Common::g_scm_branch), 312 QString::fromUtf8(Common::g_scm_branch),
@@ -314,15 +315,15 @@ GRenderWindow::GRenderWindow(GMainWindow* parent_, EmuThread* emu_thread_)
314 auto layout = new QHBoxLayout(this); 315 auto layout = new QHBoxLayout(this);
315 layout->setMargin(0); 316 layout->setMargin(0);
316 setLayout(layout); 317 setLayout(layout);
317 InputCommon::Init(); 318 input_subsystem->Initialize();
318 319
319 this->setMouseTracking(true); 320 this->setMouseTracking(true);
320 321
321 connect(this, &GRenderWindow::FirstFrameDisplayed, parent_, &GMainWindow::OnLoadComplete); 322 connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
322} 323}
323 324
324GRenderWindow::~GRenderWindow() { 325GRenderWindow::~GRenderWindow() {
325 InputCommon::Shutdown(); 326 input_subsystem->Shutdown();
326} 327}
327 328
328void GRenderWindow::PollEvents() { 329void GRenderWindow::PollEvents() {
@@ -391,11 +392,11 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
391} 392}
392 393
393void GRenderWindow::keyPressEvent(QKeyEvent* event) { 394void GRenderWindow::keyPressEvent(QKeyEvent* event) {
394 InputCommon::GetKeyboard()->PressKey(event->key()); 395 input_subsystem->GetKeyboard()->PressKey(event->key());
395} 396}
396 397
397void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { 398void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
398 InputCommon::GetKeyboard()->ReleaseKey(event->key()); 399 input_subsystem->GetKeyboard()->ReleaseKey(event->key());
399} 400}
400 401
401void GRenderWindow::mousePressEvent(QMouseEvent* event) { 402void GRenderWindow::mousePressEvent(QMouseEvent* event) {
@@ -409,7 +410,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
409 const auto [x, y] = ScaleTouch(pos); 410 const auto [x, y] = ScaleTouch(pos);
410 this->TouchPressed(x, y); 411 this->TouchPressed(x, y);
411 } else if (event->button() == Qt::RightButton) { 412 } else if (event->button() == Qt::RightButton) {
412 InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); 413 input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y());
413 } 414 }
414 QWidget::mousePressEvent(event); 415 QWidget::mousePressEvent(event);
415} 416}
@@ -423,7 +424,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
423 auto pos = event->pos(); 424 auto pos = event->pos();
424 const auto [x, y] = ScaleTouch(pos); 425 const auto [x, y] = ScaleTouch(pos);
425 this->TouchMoved(x, y); 426 this->TouchMoved(x, y);
426 InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); 427 input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y());
427 QWidget::mouseMoveEvent(event); 428 QWidget::mouseMoveEvent(event);
428} 429}
429 430
@@ -436,7 +437,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
436 if (event->button() == Qt::LeftButton) { 437 if (event->button() == Qt::LeftButton) {
437 this->TouchReleased(); 438 this->TouchReleased();
438 } else if (event->button() == Qt::RightButton) { 439 } else if (event->button() == Qt::RightButton) {
439 InputCommon::GetMotionEmu()->EndTilt(); 440 input_subsystem->GetMotionEmu()->EndTilt();
440 } 441 }
441} 442}
442 443
@@ -451,7 +452,7 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
451 int active_points = 0; 452 int active_points = 0;
452 453
453 // average all active touch points 454 // average all active touch points
454 for (const auto tp : event->touchPoints()) { 455 for (const auto& tp : event->touchPoints()) {
455 if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) { 456 if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
456 active_points++; 457 active_points++;
457 pos += tp.pos(); 458 pos += tp.pos();
@@ -485,7 +486,7 @@ bool GRenderWindow::event(QEvent* event) {
485 486
486void GRenderWindow::focusOutEvent(QFocusEvent* event) { 487void GRenderWindow::focusOutEvent(QFocusEvent* event) {
487 QWidget::focusOutEvent(event); 488 QWidget::focusOutEvent(event);
488 InputCommon::GetKeyboard()->ReleaseAllKeys(); 489 input_subsystem->GetKeyboard()->ReleaseAllKeys();
489} 490}
490 491
491void GRenderWindow::resizeEvent(QResizeEvent* event) { 492void GRenderWindow::resizeEvent(QResizeEvent* event) {
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 6c59b4d5c..ca35cf831 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -6,6 +6,7 @@
6 6
7#include <atomic> 7#include <atomic>
8#include <condition_variable> 8#include <condition_variable>
9#include <memory>
9#include <mutex> 10#include <mutex>
10 11
11#include <QImage> 12#include <QImage>
@@ -23,6 +24,10 @@ class QKeyEvent;
23class QTouchEvent; 24class QTouchEvent;
24class QStringList; 25class QStringList;
25 26
27namespace InputCommon {
28class InputSubsystem;
29}
30
26namespace VideoCore { 31namespace VideoCore {
27enum class LoadCallbackStage; 32enum class LoadCallbackStage;
28} 33}
@@ -121,7 +126,8 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
121 Q_OBJECT 126 Q_OBJECT
122 127
123public: 128public:
124 GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); 129 explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
130 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
125 ~GRenderWindow() override; 131 ~GRenderWindow() override;
126 132
127 // EmuWindow implementation. 133 // EmuWindow implementation.
@@ -183,6 +189,7 @@ private:
183 QStringList GetUnsupportedGLExtensions() const; 189 QStringList GetUnsupportedGLExtensions() const;
184 190
185 EmuThread* emu_thread; 191 EmuThread* emu_thread;
192 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
186 193
187 // Main context that will be shared with all other contexts that are requested. 194 // Main context that will be shared with all other contexts that are requested.
188 // If this is used in a shared context setting, then this should not be used directly, but 195 // If this is used in a shared context setting, then this should not be used directly, but
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 489877be9..2bc55a26a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -51,8 +51,10 @@ const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> Config:
51 }, 51 },
52}}; 52}};
53 53
54const int Config::default_lstick_mod = Qt::Key_E; 54const std::array<int, 2> Config::default_stick_mod = {
55const int Config::default_rstick_mod = Qt::Key_R; 55 Qt::Key_E,
56 Qt::Key_R,
57};
56 58
57const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons = 59const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons =
58 { 60 {
@@ -285,7 +287,7 @@ void Config::ReadPlayerValues() {
285 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 287 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
286 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 288 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
287 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 289 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
288 default_analogs[i][3], default_analogs[i][4], 0.5f); 290 default_analogs[i][3], default_stick_mod[i], 0.5f);
289 auto& player_analogs = player.analogs[i]; 291 auto& player_analogs = player.analogs[i];
290 292
291 player_analogs = qt_config 293 player_analogs = qt_config
@@ -323,7 +325,7 @@ void Config::ReadDebugValues() {
323 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 325 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
324 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 326 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
325 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 327 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
326 default_analogs[i][3], default_analogs[i][4], 0.5f); 328 default_analogs[i][3], default_stick_mod[i], 0.5f);
327 auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i]; 329 auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
328 330
329 debug_pad_analogs = qt_config 331 debug_pad_analogs = qt_config
@@ -418,14 +420,64 @@ void Config::ReadControlValues() {
418 ReadKeyboardValues(); 420 ReadKeyboardValues();
419 ReadMouseValues(); 421 ReadMouseValues();
420 ReadTouchscreenValues(); 422 ReadTouchscreenValues();
423 ReadMotionTouchValues();
421 424
422 Settings::values.vibration_enabled = 425 Settings::values.vibration_enabled =
423 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool(); 426 ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
427 Settings::values.use_docked_mode =
428 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
429
430 qt_config->endGroup();
431}
432
433void Config::ReadMotionTouchValues() {
434 int num_touch_from_button_maps =
435 qt_config->beginReadArray(QStringLiteral("touch_from_button_maps"));
436
437 if (num_touch_from_button_maps > 0) {
438 const auto append_touch_from_button_map = [this] {
439 Settings::TouchFromButtonMap map;
440 map.name = ReadSetting(QStringLiteral("name"), QStringLiteral("default"))
441 .toString()
442 .toStdString();
443 const int num_touch_maps = qt_config->beginReadArray(QStringLiteral("entries"));
444 map.buttons.reserve(num_touch_maps);
445 for (int i = 0; i < num_touch_maps; i++) {
446 qt_config->setArrayIndex(i);
447 std::string touch_mapping =
448 ReadSetting(QStringLiteral("bind")).toString().toStdString();
449 map.buttons.emplace_back(std::move(touch_mapping));
450 }
451 qt_config->endArray(); // entries
452 Settings::values.touch_from_button_maps.emplace_back(std::move(map));
453 };
454
455 for (int i = 0; i < num_touch_from_button_maps; ++i) {
456 qt_config->setArrayIndex(i);
457 append_touch_from_button_map();
458 }
459 } else {
460 Settings::values.touch_from_button_maps.emplace_back(
461 Settings::TouchFromButtonMap{"default", {}});
462 num_touch_from_button_maps = 1;
463 }
464 qt_config->endArray();
465
424 Settings::values.motion_device = 466 Settings::values.motion_device =
425 ReadSetting(QStringLiteral("motion_device"), 467 ReadSetting(QStringLiteral("motion_device"),
426 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 468 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
427 .toString() 469 .toString()
428 .toStdString(); 470 .toStdString();
471 Settings::values.touch_device =
472 ReadSetting(QStringLiteral("touch_device"), QStringLiteral("engine:emu_window"))
473 .toString()
474 .toStdString();
475 Settings::values.use_touch_from_button =
476 ReadSetting(QStringLiteral("use_touch_from_button"), false).toBool();
477 Settings::values.touch_from_button_map_index =
478 ReadSetting(QStringLiteral("touch_from_button_map"), 0).toInt();
479 Settings::values.touch_from_button_map_index =
480 std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1);
429 Settings::values.udp_input_address = 481 Settings::values.udp_input_address =
430 ReadSetting(QStringLiteral("udp_input_address"), 482 ReadSetting(QStringLiteral("udp_input_address"),
431 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) 483 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -436,10 +488,6 @@ void Config::ReadControlValues() {
436 .toInt()); 488 .toInt());
437 Settings::values.udp_pad_index = 489 Settings::values.udp_pad_index =
438 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); 490 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
439 Settings::values.use_docked_mode =
440 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
441
442 qt_config->endGroup();
443} 491}
444 492
445void Config::ReadCoreValues() { 493void Config::ReadCoreValues() {
@@ -877,7 +925,7 @@ void Config::SavePlayerValues() {
877 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 925 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
878 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 926 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
879 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 927 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
880 default_analogs[i][3], default_analogs[i][4], 0.5f); 928 default_analogs[i][3], default_stick_mod[i], 0.5f);
881 WriteSetting(QStringLiteral("player_%1_").arg(p) + 929 WriteSetting(QStringLiteral("player_%1_").arg(p) +
882 QString::fromStdString(Settings::NativeAnalog::mapping[i]), 930 QString::fromStdString(Settings::NativeAnalog::mapping[i]),
883 QString::fromStdString(player.analogs[i]), 931 QString::fromStdString(player.analogs[i]),
@@ -898,7 +946,7 @@ void Config::SaveDebugValues() {
898 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { 946 for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
899 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys( 947 const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
900 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], 948 default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
901 default_analogs[i][3], default_analogs[i][4], 0.5f); 949 default_analogs[i][3], default_stick_mod[i], 0.5f);
902 WriteSetting(QStringLiteral("debug_pad_") + 950 WriteSetting(QStringLiteral("debug_pad_") +
903 QString::fromStdString(Settings::NativeAnalog::mapping[i]), 951 QString::fromStdString(Settings::NativeAnalog::mapping[i]),
904 QString::fromStdString(Settings::values.debug_pad_analogs[i]), 952 QString::fromStdString(Settings::values.debug_pad_analogs[i]),
@@ -932,6 +980,43 @@ void Config::SaveTouchscreenValues() {
932 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); 980 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
933} 981}
934 982
983void Config::SaveMotionTouchValues() {
984 WriteSetting(QStringLiteral("motion_device"),
985 QString::fromStdString(Settings::values.motion_device),
986 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
987 WriteSetting(QStringLiteral("touch_device"),
988 QString::fromStdString(Settings::values.touch_device),
989 QStringLiteral("engine:emu_window"));
990 WriteSetting(QStringLiteral("use_touch_from_button"), Settings::values.use_touch_from_button,
991 false);
992 WriteSetting(QStringLiteral("touch_from_button_map"),
993 Settings::values.touch_from_button_map_index, 0);
994 WriteSetting(QStringLiteral("udp_input_address"),
995 QString::fromStdString(Settings::values.udp_input_address),
996 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
997 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
998 InputCommon::CemuhookUDP::DEFAULT_PORT);
999 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
1000
1001 qt_config->beginWriteArray(QStringLiteral("touch_from_button_maps"));
1002 for (std::size_t p = 0; p < Settings::values.touch_from_button_maps.size(); ++p) {
1003 qt_config->setArrayIndex(static_cast<int>(p));
1004 WriteSetting(QStringLiteral("name"),
1005 QString::fromStdString(Settings::values.touch_from_button_maps[p].name),
1006 QStringLiteral("default"));
1007 qt_config->beginWriteArray(QStringLiteral("entries"));
1008 for (std::size_t q = 0; q < Settings::values.touch_from_button_maps[p].buttons.size();
1009 ++q) {
1010 qt_config->setArrayIndex(static_cast<int>(q));
1011 WriteSetting(
1012 QStringLiteral("bind"),
1013 QString::fromStdString(Settings::values.touch_from_button_maps[p].buttons[q]));
1014 }
1015 qt_config->endArray();
1016 }
1017 qt_config->endArray();
1018}
1019
935void Config::SaveValues() { 1020void Config::SaveValues() {
936 if (global) { 1021 if (global) {
937 SaveControlValues(); 1022 SaveControlValues();
@@ -974,18 +1059,16 @@ void Config::SaveControlValues() {
974 SaveDebugValues(); 1059 SaveDebugValues();
975 SaveMouseValues(); 1060 SaveMouseValues();
976 SaveTouchscreenValues(); 1061 SaveTouchscreenValues();
1062 SaveMotionTouchValues();
977 1063
978 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); 1064 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
979 WriteSetting(QStringLiteral("motion_device"), 1065 WriteSetting(QStringLiteral("motion_device"),
980 QString::fromStdString(Settings::values.motion_device), 1066 QString::fromStdString(Settings::values.motion_device),
981 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); 1067 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
1068 WriteSetting(QStringLiteral("touch_device"),
1069 QString::fromStdString(Settings::values.touch_device),
1070 QStringLiteral("engine:emu_window"));
982 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 1071 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
983 WriteSetting(QStringLiteral("udp_input_address"),
984 QString::fromStdString(Settings::values.udp_input_address),
985 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
986 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
987 InputCommon::CemuhookUDP::DEFAULT_PORT);
988 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
989 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); 1072 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
990 1073
991 qt_config->endGroup(); 1074 qt_config->endGroup();
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 9eeaf9d1e..ca0d29c6c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -24,8 +24,7 @@ public:
24 24
25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; 25 static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
26 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs; 26 static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
27 static const int default_lstick_mod; 27 static const std::array<int, 2> default_stick_mod;
28 static const int default_rstick_mod;
29 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> 28 static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
30 default_mouse_buttons; 29 default_mouse_buttons;
31 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; 30 static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
@@ -39,6 +38,7 @@ private:
39 void ReadKeyboardValues(); 38 void ReadKeyboardValues();
40 void ReadMouseValues(); 39 void ReadMouseValues();
41 void ReadTouchscreenValues(); 40 void ReadTouchscreenValues();
41 void ReadMotionTouchValues();
42 42
43 // Read functions bases off the respective config section names. 43 // Read functions bases off the respective config section names.
44 void ReadAudioValues(); 44 void ReadAudioValues();
@@ -65,6 +65,7 @@ private:
65 void SaveDebugValues(); 65 void SaveDebugValues();
66 void SaveMouseValues(); 66 void SaveMouseValues();
67 void SaveTouchscreenValues(); 67 void SaveTouchscreenValues();
68 void SaveMotionTouchValues();
68 69
69 // Save functions based off the respective config section names. 70 // Save functions based off the respective config section names.
70 void SaveAudioValues(); 71 void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_debug_controller.cpp b/src/yuzu/configuration/configure_debug_controller.cpp
index 72885b4b8..0097c9a29 100644
--- a/src/yuzu/configuration/configure_debug_controller.cpp
+++ b/src/yuzu/configuration/configure_debug_controller.cpp
@@ -5,9 +5,10 @@
5#include "ui_configure_debug_controller.h" 5#include "ui_configure_debug_controller.h"
6#include "yuzu/configuration/configure_debug_controller.h" 6#include "yuzu/configuration/configure_debug_controller.h"
7 7
8ConfigureDebugController::ConfigureDebugController(QWidget* parent) 8ConfigureDebugController::ConfigureDebugController(QWidget* parent,
9 InputCommon::InputSubsystem* input_subsystem)
9 : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()), 10 : QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
10 debug_controller(new ConfigureInputPlayer(this, 9, nullptr, true)) { 11 debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) {
11 ui->setupUi(this); 12 ui->setupUi(this);
12 13
13 ui->controllerLayout->addWidget(debug_controller); 14 ui->controllerLayout->addWidget(debug_controller);
diff --git a/src/yuzu/configuration/configure_debug_controller.h b/src/yuzu/configuration/configure_debug_controller.h
index 36475bbea..34dcf705f 100644
--- a/src/yuzu/configuration/configure_debug_controller.h
+++ b/src/yuzu/configuration/configure_debug_controller.h
@@ -10,6 +10,10 @@
10 10
11class QPushButton; 11class QPushButton;
12 12
13namespace InputCommon {
14class InputSubsystem;
15}
16
13namespace Ui { 17namespace Ui {
14class ConfigureDebugController; 18class ConfigureDebugController;
15} 19}
@@ -18,7 +22,8 @@ class ConfigureDebugController : public QDialog {
18 Q_OBJECT 22 Q_OBJECT
19 23
20public: 24public:
21 explicit ConfigureDebugController(QWidget* parent); 25 explicit ConfigureDebugController(QWidget* parent,
26 InputCommon::InputSubsystem* input_subsystem);
22 ~ConfigureDebugController() override; 27 ~ConfigureDebugController() override;
23 28
24 void ApplyConfiguration(); 29 void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 857577591..8186929a6 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -12,7 +12,8 @@
12#include "yuzu/configuration/configure_input_player.h" 12#include "yuzu/configuration/configure_input_player.h"
13#include "yuzu/hotkeys.h" 13#include "yuzu/hotkeys.h"
14 14
15ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) 15ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
16 InputCommon::InputSubsystem* input_subsystem)
16 : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) { 17 : QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
17 Settings::configuring_global = true; 18 Settings::configuring_global = true;
18 19
@@ -20,6 +21,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
20 ui->hotkeysTab->Populate(registry); 21 ui->hotkeysTab->Populate(registry);
21 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 22 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
22 23
24 ui->inputTab->Initialize(input_subsystem);
25
23 SetConfiguration(); 26 SetConfiguration();
24 PopulateSelectionList(); 27 PopulateSelectionList();
25 28
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 4289bc225..570c3b941 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -9,6 +9,10 @@
9 9
10class HotkeyRegistry; 10class HotkeyRegistry;
11 11
12namespace InputCommon {
13class InputSubsystem;
14}
15
12namespace Ui { 16namespace Ui {
13class ConfigureDialog; 17class ConfigureDialog;
14} 18}
@@ -17,7 +21,8 @@ class ConfigureDialog : public QDialog {
17 Q_OBJECT 21 Q_OBJECT
18 22
19public: 23public:
20 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry); 24 explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
25 InputCommon::InputSubsystem* input_subsystem);
21 ~ConfigureDialog() override; 26 ~ConfigureDialog() override;
22 27
23 void ApplyConfiguration(); 28 void ApplyConfiguration();
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 0d004c2f7..ae3e31762 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -20,6 +20,7 @@
20#include "yuzu/configuration/configure_input.h" 20#include "yuzu/configuration/configure_input.h"
21#include "yuzu/configuration/configure_input_advanced.h" 21#include "yuzu/configuration/configure_input_advanced.h"
22#include "yuzu/configuration/configure_input_player.h" 22#include "yuzu/configuration/configure_input_player.h"
23#include "yuzu/configuration/configure_motion_touch.h"
23#include "yuzu/configuration/configure_mouse_advanced.h" 24#include "yuzu/configuration/configure_mouse_advanced.h"
24#include "yuzu/configuration/configure_touchscreen_advanced.h" 25#include "yuzu/configuration/configure_touchscreen_advanced.h"
25 26
@@ -65,16 +66,20 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
65ConfigureInput::ConfigureInput(QWidget* parent) 66ConfigureInput::ConfigureInput(QWidget* parent)
66 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { 67 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
67 ui->setupUi(this); 68 ui->setupUi(this);
69}
70
71ConfigureInput::~ConfigureInput() = default;
68 72
73void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
69 player_controllers = { 74 player_controllers = {
70 new ConfigureInputPlayer(this, 0, ui->consoleInputSettings), 75 new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
71 new ConfigureInputPlayer(this, 1, ui->consoleInputSettings), 76 new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
72 new ConfigureInputPlayer(this, 2, ui->consoleInputSettings), 77 new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem),
73 new ConfigureInputPlayer(this, 3, ui->consoleInputSettings), 78 new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem),
74 new ConfigureInputPlayer(this, 4, ui->consoleInputSettings), 79 new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem),
75 new ConfigureInputPlayer(this, 5, ui->consoleInputSettings), 80 new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem),
76 new ConfigureInputPlayer(this, 6, ui->consoleInputSettings), 81 new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem),
77 new ConfigureInputPlayer(this, 7, ui->consoleInputSettings), 82 new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem),
78 }; 83 };
79 84
80 player_tabs = { 85 player_tabs = {
@@ -115,12 +120,18 @@ ConfigureInput::ConfigureInput(QWidget* parent)
115 advanced = new ConfigureInputAdvanced(this); 120 advanced = new ConfigureInputAdvanced(this);
116 ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced)); 121 ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
117 ui->tabAdvanced->layout()->addWidget(advanced); 122 ui->tabAdvanced->layout()->addWidget(advanced);
118 connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, 123 connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
119 [this] { CallConfigureDialog<ConfigureDebugController>(*this); }); 124 CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem);
120 connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, 125 });
121 [this] { CallConfigureDialog<ConfigureMouseAdvanced>(*this); }); 126 connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
127 CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
128 });
122 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, 129 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
123 [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); }); 130 [this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
131 connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
132 [this, input_subsystem] {
133 CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
134 });
124 135
125 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 136 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
126 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 137 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
@@ -129,8 +140,6 @@ ConfigureInput::ConfigureInput(QWidget* parent)
129 LoadConfiguration(); 140 LoadConfiguration();
130} 141}
131 142
132ConfigureInput::~ConfigureInput() = default;
133
134QList<QWidget*> ConfigureInput::GetSubTabs() const { 143QList<QWidget*> ConfigureInput::GetSubTabs() const {
135 return { 144 return {
136 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5, 145 ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 78ca659da..d08a24f96 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,8 +7,8 @@
7#include <array> 7#include <array>
8#include <memory> 8#include <memory>
9 9
10#include <QDialog>
11#include <QKeyEvent> 10#include <QKeyEvent>
11#include <QWidget>
12 12
13#include "yuzu/configuration/configure_input_advanced.h" 13#include "yuzu/configuration/configure_input_advanced.h"
14#include "yuzu/configuration/configure_input_player.h" 14#include "yuzu/configuration/configure_input_player.h"
@@ -19,6 +19,10 @@ class QCheckBox;
19class QString; 19class QString;
20class QTimer; 20class QTimer;
21 21
22namespace InputCommon {
23class InputSubsystem;
24}
25
22namespace Ui { 26namespace Ui {
23class ConfigureInput; 27class ConfigureInput;
24} 28}
@@ -32,6 +36,9 @@ public:
32 explicit ConfigureInput(QWidget* parent = nullptr); 36 explicit ConfigureInput(QWidget* parent = nullptr);
33 ~ConfigureInput() override; 37 ~ConfigureInput() override;
34 38
39 /// Initializes the input dialog with the given input subsystem.
40 void Initialize(InputCommon::InputSubsystem* input_subsystem_);
41
35 /// Save all button configurations to settings file. 42 /// Save all button configurations to settings file.
36 void ApplyConfiguration(); 43 void ApplyConfiguration();
37 44
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index db42b826b..81f9dc16c 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -86,6 +86,8 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
86 connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); }); 86 connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
87 connect(ui->touchscreen_advanced, &QPushButton::clicked, this, 87 connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
88 [this] { CallTouchscreenConfigDialog(); }); 88 [this] { CallTouchscreenConfigDialog(); });
89 connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
90 &ConfigureInputAdvanced::CallMotionTouchConfigDialog);
89 91
90 LoadConfiguration(); 92 LoadConfiguration();
91} 93}
diff --git a/src/yuzu/configuration/configure_input_advanced.h b/src/yuzu/configuration/configure_input_advanced.h
index d8fcec52d..50bb87768 100644
--- a/src/yuzu/configuration/configure_input_advanced.h
+++ b/src/yuzu/configuration/configure_input_advanced.h
@@ -28,6 +28,7 @@ signals:
28 void CallDebugControllerDialog(); 28 void CallDebugControllerDialog();
29 void CallMouseConfigDialog(); 29 void CallMouseConfigDialog();
30 void CallTouchscreenConfigDialog(); 30 void CallTouchscreenConfigDialog();
31 void CallMotionTouchConfigDialog();
31 32
32private: 33private:
33 void changeEvent(QEvent* event) override; 34 void changeEvent(QEvent* event) override;
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index d3980eb49..13ecb3dc5 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -11,12 +11,12 @@
11#include <QMenu> 11#include <QMenu>
12#include <QMessageBox> 12#include <QMessageBox>
13#include <QTimer> 13#include <QTimer>
14#include "common/assert.h"
15#include "common/param_package.h" 14#include "common/param_package.h"
16#include "core/core.h" 15#include "core/core.h"
17#include "core/hle/service/hid/controllers/npad.h" 16#include "core/hle/service/hid/controllers/npad.h"
18#include "core/hle/service/hid/hid.h" 17#include "core/hle/service/hid/hid.h"
19#include "core/hle/service/sm/sm.h" 18#include "core/hle/service/sm/sm.h"
19#include "input_common/gcadapter/gc_poller.h"
20#include "input_common/main.h" 20#include "input_common/main.h"
21#include "ui_configure_input_player.h" 21#include "ui_configure_input_player.h"
22#include "yuzu/configuration/config.h" 22#include "yuzu/configuration/config.h"
@@ -229,9 +229,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
229} // namespace 229} // namespace
230 230
231ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index, 231ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
232 QWidget* bottom_row, bool debug) 232 QWidget* bottom_row,
233 InputCommon::InputSubsystem* input_subsystem_,
234 bool debug)
233 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index), 235 : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
234 debug(debug), timeout_timer(std::make_unique<QTimer>()), 236 debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()),
235 poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) { 237 poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) {
236 ui->setupUi(this); 238 ui->setupUi(this);
237 239
@@ -287,7 +289,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
287 params.Set("direction", "+"); 289 params.Set("direction", "+");
288 params.Set("threshold", "0.5"); 290 params.Set("threshold", "0.5");
289 } 291 }
290 (*param) = std::move(params); 292 *param = std::move(params);
291 }, 293 },
292 InputCommon::Polling::DeviceType::Button); 294 InputCommon::Polling::DeviceType::Button);
293 }); 295 });
@@ -303,8 +305,8 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
303 } 305 }
304 306
305 // Handle clicks for the modifier buttons as well. 307 // Handle clicks for the modifier buttons as well.
306 ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_lstick_mod); 308 ConfigureButtonClick(ui->buttonLStickMod, &lstick_mod, Config::default_stick_mod[0]);
307 ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_rstick_mod); 309 ConfigureButtonClick(ui->buttonRStickMod, &rstick_mod, Config::default_stick_mod[1]);
308 310
309 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 311 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
310 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { 312 for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
@@ -401,15 +403,15 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
401 403
402 connect(poll_timer.get(), &QTimer::timeout, [this] { 404 connect(poll_timer.get(), &QTimer::timeout, [this] {
403 Common::ParamPackage params; 405 Common::ParamPackage params;
404 if (InputCommon::GetGCButtons()->IsPolling()) { 406 if (input_subsystem->GetGCButtons()->IsPolling()) {
405 params = InputCommon::GetGCButtons()->GetNextInput(); 407 params = input_subsystem->GetGCButtons()->GetNextInput();
406 if (params.Has("engine")) { 408 if (params.Has("engine")) {
407 SetPollingResult(params, false); 409 SetPollingResult(params, false);
408 return; 410 return;
409 } 411 }
410 } 412 }
411 if (InputCommon::GetGCAnalogs()->IsPolling()) { 413 if (input_subsystem->GetGCAnalogs()->IsPolling()) {
412 params = InputCommon::GetGCAnalogs()->GetNextInput(); 414 params = input_subsystem->GetGCAnalogs()->GetNextInput();
413 if (params.Has("engine")) { 415 if (params.Has("engine")) {
414 SetPollingResult(params, false); 416 SetPollingResult(params, false);
415 return; 417 return;
@@ -514,7 +516,7 @@ void ConfigureInputPlayer::LoadConfiguration() {
514} 516}
515 517
516void ConfigureInputPlayer::UpdateInputDevices() { 518void ConfigureInputPlayer::UpdateInputDevices() {
517 input_devices = InputCommon::GetInputDevices(); 519 input_devices = input_subsystem->GetInputDevices();
518 ui->comboDevices->clear(); 520 ui->comboDevices->clear();
519 for (auto device : input_devices) { 521 for (auto device : input_devices) {
520 ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {}); 522 ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
@@ -530,9 +532,9 @@ void ConfigureInputPlayer::RestoreDefaults() {
530 532
531 // Reset Modifier Buttons 533 // Reset Modifier Buttons
532 lstick_mod = 534 lstick_mod =
533 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_lstick_mod)); 535 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[0]));
534 rstick_mod = 536 rstick_mod =
535 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_rstick_mod)); 537 Common::ParamPackage(InputCommon::GenerateKeyboardParam(Config::default_stick_mod[1]));
536 538
537 // Reset Analogs 539 // Reset Analogs
538 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { 540 for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
@@ -642,8 +644,8 @@ void ConfigureInputPlayer::UpdateMappingWithDefaults() {
642 return; 644 return;
643 } 645 }
644 const auto& device = input_devices[ui->comboDevices->currentIndex()]; 646 const auto& device = input_devices[ui->comboDevices->currentIndex()];
645 auto button_mapping = InputCommon::GetButtonMappingForDevice(device); 647 auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
646 auto analog_mapping = InputCommon::GetAnalogMappingForDevice(device); 648 auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
647 for (int i = 0; i < buttons_param.size(); ++i) { 649 for (int i = 0; i < buttons_param.size(); ++i) {
648 buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)]; 650 buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
649 } 651 }
@@ -666,7 +668,7 @@ void ConfigureInputPlayer::HandleClick(
666 668
667 input_setter = new_input_setter; 669 input_setter = new_input_setter;
668 670
669 device_pollers = InputCommon::Polling::GetPollers(type); 671 device_pollers = input_subsystem->GetPollers(type);
670 672
671 for (auto& poller : device_pollers) { 673 for (auto& poller : device_pollers) {
672 poller->Start(); 674 poller->Start();
@@ -676,9 +678,9 @@ void ConfigureInputPlayer::HandleClick(
676 QWidget::grabKeyboard(); 678 QWidget::grabKeyboard();
677 679
678 if (type == InputCommon::Polling::DeviceType::Button) { 680 if (type == InputCommon::Polling::DeviceType::Button) {
679 InputCommon::GetGCButtons()->BeginConfiguration(); 681 input_subsystem->GetGCButtons()->BeginConfiguration();
680 } else { 682 } else {
681 InputCommon::GetGCAnalogs()->BeginConfiguration(); 683 input_subsystem->GetGCAnalogs()->BeginConfiguration();
682 } 684 }
683 685
684 timeout_timer->start(2500); // Cancel after 2.5 seconds 686 timeout_timer->start(2500); // Cancel after 2.5 seconds
@@ -695,8 +697,8 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params,
695 QWidget::releaseMouse(); 697 QWidget::releaseMouse();
696 QWidget::releaseKeyboard(); 698 QWidget::releaseKeyboard();
697 699
698 InputCommon::GetGCButtons()->EndConfiguration(); 700 input_subsystem->GetGCButtons()->EndConfiguration();
699 InputCommon::GetGCAnalogs()->EndConfiguration(); 701 input_subsystem->GetGCAnalogs()->EndConfiguration();
700 702
701 if (!abort) { 703 if (!abort) {
702 (*input_setter)(params); 704 (*input_setter)(params);
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 25d4cde5e..a25bc3bd9 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -10,12 +10,11 @@
10#include <optional> 10#include <optional>
11#include <string> 11#include <string>
12 12
13#include <QDialog> 13#include <QWidget>
14 14
15#include "common/param_package.h" 15#include "common/param_package.h"
16#include "core/settings.h" 16#include "core/settings.h"
17#include "ui_configure_input.h" 17#include "ui_configure_input.h"
18#include "yuzu/uisettings.h"
19 18
20class QCheckBox; 19class QCheckBox;
21class QKeyEvent; 20class QKeyEvent;
@@ -27,6 +26,10 @@ class QString;
27class QTimer; 26class QTimer;
28class QWidget; 27class QWidget;
29 28
29namespace InputCommon {
30class InputSubsystem;
31}
32
30namespace InputCommon::Polling { 33namespace InputCommon::Polling {
31class DevicePoller; 34class DevicePoller;
32enum class DeviceType; 35enum class DeviceType;
@@ -41,6 +44,7 @@ class ConfigureInputPlayer : public QWidget {
41 44
42public: 45public:
43 explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row, 46 explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
47 InputCommon::InputSubsystem* input_subsystem_,
44 bool debug = false); 48 bool debug = false);
45 ~ConfigureInputPlayer() override; 49 ~ConfigureInputPlayer() override;
46 50
@@ -111,6 +115,8 @@ private:
111 std::size_t player_index; 115 std::size_t player_index;
112 bool debug; 116 bool debug;
113 117
118 InputCommon::InputSubsystem* input_subsystem;
119
114 std::unique_ptr<QTimer> timeout_timer; 120 std::unique_ptr<QTimer> timeout_timer;
115 std::unique_ptr<QTimer> poll_timer; 121 std::unique_ptr<QTimer> poll_timer;
116 122
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp
new file mode 100644
index 000000000..c7d085151
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.cpp
@@ -0,0 +1,314 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <QCloseEvent>
7#include <QLabel>
8#include <QMessageBox>
9#include <QPushButton>
10#include <QVBoxLayout>
11#include "common/logging/log.h"
12#include "core/settings.h"
13#include "input_common/main.h"
14#include "input_common/udp/client.h"
15#include "input_common/udp/udp.h"
16#include "ui_configure_motion_touch.h"
17#include "yuzu/configuration/configure_motion_touch.h"
18#include "yuzu/configuration/configure_touch_from_button.h"
19
20CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
21 const std::string& host, u16 port,
22 u8 pad_index, u16 client_id)
23 : QDialog(parent) {
24 layout = new QVBoxLayout;
25 status_label = new QLabel(tr("Communicating with the server..."));
26 cancel_button = new QPushButton(tr("Cancel"));
27 connect(cancel_button, &QPushButton::clicked, this, [this] {
28 if (!completed) {
29 job->Stop();
30 }
31 accept();
32 });
33 layout->addWidget(status_label);
34 layout->addWidget(cancel_button);
35 setLayout(layout);
36
37 using namespace InputCommon::CemuhookUDP;
38 job = std::make_unique<CalibrationConfigurationJob>(
39 host, port, pad_index, client_id,
40 [this](CalibrationConfigurationJob::Status status) {
41 QString text;
42 switch (status) {
43 case CalibrationConfigurationJob::Status::Ready:
44 text = tr("Touch the top left corner <br>of your touchpad.");
45 break;
46 case CalibrationConfigurationJob::Status::Stage1Completed:
47 text = tr("Now touch the bottom right corner <br>of your touchpad.");
48 break;
49 case CalibrationConfigurationJob::Status::Completed:
50 text = tr("Configuration completed!");
51 break;
52 }
53 QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
54 if (status == CalibrationConfigurationJob::Status::Completed) {
55 QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
56 }
57 },
58 [this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
59 completed = true;
60 min_x = min_x_;
61 min_y = min_y_;
62 max_x = max_x_;
63 max_y = max_y_;
64 });
65}
66
67CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
68
69void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
70 status_label->setText(text);
71}
72
73void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
74 cancel_button->setText(text);
75}
76
77constexpr std::array<std::pair<const char*, const char*>, 2> MotionProviders = {{
78 {"motion_emu", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Mouse (Right Click)")},
79 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
80}};
81
82constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
83 {"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
84 {"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
85}};
86
87ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
88 InputCommon::InputSubsystem* input_subsystem_)
89 : QDialog(parent), input_subsystem{input_subsystem_},
90 ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
91 ui->setupUi(this);
92 for (const auto& [provider, name] : MotionProviders) {
93 ui->motion_provider->addItem(tr(name), QString::fromUtf8(provider));
94 }
95 for (const auto& [provider, name] : TouchProviders) {
96 ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
97 }
98
99 ui->udp_learn_more->setOpenExternalLinks(true);
100 ui->udp_learn_more->setText(
101 tr("<a "
102 "href='https://yuzu-emu.org/wiki/"
103 "using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
104 "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
105
106 SetConfiguration();
107 UpdateUiDisplay();
108 ConnectEvents();
109}
110
111ConfigureMotionTouch::~ConfigureMotionTouch() = default;
112
113void ConfigureMotionTouch::SetConfiguration() {
114 const Common::ParamPackage motion_param(Settings::values.motion_device);
115 const Common::ParamPackage touch_param(Settings::values.touch_device);
116 const std::string motion_engine = motion_param.Get("engine", "motion_emu");
117 const std::string touch_engine = touch_param.Get("engine", "emu_window");
118
119 ui->motion_provider->setCurrentIndex(
120 ui->motion_provider->findData(QString::fromStdString(motion_engine)));
121 ui->touch_provider->setCurrentIndex(
122 ui->touch_provider->findData(QString::fromStdString(touch_engine)));
123 ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
124 touch_from_button_maps = Settings::values.touch_from_button_maps;
125 for (const auto& touch_map : touch_from_button_maps) {
126 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
127 }
128 ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
129 ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
130
131 min_x = touch_param.Get("min_x", 100);
132 min_y = touch_param.Get("min_y", 50);
133 max_x = touch_param.Get("max_x", 1800);
134 max_y = touch_param.Get("max_y", 850);
135
136 ui->udp_server->setText(QString::fromStdString(Settings::values.udp_input_address));
137 ui->udp_port->setText(QString::number(Settings::values.udp_input_port));
138 ui->udp_pad_index->setCurrentIndex(Settings::values.udp_pad_index);
139}
140
141void ConfigureMotionTouch::UpdateUiDisplay() {
142 const QString motion_engine = ui->motion_provider->currentData().toString();
143 const QString touch_engine = ui->touch_provider->currentData().toString();
144 const QString cemuhook_udp = QStringLiteral("cemuhookudp");
145
146 if (motion_engine == QStringLiteral("motion_emu")) {
147 ui->motion_sensitivity_label->setVisible(true);
148 ui->motion_sensitivity->setVisible(true);
149 } else {
150 ui->motion_sensitivity_label->setVisible(false);
151 ui->motion_sensitivity->setVisible(false);
152 }
153
154 if (touch_engine == cemuhook_udp) {
155 ui->touch_calibration->setVisible(true);
156 ui->touch_calibration_config->setVisible(true);
157 ui->touch_calibration_label->setVisible(true);
158 ui->touch_calibration->setText(
159 QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
160 } else {
161 ui->touch_calibration->setVisible(false);
162 ui->touch_calibration_config->setVisible(false);
163 ui->touch_calibration_label->setVisible(false);
164 }
165
166 if (motion_engine == cemuhook_udp || touch_engine == cemuhook_udp) {
167 ui->udp_config_group_box->setVisible(true);
168 } else {
169 ui->udp_config_group_box->setVisible(false);
170 }
171}
172
173void ConfigureMotionTouch::ConnectEvents() {
174 connect(ui->motion_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
175 [this](int index) { UpdateUiDisplay(); });
176 connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
177 [this](int index) { UpdateUiDisplay(); });
178 connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
179 connect(ui->touch_calibration_config, &QPushButton::clicked, this,
180 &ConfigureMotionTouch::OnConfigureTouchCalibration);
181 connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
182 &ConfigureMotionTouch::OnConfigureTouchFromButton);
183 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
184 if (CanCloseDialog()) {
185 reject();
186 }
187 });
188}
189
190void ConfigureMotionTouch::OnCemuhookUDPTest() {
191 ui->udp_test->setEnabled(false);
192 ui->udp_test->setText(tr("Testing"));
193 udp_test_in_progress = true;
194 InputCommon::CemuhookUDP::TestCommunication(
195 ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()),
196 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872,
197 [this] {
198 LOG_INFO(Frontend, "UDP input test success");
199 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
200 },
201 [this] {
202 LOG_ERROR(Frontend, "UDP input test failed");
203 QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
204 });
205}
206
207void ConfigureMotionTouch::OnConfigureTouchCalibration() {
208 ui->touch_calibration_config->setEnabled(false);
209 ui->touch_calibration_config->setText(tr("Configuring"));
210 CalibrationConfigurationDialog dialog(
211 this, ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toUInt()),
212 static_cast<u8>(ui->udp_pad_index->currentIndex()), 24872);
213 dialog.exec();
214 if (dialog.completed) {
215 min_x = dialog.min_x;
216 min_y = dialog.min_y;
217 max_x = dialog.max_x;
218 max_y = dialog.max_y;
219 LOG_INFO(Frontend,
220 "UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
221 min_x, min_y, max_x, max_y);
222 UpdateUiDisplay();
223 } else {
224 LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
225 }
226 ui->touch_calibration_config->setEnabled(true);
227 ui->touch_calibration_config->setText(tr("Configure"));
228}
229
230void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
231 if (CanCloseDialog()) {
232 event->accept();
233 } else {
234 event->ignore();
235 }
236}
237
238void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
239 udp_test_in_progress = false;
240 if (result) {
241 QMessageBox::information(this, tr("Test Successful"),
242 tr("Successfully received data from the server."));
243 } else {
244 QMessageBox::warning(this, tr("Test Failed"),
245 tr("Could not receive valid data from the server.<br>Please verify "
246 "that the server is set up correctly and "
247 "the address and port are correct."));
248 }
249 ui->udp_test->setEnabled(true);
250 ui->udp_test->setText(tr("Test"));
251}
252
253void ConfigureMotionTouch::OnConfigureTouchFromButton() {
254 ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
255 ui->touch_from_button_map->currentIndex()};
256 if (dialog.exec() != QDialog::Accepted) {
257 return;
258 }
259 touch_from_button_maps = dialog.GetMaps();
260
261 while (ui->touch_from_button_map->count() > 0) {
262 ui->touch_from_button_map->removeItem(0);
263 }
264 for (const auto& touch_map : touch_from_button_maps) {
265 ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
266 }
267 ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
268}
269
270bool ConfigureMotionTouch::CanCloseDialog() {
271 if (udp_test_in_progress) {
272 QMessageBox::warning(this, tr("Citra"),
273 tr("UDP Test or calibration configuration is in progress.<br>Please "
274 "wait for them to finish."));
275 return false;
276 }
277 return true;
278}
279
280void ConfigureMotionTouch::ApplyConfiguration() {
281 if (!CanCloseDialog()) {
282 return;
283 }
284
285 std::string motion_engine = ui->motion_provider->currentData().toString().toStdString();
286 std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
287
288 Common::ParamPackage motion_param{}, touch_param{};
289 motion_param.Set("engine", std::move(motion_engine));
290 touch_param.Set("engine", std::move(touch_engine));
291
292 if (motion_engine == "motion_emu") {
293 motion_param.Set("sensitivity", static_cast<float>(ui->motion_sensitivity->value()));
294 }
295
296 if (touch_engine == "cemuhookudp") {
297 touch_param.Set("min_x", min_x);
298 touch_param.Set("min_y", min_y);
299 touch_param.Set("max_x", max_x);
300 touch_param.Set("max_y", max_y);
301 }
302
303 Settings::values.motion_device = motion_param.Serialize();
304 Settings::values.touch_device = touch_param.Serialize();
305 Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
306 Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
307 Settings::values.touch_from_button_maps = touch_from_button_maps;
308 Settings::values.udp_input_address = ui->udp_server->text().toStdString();
309 Settings::values.udp_input_port = static_cast<u16>(ui->udp_port->text().toInt());
310 Settings::values.udp_pad_index = static_cast<u8>(ui->udp_pad_index->currentIndex());
311 input_subsystem->ReloadInputDevices();
312
313 accept();
314}
diff --git a/src/yuzu/configuration/configure_motion_touch.h b/src/yuzu/configuration/configure_motion_touch.h
new file mode 100644
index 000000000..3d4b5d659
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.h
@@ -0,0 +1,90 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QDialog>
9#include "common/param_package.h"
10
11class QLabel;
12class QPushButton;
13class QVBoxLayout;
14
15namespace InputCommon {
16class InputSubsystem;
17}
18
19namespace InputCommon::CemuhookUDP {
20class CalibrationConfigurationJob;
21}
22
23namespace Ui {
24class ConfigureMotionTouch;
25}
26
27/// A dialog for touchpad calibration configuration.
28class CalibrationConfigurationDialog : public QDialog {
29 Q_OBJECT
30public:
31 explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
32 u8 pad_index, u16 client_id);
33 ~CalibrationConfigurationDialog() override;
34
35private:
36 Q_INVOKABLE void UpdateLabelText(const QString& text);
37 Q_INVOKABLE void UpdateButtonText(const QString& text);
38
39 QVBoxLayout* layout;
40 QLabel* status_label;
41 QPushButton* cancel_button;
42 std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
43
44 // Configuration results
45 bool completed{};
46 u16 min_x{};
47 u16 min_y{};
48 u16 max_x{};
49 u16 max_y{};
50
51 friend class ConfigureMotionTouch;
52};
53
54class ConfigureMotionTouch : public QDialog {
55 Q_OBJECT
56
57public:
58 explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
59 ~ConfigureMotionTouch() override;
60
61public slots:
62 void ApplyConfiguration();
63
64private slots:
65 void OnCemuhookUDPTest();
66 void OnConfigureTouchCalibration();
67 void OnConfigureTouchFromButton();
68
69private:
70 void closeEvent(QCloseEvent* event) override;
71 Q_INVOKABLE void ShowUDPTestResult(bool result);
72 void SetConfiguration();
73 void UpdateUiDisplay();
74 void ConnectEvents();
75 bool CanCloseDialog();
76
77 InputCommon::InputSubsystem* input_subsystem;
78
79 std::unique_ptr<Ui::ConfigureMotionTouch> ui;
80
81 // Coordinate system of the CemuhookUDP touch provider
82 int min_x{};
83 int min_y{};
84 int max_x{};
85 int max_y{};
86
87 bool udp_test_in_progress{};
88
89 std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
90};
diff --git a/src/yuzu/configuration/configure_motion_touch.ui b/src/yuzu/configuration/configure_motion_touch.ui
new file mode 100644
index 000000000..602cf8cd8
--- /dev/null
+++ b/src/yuzu/configuration/configure_motion_touch.ui
@@ -0,0 +1,327 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureMotionTouch</class>
4 <widget class="QDialog" name="ConfigureMotionTouch">
5 <property name="windowTitle">
6 <string>Configure Motion / Touch</string>
7 </property>
8 <property name="geometry">
9 <rect>
10 <x>0</x>
11 <y>0</y>
12 <width>500</width>
13 <height>450</height>
14 </rect>
15 </property>
16 <layout class="QVBoxLayout">
17 <item>
18 <widget class="QGroupBox" name="motion_group_box">
19 <property name="title">
20 <string>Motion</string>
21 </property>
22 <layout class="QVBoxLayout">
23 <item>
24 <layout class="QHBoxLayout">
25 <item>
26 <widget class="QLabel" name="motion_provider_label">
27 <property name="text">
28 <string>Motion Provider:</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <widget class="QComboBox" name="motion_provider"/>
34 </item>
35 </layout>
36 </item>
37 <item>
38 <layout class="QHBoxLayout">
39 <item>
40 <widget class="QLabel" name="motion_sensitivity_label">
41 <property name="text">
42 <string>Sensitivity:</string>
43 </property>
44 </widget>
45 </item>
46 <item>
47 <widget class="QDoubleSpinBox" name="motion_sensitivity">
48 <property name="alignment">
49 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
50 </property>
51 <property name="decimals">
52 <number>4</number>
53 </property>
54 <property name="minimum">
55 <double>0.010000000000000</double>
56 </property>
57 <property name="maximum">
58 <double>10.000000000000000</double>
59 </property>
60 <property name="singleStep">
61 <double>0.001000000000000</double>
62 </property>
63 <property name="value">
64 <double>0.010000000000000</double>
65 </property>
66 </widget>
67 </item>
68 </layout>
69 </item>
70 </layout>
71 </widget>
72 </item>
73 <item>
74 <widget class="QGroupBox" name="touch_group_box">
75 <property name="title">
76 <string>Touch</string>
77 </property>
78 <layout class="QVBoxLayout">
79 <item>
80 <layout class="QHBoxLayout">
81 <item>
82 <widget class="QLabel" name="touch_provider_label">
83 <property name="text">
84 <string>Touch Provider:</string>
85 </property>
86 </widget>
87 </item>
88 <item>
89 <widget class="QComboBox" name="touch_provider"/>
90 </item>
91 </layout>
92 </item>
93 <item>
94 <layout class="QHBoxLayout">
95 <item>
96 <widget class="QLabel" name="touch_calibration_label">
97 <property name="text">
98 <string>Calibration:</string>
99 </property>
100 </widget>
101 </item>
102 <item>
103 <widget class="QLabel" name="touch_calibration">
104 <property name="text">
105 <string>(100, 50) - (1800, 850)</string>
106 </property>
107 <property name="alignment">
108 <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
109 </property>
110 </widget>
111 </item>
112 <item>
113 <widget class="QPushButton" name="touch_calibration_config">
114 <property name="sizePolicy">
115 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
116 <horstretch>0</horstretch>
117 <verstretch>0</verstretch>
118 </sizepolicy>
119 </property>
120 <property name="text">
121 <string>Configure</string>
122 </property>
123 </widget>
124 </item>
125 </layout>
126 </item>
127 <item>
128 <layout class="QHBoxLayout">
129 <item>
130 <widget class="QCheckBox" name="touch_from_button_checkbox">
131 <property name="sizePolicy">
132 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
133 <horstretch>0</horstretch>
134 <verstretch>0</verstretch>
135 </sizepolicy>
136 </property>
137 <property name="text">
138 <string>Use button mapping:</string>
139 </property>
140 </widget>
141 </item>
142 <item>
143 <widget class="QComboBox" name="touch_from_button_map"/>
144 </item>
145 <item>
146 <widget class="QPushButton" name="touch_from_button_config_btn">
147 <property name="sizePolicy">
148 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
149 <horstretch>0</horstretch>
150 <verstretch>0</verstretch>
151 </sizepolicy>
152 </property>
153 <property name="text">
154 <string>Configure</string>
155 </property>
156 </widget>
157 </item>
158 </layout>
159 </item>
160 </layout>
161 </widget>
162 </item>
163 <item>
164 <widget class="QGroupBox" name="udp_config_group_box">
165 <property name="title">
166 <string>CemuhookUDP Config</string>
167 </property>
168 <layout class="QVBoxLayout">
169 <item>
170 <widget class="QLabel" name="udp_help">
171 <property name="text">
172 <string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
173 </property>
174 <property name="alignment">
175 <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
176 </property>
177 <property name="wordWrap">
178 <bool>true</bool>
179 </property>
180 </widget>
181 </item>
182 <item>
183 <layout class="QHBoxLayout">
184 <item>
185 <widget class="QLabel" name="udp_server_label">
186 <property name="text">
187 <string>Server:</string>
188 </property>
189 </widget>
190 </item>
191 <item>
192 <widget class="QLineEdit" name="udp_server">
193 <property name="sizePolicy">
194 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
195 <horstretch>0</horstretch>
196 <verstretch>0</verstretch>
197 </sizepolicy>
198 </property>
199 </widget>
200 </item>
201 </layout>
202 </item>
203 <item>
204 <layout class="QHBoxLayout">
205 <item>
206 <widget class="QLabel" name="udp_port_label">
207 <property name="text">
208 <string>Port:</string>
209 </property>
210 </widget>
211 </item>
212 <item>
213 <widget class="QLineEdit" name="udp_port">
214 <property name="sizePolicy">
215 <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
216 <horstretch>0</horstretch>
217 <verstretch>0</verstretch>
218 </sizepolicy>
219 </property>
220 </widget>
221 </item>
222 </layout>
223 </item>
224 <item>
225 <layout class="QHBoxLayout">
226 <item>
227 <widget class="QLabel" name="udp_pad_index_label">
228 <property name="text">
229 <string>Pad:</string>
230 </property>
231 </widget>
232 </item>
233 <item>
234 <widget class="QComboBox" name="udp_pad_index">
235 <item>
236 <property name="text">
237 <string>Pad 1</string>
238 </property>
239 </item>
240 <item>
241 <property name="text">
242 <string>Pad 2</string>
243 </property>
244 </item>
245 <item>
246 <property name="text">
247 <string>Pad 3</string>
248 </property>
249 </item>
250 <item>
251 <property name="text">
252 <string>Pad 4</string>
253 </property>
254 </item>
255 </widget>
256 </item>
257 </layout>
258 </item>
259 <item>
260 <layout class="QHBoxLayout">
261 <item>
262 <widget class="QLabel" name="udp_learn_more">
263 <property name="text">
264 <string>Learn More</string>
265 </property>
266 </widget>
267 </item>
268 <item>
269 <widget class="QPushButton" name="udp_test">
270 <property name="sizePolicy">
271 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
272 <horstretch>0</horstretch>
273 <verstretch>0</verstretch>
274 </sizepolicy>
275 </property>
276 <property name="text">
277 <string>Test</string>
278 </property>
279 </widget>
280 </item>
281 </layout>
282 </item>
283 </layout>
284 </widget>
285 </item>
286 <item>
287 <spacer>
288 <property name="orientation">
289 <enum>Qt::Vertical</enum>
290 </property>
291 <property name="sizeHint" stdset="0">
292 <size>
293 <width>167</width>
294 <height>55</height>
295 </size>
296 </property>
297 </spacer>
298 </item>
299 <item>
300 <widget class="QDialogButtonBox" name="buttonBox">
301 <property name="standardButtons">
302 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
303 </property>
304 </widget>
305 </item>
306 </layout>
307 </widget>
308 <resources/>
309 <connections>
310 <connection>
311 <sender>buttonBox</sender>
312 <signal>accepted()</signal>
313 <receiver>ConfigureMotionTouch</receiver>
314 <slot>ApplyConfiguration()</slot>
315 <hints>
316 <hint type="sourcelabel">
317 <x>220</x>
318 <y>380</y>
319 </hint>
320 <hint type="destinationlabel">
321 <x>220</x>
322 <y>200</y>
323 </hint>
324 </hints>
325 </connection>
326 </connections>
327</ui>
diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp
index dcda8ab14..2af3afda8 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.cpp
+++ b/src/yuzu/configuration/configure_mouse_advanced.cpp
@@ -76,8 +76,10 @@ static QString ButtonToText(const Common::ParamPackage& param) {
76 return QObject::tr("[unknown]"); 76 return QObject::tr("[unknown]");
77} 77}
78 78
79ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) 79ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
80 : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), 80 InputCommon::InputSubsystem* input_subsystem_)
81 : QDialog(parent),
82 ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},
81 timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { 83 timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
82 ui->setupUi(this); 84 ui->setupUi(this);
83 setFocusPolicy(Qt::ClickFocus); 85 setFocusPolicy(Qt::ClickFocus);
@@ -209,7 +211,7 @@ void ConfigureMouseAdvanced::HandleClick(
209 211
210 input_setter = new_input_setter; 212 input_setter = new_input_setter;
211 213
212 device_pollers = InputCommon::Polling::GetPollers(type); 214 device_pollers = input_subsystem->GetPollers(type);
213 215
214 for (auto& poller : device_pollers) { 216 for (auto& poller : device_pollers) {
215 poller->Start(); 217 poller->Start();
diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h
index e7d27dab7..65b6fca9a 100644
--- a/src/yuzu/configuration/configure_mouse_advanced.h
+++ b/src/yuzu/configuration/configure_mouse_advanced.h
@@ -8,12 +8,14 @@
8#include <optional> 8#include <optional>
9#include <QDialog> 9#include <QDialog>
10 10
11#include "core/settings.h"
12
13class QCheckBox; 11class QCheckBox;
14class QPushButton; 12class QPushButton;
15class QTimer; 13class QTimer;
16 14
15namespace InputCommon {
16class InputSubsystem;
17}
18
17namespace Ui { 19namespace Ui {
18class ConfigureMouseAdvanced; 20class ConfigureMouseAdvanced;
19} 21}
@@ -22,7 +24,7 @@ class ConfigureMouseAdvanced : public QDialog {
22 Q_OBJECT 24 Q_OBJECT
23 25
24public: 26public:
25 explicit ConfigureMouseAdvanced(QWidget* parent); 27 explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
26 ~ConfigureMouseAdvanced() override; 28 ~ConfigureMouseAdvanced() override;
27 29
28 void ApplyConfiguration(); 30 void ApplyConfiguration();
@@ -57,6 +59,8 @@ private:
57 59
58 std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; 60 std::unique_ptr<Ui::ConfigureMouseAdvanced> ui;
59 61
62 InputCommon::InputSubsystem* input_subsystem;
63
60 /// This will be the the setting function when an input is awaiting configuration. 64 /// This will be the the setting function when an input is awaiting configuration.
61 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; 65 std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
62 66
diff --git a/src/yuzu/configuration/configure_touch_from_button.cpp b/src/yuzu/configuration/configure_touch_from_button.cpp
new file mode 100644
index 000000000..15557e4b8
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.cpp
@@ -0,0 +1,623 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <QInputDialog>
6#include <QKeyEvent>
7#include <QMessageBox>
8#include <QMouseEvent>
9#include <QResizeEvent>
10#include <QStandardItemModel>
11#include <QTimer>
12#include "common/param_package.h"
13#include "core/frontend/framebuffer_layout.h"
14#include "core/settings.h"
15#include "input_common/main.h"
16#include "ui_configure_touch_from_button.h"
17#include "yuzu/configuration/configure_touch_from_button.h"
18#include "yuzu/configuration/configure_touch_widget.h"
19
20static QString GetKeyName(int key_code) {
21 switch (key_code) {
22 case Qt::Key_Shift:
23 return QObject::tr("Shift");
24 case Qt::Key_Control:
25 return QObject::tr("Ctrl");
26 case Qt::Key_Alt:
27 return QObject::tr("Alt");
28 case Qt::Key_Meta:
29 return QString{};
30 default:
31 return QKeySequence(key_code).toString();
32 }
33}
34
35static QString ButtonToText(const Common::ParamPackage& param) {
36 if (!param.Has("engine")) {
37 return QObject::tr("[not set]");
38 }
39
40 if (param.Get("engine", "") == "keyboard") {
41 return GetKeyName(param.Get("code", 0));
42 }
43
44 if (param.Get("engine", "") == "sdl") {
45 if (param.Has("hat")) {
46 const QString hat_str = QString::fromStdString(param.Get("hat", ""));
47 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
48
49 return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
50 }
51
52 if (param.Has("axis")) {
53 const QString axis_str = QString::fromStdString(param.Get("axis", ""));
54 const QString direction_str = QString::fromStdString(param.Get("direction", ""));
55
56 return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
57 }
58
59 if (param.Has("button")) {
60 const QString button_str = QString::fromStdString(param.Get("button", ""));
61
62 return QObject::tr("Button %1").arg(button_str);
63 }
64
65 return {};
66 }
67
68 return QObject::tr("[unknown]");
69}
70
71ConfigureTouchFromButton::ConfigureTouchFromButton(
72 QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
73 InputCommon::InputSubsystem* input_subsystem_, const int default_index)
74 : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
75 touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
76 timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
77 ui->setupUi(this);
78 binding_list_model = new QStandardItemModel(0, 3, this);
79 binding_list_model->setHorizontalHeaderLabels(
80 {tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
81 ui->binding_list->setModel(binding_list_model);
82 ui->bottom_screen->SetCoordLabel(ui->coord_label);
83
84 SetConfiguration();
85 UpdateUiDisplay();
86 ConnectEvents();
87}
88
89ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
90
91void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
92 QWidget::showEvent(ev);
93
94 // width values are not valid in the constructor
95 const int w =
96 ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
97 if (w <= 0) {
98 return;
99 }
100 ui->binding_list->setColumnWidth(0, w);
101 ui->binding_list->setColumnWidth(1, w);
102 ui->binding_list->setColumnWidth(2, w);
103}
104
105void ConfigureTouchFromButton::SetConfiguration() {
106 for (const auto& touch_map : touch_maps) {
107 ui->mapping->addItem(QString::fromStdString(touch_map.name));
108 }
109
110 ui->mapping->setCurrentIndex(selected_index);
111}
112
113void ConfigureTouchFromButton::UpdateUiDisplay() {
114 ui->button_delete->setEnabled(touch_maps.size() > 1);
115 ui->button_delete_bind->setEnabled(false);
116
117 binding_list_model->removeRows(0, binding_list_model->rowCount());
118
119 for (const auto& button_str : touch_maps[selected_index].buttons) {
120 Common::ParamPackage package{button_str};
121 QStandardItem* button = new QStandardItem(ButtonToText(package));
122 button->setData(QString::fromStdString(button_str));
123 button->setEditable(false);
124 QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
125 QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
126 binding_list_model->appendRow({button, xcoord, ycoord});
127
128 const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
129 button->setData(dot, DataRoleDot);
130 }
131}
132
133void ConfigureTouchFromButton::ConnectEvents() {
134 connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
135 SaveCurrentMapping();
136 selected_index = index;
137 UpdateUiDisplay();
138 });
139 connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
140 connect(ui->button_delete, &QPushButton::clicked, this,
141 &ConfigureTouchFromButton::DeleteMapping);
142 connect(ui->button_rename, &QPushButton::clicked, this,
143 &ConfigureTouchFromButton::RenameMapping);
144 connect(ui->button_delete_bind, &QPushButton::clicked, this,
145 &ConfigureTouchFromButton::DeleteBinding);
146 connect(ui->binding_list, &QTreeView::doubleClicked, this,
147 &ConfigureTouchFromButton::EditBinding);
148 connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
149 &ConfigureTouchFromButton::OnBindingSelection);
150 connect(binding_list_model, &QStandardItemModel::itemChanged, this,
151 &ConfigureTouchFromButton::OnBindingChanged);
152 connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
153 &ConfigureTouchFromButton::OnBindingDeleted);
154 connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
155 &ConfigureTouchFromButton::NewBinding);
156 connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
157 &ConfigureTouchFromButton::SetActiveBinding);
158 connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
159 &ConfigureTouchFromButton::SetCoordinates);
160 connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
161 &ConfigureTouchFromButton::ApplyConfiguration);
162
163 connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
164
165 connect(poll_timer.get(), &QTimer::timeout, [this]() {
166 Common::ParamPackage params;
167 for (auto& poller : device_pollers) {
168 params = poller->GetNextInput();
169 if (params.Has("engine")) {
170 SetPollingResult(params, false);
171 return;
172 }
173 }
174 });
175}
176
177void ConfigureTouchFromButton::SaveCurrentMapping() {
178 auto& map = touch_maps[selected_index];
179 map.buttons.clear();
180 for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
181 const auto bind_str = binding_list_model->index(i, 0)
182 .data(Qt::ItemDataRole::UserRole + 1)
183 .toString()
184 .toStdString();
185 if (bind_str.empty()) {
186 continue;
187 }
188 Common::ParamPackage params{bind_str};
189 if (!params.Has("engine")) {
190 continue;
191 }
192 params.Set("x", binding_list_model->index(i, 1).data().toInt());
193 params.Set("y", binding_list_model->index(i, 2).data().toInt());
194 map.buttons.emplace_back(params.Serialize());
195 }
196}
197
198void ConfigureTouchFromButton::NewMapping() {
199 const QString name =
200 QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
201 if (name.isEmpty()) {
202 return;
203 }
204 touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
205 ui->mapping->addItem(name);
206 ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
207}
208
209void ConfigureTouchFromButton::DeleteMapping() {
210 const auto answer = QMessageBox::question(
211 this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
212 if (answer != QMessageBox::Yes) {
213 return;
214 }
215 const bool blocked = ui->mapping->blockSignals(true);
216 ui->mapping->removeItem(selected_index);
217 ui->mapping->blockSignals(blocked);
218 touch_maps.erase(touch_maps.begin() + selected_index);
219 selected_index = ui->mapping->currentIndex();
220 UpdateUiDisplay();
221}
222
223void ConfigureTouchFromButton::RenameMapping() {
224 const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
225 if (new_name.isEmpty()) {
226 return;
227 }
228 ui->mapping->setItemText(selected_index, new_name);
229 touch_maps[selected_index].name = new_name.toStdString();
230}
231
232void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
233 binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
234
235 input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
236 const bool cancel) {
237 auto* cell = binding_list_model->item(row_index, 0);
238 if (cancel) {
239 if (is_new) {
240 binding_list_model->removeRow(row_index);
241 } else {
242 cell->setText(
243 ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
244 }
245 } else {
246 cell->setText(ButtonToText(params));
247 cell->setData(QString::fromStdString(params.Serialize()));
248 }
249 };
250
251 device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
252
253 for (auto& poller : device_pollers) {
254 poller->Start();
255 }
256
257 grabKeyboard();
258 grabMouse();
259 qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
260 timeout_timer->start(5000); // Cancel after 5 seconds
261 poll_timer->start(200); // Check for new inputs every 200ms
262}
263
264void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
265 auto* button = new QStandardItem();
266 button->setEditable(false);
267 auto* x_coord = new QStandardItem(QString::number(pos.x()));
268 auto* y_coord = new QStandardItem(QString::number(pos.y()));
269
270 const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
271 button->setData(dot_id, DataRoleDot);
272
273 binding_list_model->appendRow({button, x_coord, y_coord});
274 ui->binding_list->setFocus();
275 ui->binding_list->setCurrentIndex(button->index());
276
277 GetButtonInput(binding_list_model->rowCount() - 1, true);
278}
279
280void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
281 if (qi.row() >= 0 && qi.column() == 0) {
282 GetButtonInput(qi.row(), false);
283 }
284}
285
286void ConfigureTouchFromButton::DeleteBinding() {
287 const int row_index = ui->binding_list->currentIndex().row();
288 if (row_index < 0) {
289 return;
290 }
291 ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
292 binding_list_model->removeRow(row_index);
293}
294
295void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
296 const QItemSelection& deselected) {
297 ui->button_delete_bind->setEnabled(!selected.isEmpty());
298 if (!selected.isEmpty()) {
299 const auto dot_data = selected.indexes().first().data(DataRoleDot);
300 if (dot_data.isValid()) {
301 ui->bottom_screen->HighlightDot(dot_data.toInt());
302 }
303 }
304 if (!deselected.isEmpty()) {
305 const auto dot_data = deselected.indexes().first().data(DataRoleDot);
306 if (dot_data.isValid()) {
307 ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
308 }
309 }
310}
311
312void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
313 if (item->column() == 0) {
314 return;
315 }
316
317 const bool blocked = binding_list_model->blockSignals(true);
318 item->setText(QString::number(
319 std::clamp(item->text().toInt(), 0,
320 static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
321 : Layout::ScreenUndocked::Height) -
322 1))));
323 binding_list_model->blockSignals(blocked);
324
325 const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
326 if (dot_data.isValid()) {
327 ui->bottom_screen->MoveDot(dot_data.toInt(),
328 binding_list_model->item(item->row(), 1)->text().toInt(),
329 binding_list_model->item(item->row(), 2)->text().toInt());
330 }
331}
332
333void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
334 for (int i = first; i <= last; ++i) {
335 const auto ix = binding_list_model->index(i, 0);
336 if (!ix.isValid()) {
337 return;
338 }
339 const auto dot_data = ix.data(DataRoleDot);
340 if (dot_data.isValid()) {
341 ui->bottom_screen->RemoveDot(dot_data.toInt());
342 }
343 }
344}
345
346void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
347 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
348 if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
349 ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
350 ui->binding_list->setFocus();
351 return;
352 }
353 }
354}
355
356void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
357 for (int i = 0; i < binding_list_model->rowCount(); ++i) {
358 if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
359 binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
360 binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
361 return;
362 }
363 }
364}
365
366void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
367 const bool cancel) {
368 releaseKeyboard();
369 releaseMouse();
370 qApp->restoreOverrideCursor();
371 timeout_timer->stop();
372 poll_timer->stop();
373 for (auto& poller : device_pollers) {
374 poller->Stop();
375 }
376 if (input_setter) {
377 (*input_setter)(params, cancel);
378 input_setter.reset();
379 }
380}
381
382void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
383 if (!input_setter && event->key() == Qt::Key_Delete) {
384 DeleteBinding();
385 return;
386 }
387
388 if (!input_setter) {
389 return QDialog::keyPressEvent(event);
390 }
391
392 if (event->key() != Qt::Key_Escape) {
393 SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
394 false);
395 } else {
396 SetPollingResult({}, true);
397 }
398}
399
400void ConfigureTouchFromButton::ApplyConfiguration() {
401 SaveCurrentMapping();
402 accept();
403}
404
405int ConfigureTouchFromButton::GetSelectedIndex() const {
406 return selected_index;
407}
408
409std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
410 return touch_maps;
411}
412
413TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
414 setBackgroundRole(QPalette::ColorRole::Base);
415}
416
417TouchScreenPreview::~TouchScreenPreview() = default;
418
419void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
420 coord_label = label;
421}
422
423int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
424 QFont dot_font{QStringLiteral("monospace")};
425 dot_font.setStyleHint(QFont::Monospace);
426 dot_font.setPointSize(20);
427
428 auto* dot = new QLabel(this);
429 dot->setAttribute(Qt::WA_TranslucentBackground);
430 dot->setFont(dot_font);
431 dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
432 dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
433 dot->setProperty(PropId, ++max_dot_id);
434 dot->setProperty(PropX, device_x);
435 dot->setProperty(PropY, device_y);
436 dot->setCursor(Qt::CursorShape::PointingHandCursor);
437 dot->setMouseTracking(true);
438 dot->installEventFilter(this);
439 dot->show();
440 PositionDot(dot, device_x, device_y);
441 dots.emplace_back(max_dot_id, dot);
442 return max_dot_id;
443}
444
445void TouchScreenPreview::RemoveDot(const int id) {
446 const auto iter = std::find_if(dots.begin(), dots.end(),
447 [id](const auto& entry) { return entry.first == id; });
448 if (iter == dots.cend()) {
449 return;
450 }
451
452 iter->second->deleteLater();
453 dots.erase(iter);
454}
455
456void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
457 for (const auto& dot : dots) {
458 if (dot.first == id) {
459 // use color property from the stylesheet, or fall back to the default palette
460 if (dot_highlight_color.isValid()) {
461 dot.second->setStyleSheet(
462 active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
463 : QString{});
464 } else {
465 dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
466 : QPalette::ColorRole::NoRole);
467 }
468 if (active) {
469 dot.second->raise();
470 }
471 return;
472 }
473 }
474}
475
476void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
477 const auto iter = std::find_if(dots.begin(), dots.end(),
478 [id](const auto& entry) { return entry.first == id; });
479 if (iter == dots.cend()) {
480 return;
481 }
482
483 iter->second->setProperty(PropX, device_x);
484 iter->second->setProperty(PropY, device_y);
485 PositionDot(iter->second, device_x, device_y);
486}
487
488void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
489 if (ignore_resize) {
490 return;
491 }
492
493 const int target_width = std::min(width(), height() * 4 / 3);
494 const int target_height = std::min(height(), width() * 3 / 4);
495 if (target_width == width() && target_height == height()) {
496 return;
497 }
498 ignore_resize = true;
499 setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
500 target_height);
501 ignore_resize = false;
502
503 if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
504 for (const auto& dot : dots) {
505 PositionDot(dot.second);
506 }
507 }
508}
509
510void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
511 if (!coord_label) {
512 return;
513 }
514 const auto pos = MapToDeviceCoords(event->x(), event->y());
515 if (pos) {
516 coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
517 } else {
518 coord_label->clear();
519 }
520}
521
522void TouchScreenPreview::leaveEvent(QEvent* event) {
523 if (coord_label) {
524 coord_label->clear();
525 }
526}
527
528void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
529 if (event->button() != Qt::MouseButton::LeftButton) {
530 return;
531 }
532 const auto pos = MapToDeviceCoords(event->x(), event->y());
533 if (pos) {
534 emit DotAdded(*pos);
535 }
536}
537
538bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
539 switch (event->type()) {
540 case QEvent::Type::MouseButtonPress: {
541 const auto mouse_event = static_cast<QMouseEvent*>(event);
542 if (mouse_event->button() != Qt::MouseButton::LeftButton) {
543 break;
544 }
545 emit DotSelected(obj->property(PropId).toInt());
546
547 drag_state.dot = qobject_cast<QLabel*>(obj);
548 drag_state.start_pos = mouse_event->globalPos();
549 return true;
550 }
551 case QEvent::Type::MouseMove: {
552 if (!drag_state.dot) {
553 break;
554 }
555 const auto mouse_event = static_cast<QMouseEvent*>(event);
556 if (!drag_state.active) {
557 drag_state.active =
558 (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
559 QApplication::startDragDistance();
560 if (!drag_state.active) {
561 break;
562 }
563 }
564 auto current_pos = mapFromGlobal(mouse_event->globalPos());
565 current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
566 contentsMargins().left() + contentsRect().width() - 1));
567 current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
568 contentsMargins().top() + contentsRect().height() - 1));
569 const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
570 if (device_coord) {
571 drag_state.dot->setProperty(PropX, device_coord->x());
572 drag_state.dot->setProperty(PropY, device_coord->y());
573 PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
574 emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
575 if (coord_label) {
576 coord_label->setText(
577 QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
578 }
579 }
580 return true;
581 }
582 case QEvent::Type::MouseButtonRelease: {
583 drag_state.dot.clear();
584 drag_state.active = false;
585 return true;
586 }
587 default:
588 break;
589 }
590 return obj->eventFilter(obj, event);
591}
592
593std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
594 const int screen_y) const {
595 const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
596 (Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
597 const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
598 (Layout::ScreenUndocked::Height - 1) /
599 (contentsRect().height() - 1);
600 if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
601 t_y < Layout::ScreenUndocked::Height) {
602
603 return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
604 }
605 return std::nullopt;
606}
607
608void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
609 const int device_y) const {
610 const float device_coord_x =
611 static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
612 int x_coord = static_cast<int>(
613 device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
614 contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
615
616 const float device_coord_y =
617 static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
618 const int y_coord = static_cast<int>(
619 device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
620 contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
621
622 dot->move(x_coord, y_coord);
623}
diff --git a/src/yuzu/configuration/configure_touch_from_button.h b/src/yuzu/configuration/configure_touch_from_button.h
new file mode 100644
index 000000000..d9513e3bc
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.h
@@ -0,0 +1,92 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <optional>
10#include <vector>
11#include <QDialog>
12
13class QItemSelection;
14class QModelIndex;
15class QStandardItemModel;
16class QStandardItem;
17class QTimer;
18
19namespace Common {
20class ParamPackage;
21}
22
23namespace InputCommon {
24class InputSubsystem;
25}
26
27namespace InputCommon::Polling {
28class DevicePoller;
29}
30
31namespace Settings {
32struct TouchFromButtonMap;
33}
34
35namespace Ui {
36class ConfigureTouchFromButton;
37}
38
39class ConfigureTouchFromButton : public QDialog {
40 Q_OBJECT
41
42public:
43 explicit ConfigureTouchFromButton(QWidget* parent,
44 const std::vector<Settings::TouchFromButtonMap>& touch_maps,
45 InputCommon::InputSubsystem* input_subsystem_,
46 int default_index = 0);
47 ~ConfigureTouchFromButton() override;
48
49 int GetSelectedIndex() const;
50 std::vector<Settings::TouchFromButtonMap> GetMaps() const;
51
52public slots:
53 void ApplyConfiguration();
54 void NewBinding(const QPoint& pos);
55 void SetActiveBinding(int dot_id);
56 void SetCoordinates(int dot_id, const QPoint& pos);
57
58protected:
59 void showEvent(QShowEvent* ev) override;
60 void keyPressEvent(QKeyEvent* event) override;
61
62private slots:
63 void NewMapping();
64 void DeleteMapping();
65 void RenameMapping();
66 void EditBinding(const QModelIndex& qi);
67 void DeleteBinding();
68 void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
69 void OnBindingChanged(QStandardItem* item);
70 void OnBindingDeleted(const QModelIndex& parent, int first, int last);
71
72private:
73 void SetConfiguration();
74 void UpdateUiDisplay();
75 void ConnectEvents();
76 void GetButtonInput(int row_index, bool is_new);
77 void SetPollingResult(const Common::ParamPackage& params, bool cancel);
78 void SaveCurrentMapping();
79
80 std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
81 std::vector<Settings::TouchFromButtonMap> touch_maps;
82 QStandardItemModel* binding_list_model;
83 InputCommon::InputSubsystem* input_subsystem;
84 int selected_index;
85
86 std::unique_ptr<QTimer> timeout_timer;
87 std::unique_ptr<QTimer> poll_timer;
88 std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
89 std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
90
91 static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
92};
diff --git a/src/yuzu/configuration/configure_touch_from_button.ui b/src/yuzu/configuration/configure_touch_from_button.ui
new file mode 100644
index 000000000..f581e27e0
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_from_button.ui
@@ -0,0 +1,231 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureTouchFromButton</class>
4 <widget class="QDialog" name="ConfigureTouchFromButton">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>500</width>
10 <height>500</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Configure Touchscreen Mappings</string>
15 </property>
16 <layout class="QVBoxLayout">
17 <item>
18 <layout class="QHBoxLayout" name="horizontalLayout">
19 <item>
20 <widget class="QLabel" name="label">
21 <property name="text">
22 <string>Mapping:</string>
23 </property>
24 <property name="textFormat">
25 <enum>Qt::PlainText</enum>
26 </property>
27 </widget>
28 </item>
29 <item>
30 <widget class="QComboBox" name="mapping">
31 <property name="sizePolicy">
32 <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
33 <horstretch>0</horstretch>
34 <verstretch>0</verstretch>
35 </sizepolicy>
36 </property>
37 </widget>
38 </item>
39 <item>
40 <widget class="QPushButton" name="button_new">
41 <property name="sizePolicy">
42 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
43 <horstretch>0</horstretch>
44 <verstretch>0</verstretch>
45 </sizepolicy>
46 </property>
47 <property name="text">
48 <string>New</string>
49 </property>
50 </widget>
51 </item>
52 <item>
53 <widget class="QPushButton" name="button_delete">
54 <property name="sizePolicy">
55 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
56 <horstretch>0</horstretch>
57 <verstretch>0</verstretch>
58 </sizepolicy>
59 </property>
60 <property name="text">
61 <string>Delete</string>
62 </property>
63 </widget>
64 </item>
65 <item>
66 <widget class="QPushButton" name="button_rename">
67 <property name="sizePolicy">
68 <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
69 <horstretch>0</horstretch>
70 <verstretch>0</verstretch>
71 </sizepolicy>
72 </property>
73 <property name="text">
74 <string>Rename</string>
75 </property>
76 </widget>
77 </item>
78 </layout>
79 </item>
80 <item>
81 <widget class="Line" name="line">
82 <property name="orientation">
83 <enum>Qt::Horizontal</enum>
84 </property>
85 </widget>
86 </item>
87 <item>
88 <layout class="QHBoxLayout" name="horizontalLayout_2">
89 <item>
90 <widget class="QLabel" name="label_2">
91 <property name="text">
92 <string>Click the bottom area to add a point, then press a button to bind.
93Drag points to change position, or double-click table cells to edit values.</string>
94 </property>
95 <property name="textFormat">
96 <enum>Qt::PlainText</enum>
97 </property>
98 </widget>
99 </item>
100 <item>
101 <spacer name="horizontalSpacer">
102 <property name="orientation">
103 <enum>Qt::Horizontal</enum>
104 </property>
105 <property name="sizeHint" stdset="0">
106 <size>
107 <width>40</width>
108 <height>20</height>
109 </size>
110 </property>
111 </spacer>
112 </item>
113 <item>
114 <widget class="QPushButton" name="button_delete_bind">
115 <property name="text">
116 <string>Delete Point</string>
117 </property>
118 </widget>
119 </item>
120 </layout>
121 </item>
122 <item>
123 <widget class="QTreeView" name="binding_list">
124 <property name="sizePolicy">
125 <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
126 <horstretch>0</horstretch>
127 <verstretch>0</verstretch>
128 </sizepolicy>
129 </property>
130 <property name="rootIsDecorated">
131 <bool>false</bool>
132 </property>
133 <property name="uniformRowHeights">
134 <bool>true</bool>
135 </property>
136 <property name="itemsExpandable">
137 <bool>false</bool>
138 </property>
139 </widget>
140 </item>
141 <item>
142 <widget class="TouchScreenPreview" name="bottom_screen">
143 <property name="sizePolicy">
144 <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
145 <horstretch>0</horstretch>
146 <verstretch>0</verstretch>
147 </sizepolicy>
148 </property>
149 <property name="minimumSize">
150 <size>
151 <width>160</width>
152 <height>120</height>
153 </size>
154 </property>
155 <property name="baseSize">
156 <size>
157 <width>320</width>
158 <height>240</height>
159 </size>
160 </property>
161 <property name="cursor">
162 <cursorShape>CrossCursor</cursorShape>
163 </property>
164 <property name="mouseTracking">
165 <bool>true</bool>
166 </property>
167 <property name="autoFillBackground">
168 <bool>true</bool>
169 </property>
170 <property name="frameShape">
171 <enum>QFrame::StyledPanel</enum>
172 </property>
173 <property name="frameShadow">
174 <enum>QFrame::Sunken</enum>
175 </property>
176 </widget>
177 </item>
178 <item>
179 <layout class="QHBoxLayout" name="horizontalLayout_3">
180 <item>
181 <widget class="QLabel" name="coord_label">
182 <property name="sizePolicy">
183 <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
184 <horstretch>0</horstretch>
185 <verstretch>0</verstretch>
186 </sizepolicy>
187 </property>
188 <property name="textFormat">
189 <enum>Qt::PlainText</enum>
190 </property>
191 </widget>
192 </item>
193 <item>
194 <widget class="QDialogButtonBox" name="buttonBox">
195 <property name="standardButtons">
196 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
197 </property>
198 </widget>
199 </item>
200 </layout>
201 </item>
202 </layout>
203 </widget>
204 <customwidgets>
205 <customwidget>
206 <class>TouchScreenPreview</class>
207 <extends>QFrame</extends>
208 <header>yuzu/configuration/configure_touch_widget.h</header>
209 <container>1</container>
210 </customwidget>
211 </customwidgets>
212 <resources/>
213 <connections>
214 <connection>
215 <sender>buttonBox</sender>
216 <signal>rejected()</signal>
217 <receiver>ConfigureTouchFromButton</receiver>
218 <slot>reject()</slot>
219 <hints>
220 <hint type="sourcelabel">
221 <x>249</x>
222 <y>428</y>
223 </hint>
224 <hint type="destinationlabel">
225 <x>249</x>
226 <y>224</y>
227 </hint>
228 </hints>
229 </connection>
230 </connections>
231</ui>
diff --git a/src/yuzu/configuration/configure_touch_widget.h b/src/yuzu/configuration/configure_touch_widget.h
new file mode 100644
index 000000000..347b46583
--- /dev/null
+++ b/src/yuzu/configuration/configure_touch_widget.h
@@ -0,0 +1,62 @@
1// Copyright 2020 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <optional>
8#include <utility>
9#include <vector>
10#include <QFrame>
11#include <QPointer>
12
13class QLabel;
14
15// Widget for representing touchscreen coordinates
16class TouchScreenPreview : public QFrame {
17 Q_OBJECT
18 Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
19
20public:
21 explicit TouchScreenPreview(QWidget* parent);
22 ~TouchScreenPreview() override;
23
24 void SetCoordLabel(QLabel*);
25 int AddDot(int device_x, int device_y);
26 void RemoveDot(int id);
27 void HighlightDot(int id, bool active = true) const;
28 void MoveDot(int id, int device_x, int device_y) const;
29
30signals:
31 void DotAdded(const QPoint& pos);
32 void DotSelected(int dot_id);
33 void DotMoved(int dot_id, const QPoint& pos);
34
35protected:
36 void resizeEvent(QResizeEvent*) override;
37 void mouseMoveEvent(QMouseEvent*) override;
38 void leaveEvent(QEvent*) override;
39 void mousePressEvent(QMouseEvent*) override;
40 bool eventFilter(QObject*, QEvent*) override;
41
42private:
43 std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
44 void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
45
46 bool ignore_resize = false;
47 QPointer<QLabel> coord_label;
48
49 std::vector<std::pair<int, QLabel*>> dots;
50 int max_dot_id = 0;
51 QColor dot_highlight_color;
52 static constexpr char PropId[] = "dot_id";
53 static constexpr char PropX[] = "device_x";
54 static constexpr char PropY[] = "device_y";
55
56 struct DragState {
57 bool active = false;
58 QPointer<QLabel> dot;
59 QPoint start_pos;
60 };
61 DragState drag_state;
62};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index cd7e78eb4..a1b61d119 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -94,6 +94,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
94#include "core/perf_stats.h" 94#include "core/perf_stats.h"
95#include "core/settings.h" 95#include "core/settings.h"
96#include "core/telemetry_session.h" 96#include "core/telemetry_session.h"
97#include "input_common/main.h"
97#include "video_core/gpu.h" 98#include "video_core/gpu.h"
98#include "video_core/shader_notify.h" 99#include "video_core/shader_notify.h"
99#include "yuzu/about_dialog.h" 100#include "yuzu/about_dialog.h"
@@ -186,9 +187,9 @@ static void InitializeLogging() {
186} 187}
187 188
188GMainWindow::GMainWindow() 189GMainWindow::GMainWindow()
189 : config(new Config()), emu_thread(nullptr), 190 : input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
190 vfs(std::make_shared<FileSys::RealVfsFilesystem>()), 191 config{std::make_unique<Config>()}, vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
191 provider(std::make_unique<FileSys::ManualContentProvider>()) { 192 provider{std::make_unique<FileSys::ManualContentProvider>()} {
192 InitializeLogging(); 193 InitializeLogging();
193 194
194 LoadTranslation(); 195 LoadTranslation();
@@ -473,7 +474,7 @@ void GMainWindow::InitializeWidgets() {
473#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING 474#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING
474 ui.action_Report_Compatibility->setVisible(true); 475 ui.action_Report_Compatibility->setVisible(true);
475#endif 476#endif
476 render_window = new GRenderWindow(this, emu_thread.get()); 477 render_window = new GRenderWindow(this, emu_thread.get(), input_subsystem);
477 render_window->hide(); 478 render_window->hide();
478 479
479 game_list = new GameList(vfs, provider.get(), this); 480 game_list = new GameList(vfs, provider.get(), this);
@@ -2213,7 +2214,7 @@ void GMainWindow::OnConfigure() {
2213 const auto old_theme = UISettings::values.theme; 2214 const auto old_theme = UISettings::values.theme;
2214 const bool old_discord_presence = UISettings::values.enable_discord_presence; 2215 const bool old_discord_presence = UISettings::values.enable_discord_presence;
2215 2216
2216 ConfigureDialog configure_dialog(this, hotkey_registry); 2217 ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get());
2217 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, 2218 connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
2218 &GMainWindow::OnLanguageChanged); 2219 &GMainWindow::OnLanguageChanged);
2219 2220
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 01f9131e5..0ce66a1ca 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -40,12 +40,20 @@ namespace Core::Frontend {
40struct SoftwareKeyboardParameters; 40struct SoftwareKeyboardParameters;
41} // namespace Core::Frontend 41} // namespace Core::Frontend
42 42
43namespace DiscordRPC {
44class DiscordInterface;
45}
46
43namespace FileSys { 47namespace FileSys {
44class ContentProvider; 48class ContentProvider;
45class ManualContentProvider; 49class ManualContentProvider;
46class VfsFilesystem; 50class VfsFilesystem;
47} // namespace FileSys 51} // namespace FileSys
48 52
53namespace InputCommon {
54class InputSubsystem;
55}
56
49enum class EmulatedDirectoryTarget { 57enum class EmulatedDirectoryTarget {
50 NAND, 58 NAND,
51 SDMC, 59 SDMC,
@@ -62,10 +70,6 @@ enum class ReinitializeKeyBehavior {
62 Warning, 70 Warning,
63}; 71};
64 72
65namespace DiscordRPC {
66class DiscordInterface;
67}
68
69class GMainWindow : public QMainWindow { 73class GMainWindow : public QMainWindow {
70 Q_OBJECT 74 Q_OBJECT
71 75
@@ -86,8 +90,6 @@ public:
86 GMainWindow(); 90 GMainWindow();
87 ~GMainWindow() override; 91 ~GMainWindow() override;
88 92
89 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
90
91 bool DropAction(QDropEvent* event); 93 bool DropAction(QDropEvent* event);
92 void AcceptDropEvent(QDropEvent* event); 94 void AcceptDropEvent(QDropEvent* event);
93 95
@@ -255,6 +257,9 @@ private:
255 257
256 Ui::MainWindow ui; 258 Ui::MainWindow ui;
257 259
260 std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc;
261 std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
262
258 GRenderWindow* render_window; 263 GRenderWindow* render_window;
259 GameList* game_list; 264 GameList* game_list;
260 LoadingScreen* loading_screen; 265 LoadingScreen* loading_screen;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index e5e684206..a804d5185 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -13,23 +13,25 @@
13#include "input_common/sdl/sdl.h" 13#include "input_common/sdl/sdl.h"
14#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 14#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
15 15
16EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} { 16EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen,
17 InputCommon::InputSubsystem* input_subsystem_)
18 : system{system}, input_subsystem{input_subsystem_} {
17 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { 19 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
18 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); 20 LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
19 exit(1); 21 exit(1);
20 } 22 }
21 InputCommon::Init(); 23 input_subsystem->Initialize();
22 SDL_SetMainReady(); 24 SDL_SetMainReady();
23} 25}
24 26
25EmuWindow_SDL2::~EmuWindow_SDL2() { 27EmuWindow_SDL2::~EmuWindow_SDL2() {
26 InputCommon::Shutdown(); 28 input_subsystem->Shutdown();
27 SDL_Quit(); 29 SDL_Quit();
28} 30}
29 31
30void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { 32void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
31 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); 33 TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
32 InputCommon::GetMotionEmu()->Tilt(x, y); 34 input_subsystem->GetMotionEmu()->Tilt(x, y);
33} 35}
34 36
35void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { 37void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
@@ -41,9 +43,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
41 } 43 }
42 } else if (button == SDL_BUTTON_RIGHT) { 44 } else if (button == SDL_BUTTON_RIGHT) {
43 if (state == SDL_PRESSED) { 45 if (state == SDL_PRESSED) {
44 InputCommon::GetMotionEmu()->BeginTilt(x, y); 46 input_subsystem->GetMotionEmu()->BeginTilt(x, y);
45 } else { 47 } else {
46 InputCommon::GetMotionEmu()->EndTilt(); 48 input_subsystem->GetMotionEmu()->EndTilt();
47 } 49 }
48 } 50 }
49} 51}
@@ -79,9 +81,9 @@ void EmuWindow_SDL2::OnFingerUp() {
79 81
80void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { 82void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
81 if (state == SDL_PRESSED) { 83 if (state == SDL_PRESSED) {
82 InputCommon::GetKeyboard()->PressKey(key); 84 input_subsystem->GetKeyboard()->PressKey(key);
83 } else if (state == SDL_RELEASED) { 85 } else if (state == SDL_RELEASED) {
84 InputCommon::GetKeyboard()->ReleaseKey(key); 86 input_subsystem->GetKeyboard()->ReleaseKey(key);
85 } 87 }
86} 88}
87 89
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index fffac4252..82750ffec 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -14,9 +14,14 @@ namespace Core {
14class System; 14class System;
15} 15}
16 16
17namespace InputCommon {
18class InputSubsystem;
19}
20
17class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { 21class EmuWindow_SDL2 : public Core::Frontend::EmuWindow {
18public: 22public:
19 explicit EmuWindow_SDL2(Core::System& system, bool fullscreen); 23 explicit EmuWindow_SDL2(Core::System& system, bool fullscreen,
24 InputCommon::InputSubsystem* input_subsystem);
20 ~EmuWindow_SDL2(); 25 ~EmuWindow_SDL2();
21 26
22 /// Polls window events 27 /// Polls window events
@@ -76,4 +81,7 @@ protected:
76 81
77 /// Keeps track of how often to update the title bar during gameplay 82 /// Keeps track of how often to update the title bar during gameplay
78 u32 last_time = 0; 83 u32 last_time = 0;
84
85 /// Input subsystem to use with this window.
86 InputCommon::InputSubsystem* input_subsystem;
79}; 87};
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index e78025737..881b67a76 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -87,8 +87,9 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() {
87 return unsupported_ext.empty(); 87 return unsupported_ext.empty();
88} 88}
89 89
90EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen) 90EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
91 : EmuWindow_SDL2{system, fullscreen} { 91 InputCommon::InputSubsystem* input_subsystem)
92 : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
92 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); 93 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
93 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
94 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); 95 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
index 48bb41683..732a64edd 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h
@@ -8,9 +8,14 @@
8#include "core/frontend/emu_window.h" 8#include "core/frontend/emu_window.h"
9#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 9#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
10 10
11namespace InputCommon {
12class InputSubsystem;
13}
14
11class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { 15class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 {
12public: 16public:
13 explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen); 17 explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen,
18 InputCommon::InputSubsystem* input_subsystem);
14 ~EmuWindow_SDL2_GL(); 19 ~EmuWindow_SDL2_GL();
15 20
16 void Present() override; 21 void Present() override;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
index cb8e68a39..53491f86e 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp
@@ -19,8 +19,9 @@
19#include <SDL.h> 19#include <SDL.h>
20#include <SDL_syswm.h> 20#include <SDL_syswm.h>
21 21
22EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen) 22EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
23 : EmuWindow_SDL2{system, fullscreen} { 23 InputCommon::InputSubsystem* input_subsystem)
24 : EmuWindow_SDL2{system, fullscreen, input_subsystem} {
24 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name, 25 const std::string window_title = fmt::format("yuzu {} | {}-{} (Vulkan)", Common::g_build_name,
25 Common::g_scm_branch, Common::g_scm_desc); 26 Common::g_scm_branch, Common::g_scm_desc);
26 render_window = 27 render_window =
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
index 77a6ca72b..f99704d4c 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h
@@ -13,9 +13,14 @@ namespace Core {
13class System; 13class System;
14} 14}
15 15
16namespace InputCommon {
17class InputSubsystem;
18}
19
16class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { 20class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 {
17public: 21public:
18 explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen); 22 explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen,
23 InputCommon::InputSubsystem* input_subsystem);
19 ~EmuWindow_SDL2_VK(); 24 ~EmuWindow_SDL2_VK();
20 25
21 void Present() override; 26 void Present() override;
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 8efe49390..4f00c804d 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -23,12 +23,14 @@
23#include "common/telemetry.h" 23#include "common/telemetry.h"
24#include "core/core.h" 24#include "core/core.h"
25#include "core/crypto/key_manager.h" 25#include "core/crypto/key_manager.h"
26#include "core/file_sys/registered_cache.h"
26#include "core/file_sys/vfs_real.h" 27#include "core/file_sys/vfs_real.h"
27#include "core/gdbstub/gdbstub.h" 28#include "core/gdbstub/gdbstub.h"
28#include "core/hle/service/filesystem/filesystem.h" 29#include "core/hle/service/filesystem/filesystem.h"
29#include "core/loader/loader.h" 30#include "core/loader/loader.h"
30#include "core/settings.h" 31#include "core/settings.h"
31#include "core/telemetry_session.h" 32#include "core/telemetry_session.h"
33#include "input_common/main.h"
32#include "video_core/renderer_base.h" 34#include "video_core/renderer_base.h"
33#include "yuzu_cmd/config.h" 35#include "yuzu_cmd/config.h"
34#include "yuzu_cmd/emu_window/emu_window_sdl2.h" 36#include "yuzu_cmd/emu_window/emu_window_sdl2.h"
@@ -37,8 +39,6 @@
37#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" 39#include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h"
38#endif 40#endif
39 41
40#include "core/file_sys/registered_cache.h"
41
42#ifdef _WIN32 42#ifdef _WIN32
43// windows.h needs to be included before shellapi.h 43// windows.h needs to be included before shellapi.h
44#include <windows.h> 44#include <windows.h>
@@ -179,15 +179,16 @@ int main(int argc, char** argv) {
179 Settings::Apply(); 179 Settings::Apply();
180 180
181 Core::System& system{Core::System::GetInstance()}; 181 Core::System& system{Core::System::GetInstance()};
182 InputCommon::InputSubsystem input_subsystem;
182 183
183 std::unique_ptr<EmuWindow_SDL2> emu_window; 184 std::unique_ptr<EmuWindow_SDL2> emu_window;
184 switch (Settings::values.renderer_backend.GetValue()) { 185 switch (Settings::values.renderer_backend.GetValue()) {
185 case Settings::RendererBackend::OpenGL: 186 case Settings::RendererBackend::OpenGL:
186 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen); 187 emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen, &input_subsystem);
187 break; 188 break;
188 case Settings::RendererBackend::Vulkan: 189 case Settings::RendererBackend::Vulkan:
189#ifdef HAS_VULKAN 190#ifdef HAS_VULKAN
190 emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen); 191 emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen, &input_subsystem);
191 break; 192 break;
192#else 193#else
193 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); 194 LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!");
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
index 8584f6671..78f75fb38 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp
@@ -13,7 +13,6 @@
13 13
14#include <glad/glad.h> 14#include <glad/glad.h>
15 15
16#include "common/assert.h"
17#include "common/logging/log.h" 16#include "common/logging/log.h"
18#include "common/scm_rev.h" 17#include "common/scm_rev.h"
19#include "core/settings.h" 18#include "core/settings.h"
@@ -53,7 +52,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
53 exit(1); 52 exit(1);
54 } 53 }
55 54
56 InputCommon::Init(); 55 input_subsystem->Initialize();
57 56
58 SDL_SetMainReady(); 57 SDL_SetMainReady();
59 58
@@ -105,7 +104,7 @@ EmuWindow_SDL2_Hide::EmuWindow_SDL2_Hide() {
105} 104}
106 105
107EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { 106EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() {
108 InputCommon::Shutdown(); 107 input_subsystem->Shutdown();
109 SDL_GL_DeleteContext(gl_context); 108 SDL_GL_DeleteContext(gl_context);
110 SDL_Quit(); 109 SDL_Quit();
111} 110}
diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
index c13a82df2..a553b4b95 100644
--- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
+++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h
@@ -8,6 +8,10 @@
8 8
9struct SDL_Window; 9struct SDL_Window;
10 10
11namespace InputCommon {
12class InputSubsystem;
13}
14
11class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow { 15class EmuWindow_SDL2_Hide : public Core::Frontend::EmuWindow {
12public: 16public:
13 explicit EmuWindow_SDL2_Hide(); 17 explicit EmuWindow_SDL2_Hide();
@@ -25,6 +29,8 @@ private:
25 /// Whether the GPU and driver supports the OpenGL extension required 29 /// Whether the GPU and driver supports the OpenGL extension required
26 bool SupportsRequiredGLExtensions(); 30 bool SupportsRequiredGLExtensions();
27 31
32 std::unique_ptr<InputCommon::InputSubsystem> input_subsystem;
33
28 /// Internal SDL2 render window 34 /// Internal SDL2 render window
29 SDL_Window* render_window; 35 SDL_Window* render_window;
30 36