summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/common_funcs.h14
-rw-r--r--src/common/math_util.h2
-rw-r--r--src/common/quaternion.h30
-rw-r--r--src/common/thread.cpp12
-rw-r--r--src/common/thread.h9
-rw-r--r--src/core/cpu_manager.cpp2
-rw-r--r--src/core/crypto/partition_data_manager.cpp1
-rw-r--r--src/core/file_sys/bis_factory.cpp1
-rw-r--r--src/core/file_sys/bis_factory.h3
-rw-r--r--src/core/file_sys/card_image.cpp5
-rw-r--r--src/core/file_sys/card_image.h7
-rw-r--r--src/core/file_sys/content_archive.cpp5
-rw-r--r--src/core/file_sys/content_archive.h2
-rw-r--r--src/core/file_sys/control_metadata.cpp1
-rw-r--r--src/core/file_sys/control_metadata.h2
-rw-r--r--src/core/file_sys/kernel_executable.cpp3
-rw-r--r--src/core/file_sys/kernel_executable.h9
-rw-r--r--src/core/file_sys/nca_metadata.cpp1
-rw-r--r--src/core/file_sys/nca_metadata.h2
-rw-r--r--src/core/file_sys/partition_filesystem.cpp8
-rw-r--r--src/core/file_sys/partition_filesystem.h8
-rw-r--r--src/core/file_sys/patch_manager.cpp3
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/file_sys/program_metadata.cpp1
-rw-r--r--src/core/file_sys/program_metadata.h2
-rw-r--r--src/core/file_sys/romfs.h1
-rw-r--r--src/core/file_sys/sdmc_factory.cpp2
-rw-r--r--src/core/file_sys/sdmc_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp2
-rw-r--r--src/core/file_sys/submission_package.h6
-rw-r--r--src/core/file_sys/xts_archive.cpp10
-rw-r--r--src/core/file_sys/xts_archive.h10
-rw-r--r--src/core/frontend/framebuffer_layout.h1
-rw-r--r--src/core/hle/kernel/kernel.cpp3
-rw-r--r--src/core/hle/kernel/scheduler.cpp8
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp16
-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/hle/service/ns/ns.cpp1
-rw-r--r--src/core/settings.h12
-rw-r--r--src/input_common/CMakeLists.txt4
-rw-r--r--src/input_common/gcadapter/gc_poller.cpp8
-rw-r--r--src/input_common/main.cpp11
-rw-r--r--src/input_common/main.h3
-rw-r--r--src/input_common/motion_input.cpp181
-rw-r--r--src/input_common/motion_input.h68
-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.cpp15
-rw-r--r--src/video_core/shader/async_shaders.cpp6
-rw-r--r--src/video_core/shader/async_shaders.h26
-rw-r--r--src/yuzu/CMakeLists.txt7
-rw-r--r--src/yuzu/configuration/config.cpp101
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_input.cpp5
-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_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_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.ui2
65 files changed, 2387 insertions, 93 deletions
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index 98421bced..367b6bf6e 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -64,14 +64,20 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
64 using T = std::underlying_type_t<type>; \ 64 using T = std::underlying_type_t<type>; \
65 return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \ 65 return static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \
66 } \ 66 } \
67 constexpr type& operator|=(type& a, type b) noexcept { \ 67 [[nodiscard]] constexpr type operator^(type a, type b) noexcept { \
68 using T = std::underlying_type_t<type>; \ 68 using T = std::underlying_type_t<type>; \
69 a = static_cast<type>(static_cast<T>(a) | static_cast<T>(b)); \ 69 return static_cast<type>(static_cast<T>(a) ^ static_cast<T>(b)); \
70 } \
71 constexpr type& operator|=(type& a, type b) noexcept { \
72 a = a | b; \
70 return a; \ 73 return a; \
71 } \ 74 } \
72 constexpr type& operator&=(type& a, type b) noexcept { \ 75 constexpr type& operator&=(type& a, type b) noexcept { \
73 using T = std::underlying_type_t<type>; \ 76 a = a & b; \
74 a = static_cast<type>(static_cast<T>(a) & static_cast<T>(b)); \ 77 return a; \
78 } \
79 constexpr type& operator^=(type& a, type b) noexcept { \
80 a = a ^ b; \
75 return a; \ 81 return a; \
76 } \ 82 } \
77 [[nodiscard]] constexpr type operator~(type key) noexcept { \ 83 [[nodiscard]] constexpr type operator~(type key) noexcept { \
diff --git a/src/common/math_util.h b/src/common/math_util.h
index cc35c90ee..b35ad8507 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -9,7 +9,7 @@
9 9
10namespace Common { 10namespace Common {
11 11
12constexpr float PI = 3.14159265f; 12constexpr float PI = 3.1415926535f;
13 13
14template <class T> 14template <class T>
15struct Rectangle { 15struct Rectangle {
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
index da44f35cd..4d0871eb4 100644
--- a/src/common/quaternion.h
+++ b/src/common/quaternion.h
@@ -36,6 +36,36 @@ public:
36 T length = std::sqrt(xyz.Length2() + w * w); 36 T length = std::sqrt(xyz.Length2() + w * w);
37 return {xyz / length, w / length}; 37 return {xyz / length, w / length};
38 } 38 }
39
40 [[nodiscard]] std::array<decltype(-T{}), 16> ToMatrix() const {
41 const T x2 = xyz[0] * xyz[0];
42 const T y2 = xyz[1] * xyz[1];
43 const T z2 = xyz[2] * xyz[2];
44
45 const T xy = xyz[0] * xyz[1];
46 const T wz = w * xyz[2];
47 const T xz = xyz[0] * xyz[2];
48 const T wy = w * xyz[1];
49 const T yz = xyz[1] * xyz[2];
50 const T wx = w * xyz[0];
51
52 return {1.0f - 2.0f * (y2 + z2),
53 2.0f * (xy + wz),
54 2.0f * (xz - wy),
55 0.0f,
56 2.0f * (xy - wz),
57 1.0f - 2.0f * (x2 + z2),
58 2.0f * (yz + wx),
59 0.0f,
60 2.0f * (xz + wy),
61 2.0f * (yz - wx),
62 1.0f - 2.0f * (x2 + y2),
63 0.0f,
64 0.0f,
65 0.0f,
66 0.0f,
67 1.0f};
68 }
39}; 69};
40 70
41template <typename T> 71template <typename T>
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/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/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
index 46136d04a..5f1c86a09 100644
--- a/src/core/crypto/partition_data_manager.cpp
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -26,6 +26,7 @@
26#include "core/file_sys/vfs.h" 26#include "core/file_sys/vfs.h"
27#include "core/file_sys/vfs_offset.h" 27#include "core/file_sys/vfs_offset.h"
28#include "core/file_sys/vfs_vector.h" 28#include "core/file_sys/vfs_vector.h"
29#include "core/loader/loader.h"
29 30
30using Common::AsArray; 31using Common::AsArray;
31 32
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 9ffda2e14..e04a54c3c 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -8,7 +8,6 @@
8#include "core/file_sys/bis_factory.h" 8#include "core/file_sys/bis_factory.h"
9#include "core/file_sys/mode.h" 9#include "core/file_sys/mode.h"
10#include "core/file_sys/registered_cache.h" 10#include "core/file_sys/registered_cache.h"
11#include "core/settings.h"
12 11
13namespace FileSys { 12namespace FileSys {
14 13
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 8f0451c98..438d3f8d8 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -6,7 +6,8 @@
6 6
7#include <memory> 7#include <memory>
8 8
9#include "core/file_sys/vfs.h" 9#include "common/common_types.h"
10#include "core/file_sys/vfs_types.h"
10 11
11namespace FileSys { 12namespace FileSys {
12 13
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 664a47e7f..956da68f7 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -8,11 +8,11 @@
8#include <fmt/ostream.h> 8#include <fmt/ostream.h>
9 9
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/crypto/key_manager.h"
11#include "core/file_sys/card_image.h" 12#include "core/file_sys/card_image.h"
12#include "core/file_sys/content_archive.h" 13#include "core/file_sys/content_archive.h"
13#include "core/file_sys/nca_metadata.h" 14#include "core/file_sys/nca_metadata.h"
14#include "core/file_sys/partition_filesystem.h" 15#include "core/file_sys/partition_filesystem.h"
15#include "core/file_sys/romfs.h"
16#include "core/file_sys/submission_package.h" 16#include "core/file_sys/submission_package.h"
17#include "core/file_sys/vfs_concat.h" 17#include "core/file_sys/vfs_concat.h"
18#include "core/file_sys/vfs_offset.h" 18#include "core/file_sys/vfs_offset.h"
@@ -31,7 +31,8 @@ constexpr std::array partition_names{
31 31
32XCI::XCI(VirtualFile file_) 32XCI::XCI(VirtualFile file_)
33 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, 33 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
34 partitions(partition_names.size()), partitions_raw(partition_names.size()) { 34 partitions(partition_names.size()),
35 partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
35 if (file->ReadObject(&header) != sizeof(GamecardHeader)) { 36 if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
36 status = Loader::ResultStatus::ErrorBadXCIHeader; 37 status = Loader::ResultStatus::ErrorBadXCIHeader;
37 return; 38 return;
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index e1b136426..2d0a0f285 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -9,9 +9,12 @@
9#include <vector> 9#include <vector>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/swap.h" 11#include "common/swap.h"
12#include "core/crypto/key_manager.h"
13#include "core/file_sys/vfs.h" 12#include "core/file_sys/vfs.h"
14 13
14namespace Core::Crypto {
15class KeyManager;
16}
17
15namespace Loader { 18namespace Loader {
16enum class ResultStatus : u16; 19enum class ResultStatus : u16;
17} 20}
@@ -140,6 +143,6 @@ private:
140 143
141 u64 update_normal_partition_end; 144 u64 update_normal_partition_end;
142 145
143 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 146 Core::Crypto::KeyManager& keys;
144}; 147};
145} // namespace FileSys 148} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 5039341c7..426fb6bb5 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -10,10 +10,10 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/crypto/aes_util.h" 11#include "core/crypto/aes_util.h"
12#include "core/crypto/ctr_encryption_layer.h" 12#include "core/crypto/ctr_encryption_layer.h"
13#include "core/crypto/key_manager.h"
13#include "core/file_sys/content_archive.h" 14#include "core/file_sys/content_archive.h"
14#include "core/file_sys/nca_patch.h" 15#include "core/file_sys/nca_patch.h"
15#include "core/file_sys/partition_filesystem.h" 16#include "core/file_sys/partition_filesystem.h"
16#include "core/file_sys/romfs.h"
17#include "core/file_sys/vfs_offset.h" 17#include "core/file_sys/vfs_offset.h"
18#include "core/loader/loader.h" 18#include "core/loader/loader.h"
19 19
@@ -119,7 +119,8 @@ static bool IsValidNCA(const NCAHeader& header) {
119} 119}
120 120
121NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) 121NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
122 : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) { 122 : file(std::move(file_)),
123 bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} {
123 if (file == nullptr) { 124 if (file == nullptr) {
124 status = Loader::ResultStatus::ErrorNullFile; 125 status = Loader::ResultStatus::ErrorNullFile;
125 return; 126 return;
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index d25cbcf91..69292232a 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -158,7 +158,7 @@ private:
158 bool encrypted = false; 158 bool encrypted = false;
159 bool is_update = false; 159 bool is_update = false;
160 160
161 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 161 Core::Crypto::KeyManager& keys;
162}; 162};
163 163
164} // namespace FileSys 164} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 63cd2eead..b0a130345 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -5,6 +5,7 @@
5#include "common/string_util.h" 5#include "common/string_util.h"
6#include "common/swap.h" 6#include "common/swap.h"
7#include "core/file_sys/control_metadata.h" 7#include "core/file_sys/control_metadata.h"
8#include "core/file_sys/vfs.h"
8 9
9namespace FileSys { 10namespace FileSys {
10 11
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index e37b2fadf..9ab86e35b 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -10,7 +10,7 @@
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/swap.h" 12#include "common/swap.h"
13#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs_types.h"
14 14
15namespace FileSys { 15namespace FileSys {
16 16
diff --git a/src/core/file_sys/kernel_executable.cpp b/src/core/file_sys/kernel_executable.cpp
index 76313679d..ef93ef3ed 100644
--- a/src/core/file_sys/kernel_executable.cpp
+++ b/src/core/file_sys/kernel_executable.cpp
@@ -2,9 +2,12 @@
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 <cstring>
6
5#include "common/string_util.h" 7#include "common/string_util.h"
6#include "core/file_sys/kernel_executable.h" 8#include "core/file_sys/kernel_executable.h"
7#include "core/file_sys/vfs_offset.h" 9#include "core/file_sys/vfs_offset.h"
10#include "core/loader/loader.h"
8 11
9namespace FileSys { 12namespace FileSys {
10 13
diff --git a/src/core/file_sys/kernel_executable.h b/src/core/file_sys/kernel_executable.h
index 324a57384..044c554d3 100644
--- a/src/core/file_sys/kernel_executable.h
+++ b/src/core/file_sys/kernel_executable.h
@@ -4,10 +4,17 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
8#include <vector>
9
7#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h"
8#include "common/swap.h" 12#include "common/swap.h"
9#include "core/file_sys/vfs_types.h" 13#include "core/file_sys/vfs_types.h"
10#include "core/loader/loader.h" 14
15namespace Loader {
16enum class ResultStatus : u16;
17}
11 18
12namespace FileSys { 19namespace FileSys {
13 20
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp
index 93d0df6b9..2d1476e3a 100644
--- a/src/core/file_sys/nca_metadata.cpp
+++ b/src/core/file_sys/nca_metadata.cpp
@@ -7,6 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/swap.h" 8#include "common/swap.h"
9#include "core/file_sys/nca_metadata.h" 9#include "core/file_sys/nca_metadata.h"
10#include "core/file_sys/vfs.h"
10 11
11namespace FileSys { 12namespace FileSys {
12 13
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h
index 1f82fff0a..53535e5f5 100644
--- a/src/core/file_sys/nca_metadata.h
+++ b/src/core/file_sys/nca_metadata.h
@@ -10,7 +10,7 @@
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/swap.h" 12#include "common/swap.h"
13#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs_types.h"
14 14
15namespace FileSys { 15namespace FileSys {
16class CNMT; 16class CNMT;
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 846986736..48a2ed4d4 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -21,7 +21,7 @@ bool PartitionFilesystem::Header::HasValidMagicValue() const {
21 magic == Common::MakeMagic('P', 'F', 'S', '0'); 21 magic == Common::MakeMagic('P', 'F', 'S', '0');
22} 22}
23 23
24PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { 24PartitionFilesystem::PartitionFilesystem(VirtualFile file) {
25 // At least be as large as the header 25 // At least be as large as the header
26 if (file->GetSize() < sizeof(Header)) { 26 if (file->GetSize() < sizeof(Header)) {
27 status = Loader::ResultStatus::ErrorBadPFSHeader; 27 status = Loader::ResultStatus::ErrorBadPFSHeader;
@@ -89,11 +89,11 @@ std::map<std::string, u64> PartitionFilesystem::GetFileSizes() const {
89 return sizes; 89 return sizes;
90} 90}
91 91
92std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const { 92std::vector<VirtualFile> PartitionFilesystem::GetFiles() const {
93 return pfs_files; 93 return pfs_files;
94} 94}
95 95
96std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const { 96std::vector<VirtualDir> PartitionFilesystem::GetSubdirectories() const {
97 return {}; 97 return {};
98} 98}
99 99
@@ -101,7 +101,7 @@ std::string PartitionFilesystem::GetName() const {
101 return is_hfs ? "HFS0" : "PFS0"; 101 return is_hfs ? "HFS0" : "PFS0";
102} 102}
103 103
104std::shared_ptr<VfsDirectory> PartitionFilesystem::GetParentDirectory() const { 104VirtualDir PartitionFilesystem::GetParentDirectory() const {
105 // TODO(DarkLordZach): Add support for nested containers. 105 // TODO(DarkLordZach): Add support for nested containers.
106 return nullptr; 106 return nullptr;
107} 107}
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 279193b19..0f831148e 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -24,7 +24,7 @@ namespace FileSys {
24 */ 24 */
25class PartitionFilesystem : public ReadOnlyVfsDirectory { 25class PartitionFilesystem : public ReadOnlyVfsDirectory {
26public: 26public:
27 explicit PartitionFilesystem(std::shared_ptr<VfsFile> file); 27 explicit PartitionFilesystem(VirtualFile file);
28 ~PartitionFilesystem() override; 28 ~PartitionFilesystem() override;
29 29
30 Loader::ResultStatus GetStatus() const; 30 Loader::ResultStatus GetStatus() const;
@@ -32,10 +32,10 @@ public:
32 std::map<std::string, u64> GetFileOffsets() const; 32 std::map<std::string, u64> GetFileOffsets() const;
33 std::map<std::string, u64> GetFileSizes() const; 33 std::map<std::string, u64> GetFileSizes() const;
34 34
35 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; 35 std::vector<VirtualFile> GetFiles() const override;
36 std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; 36 std::vector<VirtualDir> GetSubdirectories() const override;
37 std::string GetName() const override; 37 std::string GetName() const override;
38 std::shared_ptr<VfsDirectory> GetParentDirectory() const override; 38 VirtualDir GetParentDirectory() const override;
39 void PrintDebugInfo() const; 39 void PrintDebugInfo() const;
40 40
41private: 41private:
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 729dbb5f4..c228d253e 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -49,8 +49,7 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
49 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]); 49 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
50} 50}
51 51
52std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir, 52VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name) {
53 std::string_view name) {
54#ifdef _WIN32 53#ifdef _WIN32
55 return dir->GetSubdirectory(name); 54 return dir->GetSubdirectory(name);
56#else 55#else
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index f4cb918dd..532f4995f 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -6,10 +6,11 @@
6 6
7#include <map> 7#include <map>
8#include <memory> 8#include <memory>
9#include <optional>
9#include <string> 10#include <string>
10#include "common/common_types.h" 11#include "common/common_types.h"
11#include "core/file_sys/nca_metadata.h" 12#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs_types.h"
13#include "core/memory/dmnt_cheat_types.h" 14#include "core/memory/dmnt_cheat_types.h"
14 15
15namespace Core { 16namespace Core {
@@ -31,8 +32,7 @@ std::string FormatTitleVersion(u32 version,
31 32
32// Returns a directory with name matching name case-insensitive. Returns nullptr if directory 33// Returns a directory with name matching name case-insensitive. Returns nullptr if directory
33// doesn't have a directory with name. 34// doesn't have a directory with name.
34std::shared_ptr<VfsDirectory> FindSubdirectoryCaseless(const std::shared_ptr<VfsDirectory> dir, 35VirtualDir FindSubdirectoryCaseless(VirtualDir dir, std::string_view name);
35 std::string_view name);
36 36
37// A centralized class to manage patches to games. 37// A centralized class to manage patches to games.
38class PatchManager { 38class PatchManager {
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp
index 43169bf9f..9cf49bf44 100644
--- a/src/core/file_sys/program_metadata.cpp
+++ b/src/core/file_sys/program_metadata.cpp
@@ -7,6 +7,7 @@
7 7
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/file_sys/program_metadata.h" 9#include "core/file_sys/program_metadata.h"
10#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h" 11#include "core/loader/loader.h"
11 12
12namespace FileSys { 13namespace FileSys {
diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h
index 35069972b..455532567 100644
--- a/src/core/file_sys/program_metadata.h
+++ b/src/core/file_sys/program_metadata.h
@@ -9,7 +9,7 @@
9#include "common/bit_field.h" 9#include "common/bit_field.h"
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "common/swap.h" 11#include "common/swap.h"
12#include "core/file_sys/vfs.h" 12#include "core/file_sys/vfs_types.h"
13 13
14namespace Loader { 14namespace Loader {
15enum class ResultStatus : u16; 15enum class ResultStatus : u16;
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index 2fd07ed04..82e683782 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -4,7 +4,6 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <array>
8#include "core/file_sys/vfs.h" 7#include "core/file_sys/vfs.h"
9 8
10namespace FileSys { 9namespace FileSys {
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index 6f732e4d8..cb56d8f2d 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -5,8 +5,8 @@
5#include <memory> 5#include <memory>
6#include "core/file_sys/registered_cache.h" 6#include "core/file_sys/registered_cache.h"
7#include "core/file_sys/sdmc_factory.h" 7#include "core/file_sys/sdmc_factory.h"
8#include "core/file_sys/vfs.h"
8#include "core/file_sys/xts_archive.h" 9#include "core/file_sys/xts_archive.h"
9#include "core/settings.h"
10 10
11namespace FileSys { 11namespace FileSys {
12 12
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index 42dc4e08a..2bb92ba93 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -5,7 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <memory> 7#include <memory>
8#include "core/file_sys/vfs.h" 8#include "core/file_sys/vfs_types.h"
9#include "core/hle/result.h" 9#include "core/hle/result.h"
10 10
11namespace FileSys { 11namespace FileSys {
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 175a8266a..b9ce93b7c 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -54,7 +54,7 @@ void SetTicketKeys(const std::vector<VirtualFile>& files) {
54 54
55NSP::NSP(VirtualFile file_) 55NSP::NSP(VirtualFile file_)
56 : file(std::move(file_)), status{Loader::ResultStatus::Success}, 56 : file(std::move(file_)), status{Loader::ResultStatus::Success},
57 pfs(std::make_shared<PartitionFilesystem>(file)) { 57 pfs(std::make_shared<PartitionFilesystem>(file)), keys{Core::Crypto::KeyManager::Instance()} {
58 if (pfs->GetStatus() != Loader::ResultStatus::Success) { 58 if (pfs->GetStatus() != Loader::ResultStatus::Success) {
59 status = pfs->GetStatus(); 59 status = pfs->GetStatus();
60 return; 60 return;
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index cf89de6a9..2db5e46b8 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -10,6 +10,10 @@
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/vfs.h" 11#include "core/file_sys/vfs.h"
12 12
13namespace Core::Crypto {
14class KeyManager;
15}
16
13namespace Loader { 17namespace Loader {
14enum class ResultStatus : u16; 18enum class ResultStatus : u16;
15} 19}
@@ -73,7 +77,7 @@ private:
73 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas; 77 std::map<u64, std::map<std::pair<TitleType, ContentRecordType>, std::shared_ptr<NCA>>> ncas;
74 std::vector<VirtualFile> ticket_files; 78 std::vector<VirtualFile> ticket_files;
75 79
76 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 80 Core::Crypto::KeyManager& keys;
77 81
78 VirtualFile romfs; 82 VirtualFile romfs;
79 VirtualDir exefs; 83 VirtualDir exefs;
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index ccf5966d0..24c58e7ae 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -15,8 +15,9 @@
15#include "common/hex_util.h" 15#include "common/hex_util.h"
16#include "common/string_util.h" 16#include "common/string_util.h"
17#include "core/crypto/aes_util.h" 17#include "core/crypto/aes_util.h"
18#include "core/crypto/key_manager.h"
18#include "core/crypto/xts_encryption_layer.h" 19#include "core/crypto/xts_encryption_layer.h"
19#include "core/file_sys/partition_filesystem.h" 20#include "core/file_sys/content_archive.h"
20#include "core/file_sys/vfs_offset.h" 21#include "core/file_sys/vfs_offset.h"
21#include "core/file_sys/xts_archive.h" 22#include "core/file_sys/xts_archive.h"
22#include "core/loader/loader.h" 23#include "core/loader/loader.h"
@@ -43,7 +44,9 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t
43 return true; 44 return true;
44} 45}
45 46
46NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { 47NAX::NAX(VirtualFile file_)
48 : header(std::make_unique<NAXHeader>()),
49 file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
47 std::string path = Common::FS::SanitizePath(file->GetFullPath()); 50 std::string path = Common::FS::SanitizePath(file->GetFullPath());
48 static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", 51 static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca",
49 std::regex_constants::ECMAScript | 52 std::regex_constants::ECMAScript |
@@ -60,7 +63,8 @@ NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::m
60} 63}
61 64
62NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) 65NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id)
63 : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { 66 : header(std::make_unique<NAXHeader>()),
67 file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} {
64 Core::Crypto::SHA256Hash hash{}; 68 Core::Crypto::SHA256Hash hash{};
65 mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0); 69 mbedtls_sha256_ret(nca_id.data(), nca_id.size(), hash.data(), 0);
66 status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], 70 status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0],
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 563531bb6..c472e226e 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -9,12 +9,16 @@
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "common/swap.h" 10#include "common/swap.h"
11#include "core/crypto/key_manager.h" 11#include "core/crypto/key_manager.h"
12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/vfs.h" 12#include "core/file_sys/vfs.h"
14#include "core/loader/loader.h" 13
14namespace Loader {
15enum class ResultStatus : u16;
16}
15 17
16namespace FileSys { 18namespace FileSys {
17 19
20class NCA;
21
18struct NAXHeader { 22struct NAXHeader {
19 std::array<u8, 0x20> hmac; 23 std::array<u8, 0x20> hmac;
20 u64_le magic; 24 u64_le magic;
@@ -62,6 +66,6 @@ private:
62 66
63 VirtualFile dec_file; 67 VirtualFile dec_file;
64 68
65 Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); 69 Core::Crypto::KeyManager& keys;
66}; 70};
67} // namespace FileSys 71} // namespace FileSys
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/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index a4b234424..5cbd3b912 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -756,7 +756,11 @@ void Scheduler::SwitchToCurrent() {
756 current_thread = selected_thread; 756 current_thread = selected_thread;
757 is_context_switch_pending = false; 757 is_context_switch_pending = false;
758 } 758 }
759 while (!is_context_switch_pending) { 759 const auto is_switch_pending = [this] {
760 std::scoped_lock lock{guard};
761 return is_context_switch_pending;
762 };
763 do {
760 if (current_thread != nullptr && !current_thread->IsHLEThread()) { 764 if (current_thread != nullptr && !current_thread->IsHLEThread()) {
761 current_thread->context_guard.lock(); 765 current_thread->context_guard.lock();
762 if (!current_thread->IsRunnable()) { 766 if (!current_thread->IsRunnable()) {
@@ -775,7 +779,7 @@ void Scheduler::SwitchToCurrent() {
775 next_context = &idle_thread->GetHostContext(); 779 next_context = &idle_thread->GetHostContext();
776 } 780 }
777 Common::Fiber::YieldTo(switch_fiber, *next_context); 781 Common::Fiber::YieldTo(switch_fiber, *next_context);
778 } 782 } while (!is_switch_pending());
779 } 783 }
780} 784}
781 785
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 26fd87f58..649128be4 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -844,8 +844,7 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
844 return; 844 return;
845 } 845 }
846 846
847 FileSys::StorageId id; 847 FileSys::StorageId id{};
848
849 switch (parameters.space_id) { 848 switch (parameters.space_id) {
850 case FileSys::SaveDataSpaceId::NandUser: 849 case FileSys::SaveDataSpaceId::NandUser:
851 id = FileSys::StorageId::NandUser; 850 id = FileSys::StorageId::NandUser;
@@ -857,6 +856,10 @@ void FSP_SRV::OpenSaveDataFileSystem(Kernel::HLERequestContext& ctx) {
857 case FileSys::SaveDataSpaceId::NandSystem: 856 case FileSys::SaveDataSpaceId::NandSystem:
858 id = FileSys::StorageId::NandSystem; 857 id = FileSys::StorageId::NandSystem;
859 break; 858 break;
859 case FileSys::SaveDataSpaceId::TemporaryStorage:
860 case FileSys::SaveDataSpaceId::ProperSystem:
861 case FileSys::SaveDataSpaceId::SafeMode:
862 UNREACHABLE();
860 } 863 }
861 864
862 auto filesystem = 865 auto filesystem =
@@ -902,7 +905,14 @@ void FSP_SRV::ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(
902 // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData 905 // Stub this to None for now, backend needs an impl to read/write the SaveDataExtraData
903 constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None); 906 constexpr auto flags = static_cast<u32>(FileSys::SaveDataFlags::None);
904 907
905 LOG_WARNING(Service_FS, "(STUBBED) called, flags={}", flags); 908 LOG_WARNING(Service_FS,
909 "(STUBBED) called, flags={}, space_id={}, attribute.title_id={:016X}\n"
910 "attribute.user_id={:016X}{:016X}, attribute.save_id={:016X}\n"
911 "attribute.type={}, attribute.rank={}, attribute.index={}",
912 flags, static_cast<u32>(parameters.space_id), parameters.attribute.title_id,
913 parameters.attribute.user_id[1], parameters.attribute.user_id[0],
914 parameters.attribute.save_id, static_cast<u32>(parameters.attribute.type),
915 static_cast<u32>(parameters.attribute.rank), parameters.attribute.index);
906 916
907 IPC::ResponseBuilder rb{ctx, 3}; 917 IPC::ResponseBuilder rb{ctx, 3};
908 rb.Push(RESULT_SUCCESS); 918 rb.Push(RESULT_SUCCESS);
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/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 886450be2..58ee1f712 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -5,6 +5,7 @@
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/file_sys/control_metadata.h" 6#include "core/file_sys/control_metadata.h"
7#include "core/file_sys/patch_manager.h" 7#include "core/file_sys/patch_manager.h"
8#include "core/file_sys/vfs.h"
8#include "core/hle/ipc_helpers.h" 9#include "core/hle/ipc_helpers.h"
9#include "core/hle/kernel/hle_ipc.h" 10#include "core/hle/kernel/hle_ipc.h"
10#include "core/hle/service/ns/errors.h" 11#include "core/hle/service/ns/errors.h"
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 56267c8a8..09361e37e 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,8 +7,12 @@ add_library(input_common STATIC
7 main.h 7 main.h
8 motion_emu.cpp 8 motion_emu.cpp
9 motion_emu.h 9 motion_emu.h
10 motion_input.cpp
11 motion_input.h
10 settings.cpp 12 settings.cpp
11 settings.h 13 settings.h
14 touch_from_button.cpp
15 touch_from_button.h
12 gcadapter/gc_adapter.cpp 16 gcadapter/gc_adapter.cpp
13 gcadapter/gc_adapter.h 17 gcadapter/gc_adapter.h
14 gcadapter/gc_poller.cpp 18 gcadapter/gc_poller.cpp
diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp
index 71cd85eeb..1c8d8523a 100644
--- a/src/input_common/gcadapter/gc_poller.cpp
+++ b/src/input_common/gcadapter/gc_poller.cpp
@@ -38,7 +38,8 @@ public:
38 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, 38 explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_,
39 GCAdapter::Adapter* adapter) 39 GCAdapter::Adapter* adapter)
40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), 40 : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_),
41 gcadapter(adapter), origin_value(adapter->GetOriginValue(port_, axis_)) {} 41 gcadapter(adapter),
42 origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {}
42 43
43 bool GetStatus() const override { 44 bool GetStatus() const override {
44 if (gcadapter->DeviceConnected(port)) { 45 if (gcadapter->DeviceConnected(port)) {
@@ -151,8 +152,9 @@ public:
151 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter, 152 GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, GCAdapter::Adapter* adapter,
152 float range_) 153 float range_)
153 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), 154 : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter),
154 origin_value_x(adapter->GetOriginValue(port_, axis_x_)), 155 origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))),
155 origin_value_y(adapter->GetOriginValue(port_, axis_y_)), range(range_) {} 156 origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))),
157 range(range_) {}
156 158
157 float GetAxis(int axis) const { 159 float GetAxis(int axis) const {
158 if (gcadapter->DeviceConnected(port)) { 160 if (gcadapter->DeviceConnected(port)) {
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 57e7a25fe..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"
@@ -32,6 +33,8 @@ struct InputSubsystem::Impl {
32 std::make_shared<AnalogFromButton>()); 33 std::make_shared<AnalogFromButton>());
33 motion_emu = std::make_shared<MotionEmu>(); 34 motion_emu = std::make_shared<MotionEmu>();
34 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); 35 Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
36 Input::RegisterFactory<Input::TouchDevice>("touch_from_button",
37 std::make_shared<TouchFromButtonFactory>());
35 38
36#ifdef HAVE_SDL2 39#ifdef HAVE_SDL2
37 sdl = SDL::Init(); 40 sdl = SDL::Init();
@@ -46,6 +49,7 @@ struct InputSubsystem::Impl {
46 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); 49 Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
47 Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); 50 Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
48 motion_emu.reset(); 51 motion_emu.reset();
52 Input::UnregisterFactory<Input::TouchDevice>("touch_from_button");
49#ifdef HAVE_SDL2 53#ifdef HAVE_SDL2
50 sdl.reset(); 54 sdl.reset();
51#endif 55#endif
@@ -171,6 +175,13 @@ const GCButtonFactory* InputSubsystem::GetGCButtons() const {
171 return impl->gcbuttons.get(); 175 return impl->gcbuttons.get();
172} 176}
173 177
178void InputSubsystem::ReloadInputDevices() {
179 if (!impl->udp) {
180 return;
181 }
182 impl->udp->ReloadUDPClient();
183}
184
174std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( 185std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
175 Polling::DeviceType type) const { 186 Polling::DeviceType type) const {
176#ifdef HAVE_SDL2 187#ifdef HAVE_SDL2
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 58e5dc250..f3fbf696e 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -115,6 +115,9 @@ public:
115 /// Retrieves the underlying GameCube button handler. 115 /// Retrieves the underlying GameCube button handler.
116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const; 116 [[nodiscard]] const GCButtonFactory* GetGCButtons() const;
117 117
118 /// Reloads the input devices
119 void ReloadInputDevices();
120
118 /// Get all DevicePoller from all backends for a specific device type 121 /// Get all DevicePoller from all backends for a specific device type
119 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( 122 [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers(
120 Polling::DeviceType type) const; 123 Polling::DeviceType type) const;
diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp
new file mode 100644
index 000000000..22a849866
--- /dev/null
+++ b/src/input_common/motion_input.cpp
@@ -0,0 +1,181 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included
4
5#include "common/math_util.h"
6#include "input_common/motion_input.h"
7
8namespace InputCommon {
9
10MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd)
11 : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {}
12
13void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) {
14 accel = acceleration;
15}
16
17void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
18 gyro = gyroscope - gyro_drift;
19 if (gyro.Length2() < gyro_threshold) {
20 gyro = {};
21 }
22}
23
24void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
25 quat = quaternion;
26}
27
28void MotionInput::SetGyroDrift(const Common::Vec3f& drift) {
29 gyro_drift = drift;
30}
31
32void MotionInput::SetGyroThreshold(f32 threshold) {
33 gyro_threshold = threshold;
34}
35
36void MotionInput::EnableReset(bool reset) {
37 reset_enabled = reset;
38}
39
40void MotionInput::ResetRotations() {
41 rotations = {};
42}
43
44bool MotionInput::IsMoving(f32 sensitivity) const {
45 return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f;
46}
47
48bool MotionInput::IsCalibrated(f32 sensitivity) const {
49 return real_error.Length() < sensitivity;
50}
51
52void MotionInput::UpdateRotation(u64 elapsed_time) {
53 const f32 sample_period = elapsed_time / 1000000.0f;
54 if (sample_period > 0.1f) {
55 return;
56 }
57 rotations += gyro * sample_period;
58}
59
60void MotionInput::UpdateOrientation(u64 elapsed_time) {
61 if (!IsCalibrated(0.1f)) {
62 ResetOrientation();
63 }
64 // Short name local variable for readability
65 f32 q1 = quat.w;
66 f32 q2 = quat.xyz[0];
67 f32 q3 = quat.xyz[1];
68 f32 q4 = quat.xyz[2];
69 const f32 sample_period = elapsed_time / 1000000.0f;
70
71 // ignore invalid elapsed time
72 if (sample_period > 0.1f) {
73 return;
74 }
75
76 const auto normal_accel = accel.Normalized();
77 auto rad_gyro = gyro * Common::PI * 2;
78 const f32 swap = rad_gyro.x;
79 rad_gyro.x = rad_gyro.y;
80 rad_gyro.y = -swap;
81 rad_gyro.z = -rad_gyro.z;
82
83 // Ignore drift correction if acceleration is not reliable
84 if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) {
85 const f32 ax = -normal_accel.x;
86 const f32 ay = normal_accel.y;
87 const f32 az = -normal_accel.z;
88
89 // Estimated direction of gravity
90 const f32 vx = 2.0f * (q2 * q4 - q1 * q3);
91 const f32 vy = 2.0f * (q1 * q2 + q3 * q4);
92 const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4;
93
94 // Error is cross product between estimated direction and measured direction of gravity
95 const Common::Vec3f new_real_error = {az * vx - ax * vz, ay * vz - az * vy,
96 ax * vy - ay * vx};
97
98 derivative_error = new_real_error - real_error;
99 real_error = new_real_error;
100
101 // Prevent integral windup
102 if (ki != 0.0f && !IsCalibrated(0.05f)) {
103 integral_error += real_error;
104 } else {
105 integral_error = {};
106 }
107
108 // Apply feedback terms
109 rad_gyro += kp * real_error;
110 rad_gyro += ki * integral_error;
111 rad_gyro += kd * derivative_error;
112 }
113
114 const f32 gx = rad_gyro.y;
115 const f32 gy = rad_gyro.x;
116 const f32 gz = rad_gyro.z;
117
118 // Integrate rate of change of quaternion
119 const f32 pa = q2;
120 const f32 pb = q3;
121 const f32 pc = q4;
122 q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period);
123 q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period);
124 q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period);
125 q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period);
126
127 quat.w = q1;
128 quat.xyz[0] = q2;
129 quat.xyz[1] = q3;
130 quat.xyz[2] = q4;
131 quat = quat.Normalized();
132}
133
134std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const {
135 const Common::Quaternion<float> quad{
136 .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w},
137 .w = -quat.xyz[2],
138 };
139 const std::array<float, 16> matrix4x4 = quad.ToMatrix();
140
141 return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]),
142 Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]),
143 Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])};
144}
145
146Common::Vec3f MotionInput::GetAcceleration() const {
147 return accel;
148}
149
150Common::Vec3f MotionInput::GetGyroscope() const {
151 return gyro;
152}
153
154Common::Quaternion<f32> MotionInput::GetQuaternion() const {
155 return quat;
156}
157
158Common::Vec3f MotionInput::GetRotations() const {
159 return rotations;
160}
161
162void MotionInput::ResetOrientation() {
163 if (!reset_enabled) {
164 return;
165 }
166 if (!IsMoving(0.5f) && accel.z <= -0.9f) {
167 ++reset_counter;
168 if (reset_counter > 900) {
169 // TODO: calculate quaternion from gravity vector
170 quat.w = 0;
171 quat.xyz[0] = 0;
172 quat.xyz[1] = 0;
173 quat.xyz[2] = -1;
174 integral_error = {};
175 reset_counter = 0;
176 }
177 } else {
178 reset_counter = 0;
179 }
180}
181} // namespace InputCommon
diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h
new file mode 100644
index 000000000..54b4439d9
--- /dev/null
+++ b/src/input_common/motion_input.h
@@ -0,0 +1,68 @@
1// Copyright 2020 yuzu 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 "common/common_types.h"
8#include "common/quaternion.h"
9#include "common/vector_math.h"
10
11namespace InputCommon {
12
13class MotionInput {
14public:
15 MotionInput(f32 new_kp, f32 new_ki, f32 new_kd);
16
17 MotionInput(const MotionInput&) = default;
18 MotionInput& operator=(const MotionInput&) = default;
19
20 MotionInput(MotionInput&&) = default;
21 MotionInput& operator=(MotionInput&&) = default;
22
23 void SetAcceleration(const Common::Vec3f& acceleration);
24 void SetGyroscope(const Common::Vec3f& acceleration);
25 void SetQuaternion(const Common::Quaternion<f32>& quaternion);
26 void SetGyroDrift(const Common::Vec3f& drift);
27 void SetGyroThreshold(f32 threshold);
28
29 void EnableReset(bool reset);
30 void ResetRotations();
31
32 void UpdateRotation(u64 elapsed_time);
33 void UpdateOrientation(u64 elapsed_time);
34
35 std::array<Common::Vec3f, 3> GetOrientation() const;
36 Common::Vec3f GetAcceleration() const;
37 Common::Vec3f GetGyroscope() const;
38 Common::Vec3f GetRotations() const;
39 Common::Quaternion<f32> GetQuaternion() const;
40
41 bool IsMoving(f32 sensitivity) const;
42 bool IsCalibrated(f32 sensitivity) const;
43
44private:
45 void ResetOrientation();
46
47 // PID constants
48 const f32 kp;
49 const f32 ki;
50 const f32 kd;
51
52 // PID errors
53 Common::Vec3f real_error;
54 Common::Vec3f integral_error;
55 Common::Vec3f derivative_error;
56
57 Common::Quaternion<f32> quat;
58 Common::Vec3f rotations;
59 Common::Vec3f accel;
60 Common::Vec3f gyro;
61 Common::Vec3f gyro_drift;
62
63 f32 gyro_threshold = 0.0f;
64 u32 reset_counter = 0;
65 bool reset_enabled = true;
66};
67
68} // namespace InputCommon
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 030b4dbd3..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
@@ -691,12 +699,7 @@ std::vector<const char*> VKDevice::LoadExtensions() {
691 } 699 }
692 } 700 }
693 701
694 if (has_ext_extended_dynamic_state && driver_id == VK_DRIVER_ID_AMD_PROPRIETARY) { 702 if (has_ext_extended_dynamic_state) {
695 // AMD's proprietary driver supports VK_EXT_extended_dynamic_state but the <stride> field
696 // seems to be bugged. Blacklisting it for now.
697 LOG_WARNING(Render_Vulkan,
698 "Blacklisting AMD proprietary from VK_EXT_extended_dynamic_state");
699 } else if (has_ext_extended_dynamic_state) {
700 VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state; 703 VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
701 dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT; 704 dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
702 dynamic_state.pNext = nullptr; 705 dynamic_state.pNext = nullptr;
diff --git a/src/video_core/shader/async_shaders.cpp b/src/video_core/shader/async_shaders.cpp
index f815584f7..aabd62c5c 100644
--- a/src/video_core/shader/async_shaders.cpp
+++ b/src/video_core/shader/async_shaders.cpp
@@ -73,11 +73,11 @@ void AsyncShaders::KillWorkers() {
73 worker_threads.clear(); 73 worker_threads.clear();
74} 74}
75 75
76bool AsyncShaders::HasWorkQueued() { 76bool AsyncShaders::HasWorkQueued() const {
77 return !pending_queue.empty(); 77 return !pending_queue.empty();
78} 78}
79 79
80bool AsyncShaders::HasCompletedWork() { 80bool AsyncShaders::HasCompletedWork() const {
81 std::shared_lock lock{completed_mutex}; 81 std::shared_lock lock{completed_mutex};
82 return !finished_work.empty(); 82 return !finished_work.empty();
83} 83}
@@ -102,7 +102,7 @@ bool AsyncShaders::IsShaderAsync(const Tegra::GPU& gpu) const {
102} 102}
103 103
104std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() { 104std::vector<AsyncShaders::Result> AsyncShaders::GetCompletedWork() {
105 std::vector<AsyncShaders::Result> results; 105 std::vector<Result> results;
106 { 106 {
107 std::unique_lock lock{completed_mutex}; 107 std::unique_lock lock{completed_mutex};
108 results.assign(std::make_move_iterator(finished_work.begin()), 108 results.assign(std::make_move_iterator(finished_work.begin()),
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
index d5ae814d5..7cf8d994c 100644
--- a/src/video_core/shader/async_shaders.h
+++ b/src/video_core/shader/async_shaders.h
@@ -5,11 +5,10 @@
5#pragma once 5#pragma once
6 6
7#include <condition_variable> 7#include <condition_variable>
8#include <deque>
9#include <memory> 8#include <memory>
10#include <shared_mutex> 9#include <shared_mutex>
11#include <thread> 10#include <thread>
12#include "common/bit_field.h" 11
13#include "common/common_types.h" 12#include "common/common_types.h"
14#include "video_core/renderer_opengl/gl_device.h" 13#include "video_core/renderer_opengl/gl_device.h"
15#include "video_core/renderer_opengl/gl_resource_manager.h" 14#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -17,7 +16,6 @@
17#include "video_core/renderer_vulkan/vk_device.h" 16#include "video_core/renderer_vulkan/vk_device.h"
18#include "video_core/renderer_vulkan/vk_pipeline_cache.h" 17#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
19#include "video_core/renderer_vulkan/vk_scheduler.h" 18#include "video_core/renderer_vulkan/vk_scheduler.h"
20#include "video_core/renderer_vulkan/vk_update_descriptor.h"
21 19
22namespace Core::Frontend { 20namespace Core::Frontend {
23class EmuWindow; 21class EmuWindow;
@@ -70,20 +68,20 @@ public:
70 void KillWorkers(); 68 void KillWorkers();
71 69
72 /// Check to see if any shaders have actually been compiled 70 /// Check to see if any shaders have actually been compiled
73 bool HasCompletedWork(); 71 [[nodiscard]] bool HasCompletedWork() const;
74 72
75 /// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build 73 /// Deduce if a shader can be build on another thread of MUST be built in sync. We cannot build
76 /// every shader async as some shaders are only built and executed once. We try to "guess" which 74 /// every shader async as some shaders are only built and executed once. We try to "guess" which
77 /// shader would be used only once 75 /// shader would be used only once
78 bool IsShaderAsync(const Tegra::GPU& gpu) const; 76 [[nodiscard]] bool IsShaderAsync(const Tegra::GPU& gpu) const;
79 77
80 /// Pulls completed compiled shaders 78 /// Pulls completed compiled shaders
81 std::vector<Result> GetCompletedWork(); 79 [[nodiscard]] std::vector<Result> GetCompletedWork();
82 80
83 void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type, 81 void QueueOpenGLShader(const OpenGL::Device& device, Tegra::Engines::ShaderType shader_type,
84 u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset, 82 u64 uid, std::vector<u64> code, std::vector<u64> code_b, u32 main_offset,
85 VideoCommon::Shader::CompilerSettings compiler_settings, 83 CompilerSettings compiler_settings, const Registry& registry,
86 const VideoCommon::Shader::Registry& registry, VAddr cpu_addr); 84 VAddr cpu_addr);
87 85
88 void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device, 86 void QueueVulkanShader(Vulkan::VKPipelineCache* pp_cache, const Vulkan::VKDevice& device,
89 Vulkan::VKScheduler& scheduler, 87 Vulkan::VKScheduler& scheduler,
@@ -97,7 +95,7 @@ private:
97 void ShaderCompilerThread(Core::Frontend::GraphicsContext* context); 95 void ShaderCompilerThread(Core::Frontend::GraphicsContext* context);
98 96
99 /// Check our worker queue to see if we have any work queued already 97 /// Check our worker queue to see if we have any work queued already
100 bool HasWorkQueued(); 98 [[nodiscard]] bool HasWorkQueued() const;
101 99
102 struct WorkerParams { 100 struct WorkerParams {
103 Backend backend; 101 Backend backend;
@@ -108,8 +106,8 @@ private:
108 std::vector<u64> code; 106 std::vector<u64> code;
109 std::vector<u64> code_b; 107 std::vector<u64> code_b;
110 u32 main_offset; 108 u32 main_offset;
111 VideoCommon::Shader::CompilerSettings compiler_settings; 109 CompilerSettings compiler_settings;
112 std::optional<VideoCommon::Shader::Registry> registry; 110 std::optional<Registry> registry;
113 VAddr cpu_address; 111 VAddr cpu_address;
114 112
115 // For Vulkan 113 // For Vulkan
@@ -125,13 +123,13 @@ private:
125 }; 123 };
126 124
127 std::condition_variable cv; 125 std::condition_variable cv;
128 std::mutex queue_mutex; 126 mutable std::mutex queue_mutex;
129 std::shared_mutex completed_mutex; 127 mutable std::shared_mutex completed_mutex;
130 std::atomic<bool> is_thread_exiting{}; 128 std::atomic<bool> is_thread_exiting{};
131 std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list; 129 std::vector<std::unique_ptr<Core::Frontend::GraphicsContext>> context_list;
132 std::vector<std::thread> worker_threads; 130 std::vector<std::thread> worker_threads;
133 std::queue<WorkerParams> pending_queue; 131 std::queue<WorkerParams> pending_queue;
134 std::vector<AsyncShaders::Result> finished_work; 132 std::vector<Result> finished_work;
135 Core::Frontend::EmuWindow& emu_window; 133 Core::Frontend::EmuWindow& emu_window;
136}; 134};
137 135
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/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 588bbd677..2bc55a26a 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -420,14 +420,64 @@ void Config::ReadControlValues() {
420 ReadKeyboardValues(); 420 ReadKeyboardValues();
421 ReadMouseValues(); 421 ReadMouseValues();
422 ReadTouchscreenValues(); 422 ReadTouchscreenValues();
423 ReadMotionTouchValues();
423 424
424 Settings::values.vibration_enabled = 425 Settings::values.vibration_enabled =
425 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
426 Settings::values.motion_device = 466 Settings::values.motion_device =
427 ReadSetting(QStringLiteral("motion_device"), 467 ReadSetting(QStringLiteral("motion_device"),
428 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) 468 QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
429 .toString() 469 .toString()
430 .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);
431 Settings::values.udp_input_address = 481 Settings::values.udp_input_address =
432 ReadSetting(QStringLiteral("udp_input_address"), 482 ReadSetting(QStringLiteral("udp_input_address"),
433 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) 483 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
@@ -438,10 +488,6 @@ void Config::ReadControlValues() {
438 .toInt()); 488 .toInt());
439 Settings::values.udp_pad_index = 489 Settings::values.udp_pad_index =
440 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); 490 static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
441 Settings::values.use_docked_mode =
442 ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
443
444 qt_config->endGroup();
445} 491}
446 492
447void Config::ReadCoreValues() { 493void Config::ReadCoreValues() {
@@ -934,6 +980,43 @@ void Config::SaveTouchscreenValues() {
934 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); 980 WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15);
935} 981}
936 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
937void Config::SaveValues() { 1020void Config::SaveValues() {
938 if (global) { 1021 if (global) {
939 SaveControlValues(); 1022 SaveControlValues();
@@ -976,18 +1059,16 @@ void Config::SaveControlValues() {
976 SaveDebugValues(); 1059 SaveDebugValues();
977 SaveMouseValues(); 1060 SaveMouseValues();
978 SaveTouchscreenValues(); 1061 SaveTouchscreenValues();
1062 SaveMotionTouchValues();
979 1063
980 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true); 1064 WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
981 WriteSetting(QStringLiteral("motion_device"), 1065 WriteSetting(QStringLiteral("motion_device"),
982 QString::fromStdString(Settings::values.motion_device), 1066 QString::fromStdString(Settings::values.motion_device),
983 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"));
984 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); 1071 WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
985 WriteSetting(QStringLiteral("udp_input_address"),
986 QString::fromStdString(Settings::values.udp_input_address),
987 QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
988 WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
989 InputCommon::CemuhookUDP::DEFAULT_PORT);
990 WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
991 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false); 1072 WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
992 1073
993 qt_config->endGroup(); 1074 qt_config->endGroup();
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index aa929d134..ca0d29c6c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -38,6 +38,7 @@ private:
38 void ReadKeyboardValues(); 38 void ReadKeyboardValues();
39 void ReadMouseValues(); 39 void ReadMouseValues();
40 void ReadTouchscreenValues(); 40 void ReadTouchscreenValues();
41 void ReadMotionTouchValues();
41 42
42 // Read functions bases off the respective config section names. 43 // Read functions bases off the respective config section names.
43 void ReadAudioValues(); 44 void ReadAudioValues();
@@ -64,6 +65,7 @@ private:
64 void SaveDebugValues(); 65 void SaveDebugValues();
65 void SaveMouseValues(); 66 void SaveMouseValues();
66 void SaveTouchscreenValues(); 67 void SaveTouchscreenValues();
68 void SaveMotionTouchValues();
67 69
68 // Save functions based off the respective config section names. 70 // Save functions based off the respective config section names.
69 void SaveAudioValues(); 71 void SaveAudioValues();
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 5223eed1d..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
@@ -127,6 +128,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem) {
127 }); 128 });
128 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog, 129 connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
129 [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 });
130 135
131 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); }); 136 connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
132 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); }); 137 connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
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_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_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.ui b/src/yuzu/main.ui
index 87ea985d8..2f3792247 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -293,7 +293,7 @@
293 <bool>false</bool> 293 <bool>false</bool>
294 </property> 294 </property>
295 <property name="text"> 295 <property name="text">
296 <string>Configure Current Game..</string> 296 <string>Configure Current Game...</string>
297 </property> 297 </property>
298 </action> 298 </action>
299 </widget> 299 </widget>