diff options
77 files changed, 6232 insertions, 2606 deletions
diff --git a/.gitmodules b/.gitmodules index 2c044ed7d..a33a04167 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,9 +13,6 @@ [submodule "dynarmic"] path = externals/dynarmic url = https://github.com/MerryMage/dynarmic.git -[submodule "xbyak"] - path = externals/xbyak - url = https://github.com/herumi/xbyak.git [submodule "fmt"] path = externals/fmt url = https://github.com/fmtlib/fmt.git diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 1261062e8..e156bbece 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -9,7 +9,6 @@ target_include_directories(catch-single-include INTERFACE catch/single_include) # Dynarmic if (ARCHITECTURE_x86_64) - add_library(xbyak INTERFACE) set(DYNARMIC_TESTS OFF) set(DYNARMIC_NO_BUNDLED_FMT ON) add_subdirectory(dynarmic) @@ -53,14 +52,6 @@ target_include_directories(unicorn-headers INTERFACE ./unicorn/include) # SoundTouch add_subdirectory(soundtouch) -# Xbyak -if (ARCHITECTURE_x86_64) - # Defined before "dynarmic" above - # add_library(xbyak INTERFACE) - target_include_directories(xbyak INTERFACE ./xbyak/xbyak) - target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES) -endif() - # Opus add_subdirectory(opus) target_include_directories(opus INTERFACE ./opus/include) diff --git a/externals/xbyak b/externals/xbyak deleted file mode 160000 -Subproject 1de435ed04c8e74775804da944d176baf0ce56e diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index eccd8f64a..a5e71d879 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -44,7 +44,6 @@ add_library(common STATIC detached_tasks.cpp detached_tasks.h bit_field.h - bit_set.h cityhash.cpp cityhash.h color.h @@ -95,14 +94,9 @@ if(ARCHITECTURE_x86_64) PRIVATE x64/cpu_detect.cpp x64/cpu_detect.h - x64/xbyak_abi.h - x64/xbyak_util.h ) endif() create_target_directory_groups(common) target_link_libraries(common PUBLIC Boost::boost fmt microprofile) -if (ARCHITECTURE_x86_64) - target_link_libraries(common PRIVATE xbyak) -endif() diff --git a/src/common/assert.h b/src/common/assert.h index 0d4eddc19..6002f7ab1 100644 --- a/src/common/assert.h +++ b/src/common/assert.h @@ -52,5 +52,8 @@ __declspec(noinline, noreturn) #define DEBUG_ASSERT_MSG(_a_, _desc_, ...) #endif -#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") +#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!") #define UNIMPLEMENTED_MSG(...) ASSERT_MSG(false, __VA_ARGS__) + +#define UNIMPLEMENTED_IF(cond) ASSERT_MSG(!(cond), "Unimplemented code!") +#define UNIMPLEMENTED_IF_MSG(cond, ...) ASSERT_MSG(!(cond), __VA_ARGS__) diff --git a/src/common/bit_set.h b/src/common/bit_set.h deleted file mode 100644 index 5cd1352b2..000000000 --- a/src/common/bit_set.h +++ /dev/null @@ -1,244 +0,0 @@ -// This file is under the public domain. - -#pragma once - -#include <cstddef> -#ifdef _WIN32 -#include <intrin.h> -#endif -#include <initializer_list> -#include <new> -#include <type_traits> -#include "common/common_types.h" - -// namespace avoids conflict with OS X Carbon; don't use BitSet<T> directly -namespace Common { - -// Helper functions: - -#ifdef _MSC_VER -template <typename T> -static inline int CountSetBits(T v) { - // from https://graphics.stanford.edu/~seander/bithacks.html - // GCC has this built in, but MSVC's intrinsic will only emit the actual - // POPCNT instruction, which we're not depending on - v = v - ((v >> 1) & (T) ~(T)0 / 3); - v = (v & (T) ~(T)0 / 15 * 3) + ((v >> 2) & (T) ~(T)0 / 15 * 3); - v = (v + (v >> 4)) & (T) ~(T)0 / 255 * 15; - return (T)(v * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8; -} -static inline int LeastSignificantSetBit(u8 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u16 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u32 val) { - unsigned long index; - _BitScanForward(&index, val); - return (int)index; -} -static inline int LeastSignificantSetBit(u64 val) { - unsigned long index; - _BitScanForward64(&index, val); - return (int)index; -} -#else -static inline int CountSetBits(u8 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u16 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u32 val) { - return __builtin_popcount(val); -} -static inline int CountSetBits(u64 val) { - return __builtin_popcountll(val); -} -static inline int LeastSignificantSetBit(u8 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u16 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u32 val) { - return __builtin_ctz(val); -} -static inline int LeastSignificantSetBit(u64 val) { - return __builtin_ctzll(val); -} -#endif - -// Similar to std::bitset, this is a class which encapsulates a bitset, i.e. -// using the set bits of an integer to represent a set of integers. Like that -// class, it acts like an array of bools: -// BitSet32 bs; -// bs[1] = true; -// but also like the underlying integer ([0] = least significant bit): -// BitSet32 bs2 = ...; -// bs = (bs ^ bs2) & BitSet32(0xffff); -// The following additional functionality is provided: -// - Construction using an initializer list. -// BitSet bs { 1, 2, 4, 8 }; -// - Efficiently iterating through the set bits: -// for (int i : bs) -// [i is the *index* of a set bit] -// (This uses the appropriate CPU instruction to find the next set bit in one -// operation.) -// - Counting set bits using .Count() - see comment on that method. - -// TODO: use constexpr when MSVC gets out of the Dark Ages - -template <typename IntTy> -class BitSet { - static_assert(!std::is_signed_v<IntTy>, "BitSet should not be used with signed types"); - -public: - // A reference to a particular bit, returned from operator[]. - class Ref { - public: - Ref(Ref&& other) : m_bs(other.m_bs), m_mask(other.m_mask) {} - Ref(BitSet* bs, IntTy mask) : m_bs(bs), m_mask(mask) {} - operator bool() const { - return (m_bs->m_val & m_mask) != 0; - } - bool operator=(bool set) { - m_bs->m_val = (m_bs->m_val & ~m_mask) | (set ? m_mask : 0); - return set; - } - - private: - BitSet* m_bs; - IntTy m_mask; - }; - - // A STL-like iterator is required to be able to use range-based for loops. - class Iterator { - public: - Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {} - Iterator(IntTy val) : m_val(val), m_bit(0) {} - Iterator& operator=(Iterator other) { - new (this) Iterator(other); - return *this; - } - int operator*() { - return m_bit + ComputeLsb(); - } - Iterator& operator++() { - int lsb = ComputeLsb(); - m_val >>= lsb + 1; - m_bit += lsb + 1; - m_has_lsb = false; - return *this; - } - Iterator operator++(int _) { - Iterator other(*this); - ++*this; - return other; - } - bool operator==(Iterator other) const { - return m_val == other.m_val; - } - bool operator!=(Iterator other) const { - return m_val != other.m_val; - } - - private: - int ComputeLsb() { - if (!m_has_lsb) { - m_lsb = LeastSignificantSetBit(m_val); - m_has_lsb = true; - } - return m_lsb; - } - IntTy m_val; - int m_bit; - int m_lsb = -1; - bool m_has_lsb = false; - }; - - BitSet() : m_val(0) {} - explicit BitSet(IntTy val) : m_val(val) {} - BitSet(std::initializer_list<int> init) { - m_val = 0; - for (int bit : init) - m_val |= (IntTy)1 << bit; - } - - static BitSet AllTrue(std::size_t count) { - return BitSet(count == sizeof(IntTy) * 8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1)); - } - - Ref operator[](std::size_t bit) { - return Ref(this, (IntTy)1 << bit); - } - const Ref operator[](std::size_t bit) const { - return (*const_cast<BitSet*>(this))[bit]; - } - bool operator==(BitSet other) const { - return m_val == other.m_val; - } - bool operator!=(BitSet other) const { - return m_val != other.m_val; - } - bool operator<(BitSet other) const { - return m_val < other.m_val; - } - bool operator>(BitSet other) const { - return m_val > other.m_val; - } - BitSet operator|(BitSet other) const { - return BitSet(m_val | other.m_val); - } - BitSet operator&(BitSet other) const { - return BitSet(m_val & other.m_val); - } - BitSet operator^(BitSet other) const { - return BitSet(m_val ^ other.m_val); - } - BitSet operator~() const { - return BitSet(~m_val); - } - BitSet& operator|=(BitSet other) { - return *this = *this | other; - } - BitSet& operator&=(BitSet other) { - return *this = *this & other; - } - BitSet& operator^=(BitSet other) { - return *this = *this ^ other; - } - operator u32() = delete; - operator bool() { - return m_val != 0; - } - - // Warning: Even though on modern CPUs this is a single fast instruction, - // Dolphin's official builds do not currently assume POPCNT support on x86, - // so slower explicit bit twiddling is generated. Still should generally - // be faster than a loop. - unsigned int Count() const { - return CountSetBits(m_val); - } - - Iterator begin() const { - return Iterator(m_val); - } - Iterator end() const { - return Iterator(0); - } - - IntTy m_val; -}; - -} // namespace Common - -typedef Common::BitSet<u8> BitSet8; -typedef Common::BitSet<u16> BitSet16; -typedef Common::BitSet<u32> BitSet32; -typedef Common::BitSet<u64> BitSet64; diff --git a/src/common/math_util.h b/src/common/math_util.h index 343cdd902..94b4394c5 100644 --- a/src/common/math_util.h +++ b/src/common/math_util.h @@ -4,18 +4,12 @@ #pragma once -#include <algorithm> #include <cstdlib> #include <type_traits> namespace MathUtil { -static constexpr float PI = 3.14159265f; - -inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1, - unsigned length1) { - return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1)); -} +constexpr float PI = 3.14159265f; template <class T> struct Rectangle { @@ -24,16 +18,16 @@ struct Rectangle { T right{}; T bottom{}; - Rectangle() = default; + constexpr Rectangle() = default; - Rectangle(T left, T top, T right, T bottom) + constexpr Rectangle(T left, T top, T right, T bottom) : left(left), top(top), right(right), bottom(bottom) {} T GetWidth() const { - return std::abs(static_cast<typename std::make_signed<T>::type>(right - left)); + return std::abs(static_cast<std::make_signed_t<T>>(right - left)); } T GetHeight() const { - return std::abs(static_cast<typename std::make_signed<T>::type>(bottom - top)); + return std::abs(static_cast<std::make_signed_t<T>>(bottom - top)); } Rectangle<T> TranslateX(const T x) const { return Rectangle{left + x, top, right + x, bottom}; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 14f7037d8..959f278aa 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -214,6 +214,15 @@ std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t return std::string(buffer, len); } +std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, + std::size_t max_len) { + std::size_t len = 0; + while (len < max_len && buffer[len] != '\0') + ++len; + + return std::u16string(buffer.begin(), buffer.begin() + len); +} + const char* TrimSourcePath(const char* path, const char* root) { const char* p = path; diff --git a/src/common/string_util.h b/src/common/string_util.h index 08f96533b..583fd05e6 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -67,6 +67,14 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); /** + * Creates a UTF-16 std::u16string from a fixed-size NUL-terminated char buffer. If the buffer isn't + * null-terminated, then the string ends at the greatest multiple of two less then or equal to + * max_len_bytes. + */ +std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buffer, + std::size_t max_len); + +/** * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's * intended to be used to strip a system-specific build directory from the `__FILE__` macro, * leaving only the path relative to the sources root. diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h deleted file mode 100644 index 636a5c0f9..000000000 --- a/src/common/x64/xbyak_abi.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <initializer_list> -#include <xbyak.h> -#include "common/assert.h" -#include "common/bit_set.h" - -namespace Common::X64 { - -inline int RegToIndex(const Xbyak::Reg& reg) { - using Kind = Xbyak::Reg::Kind; - ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0, - "RegSet only support GPRs and XMM registers."); - ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15."); - return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16); -} - -inline Xbyak::Reg64 IndexToReg64(int reg_index) { - ASSERT(reg_index < 16); - return Xbyak::Reg64(reg_index); -} - -inline Xbyak::Xmm IndexToXmm(int reg_index) { - ASSERT(reg_index >= 16 && reg_index < 32); - return Xbyak::Xmm(reg_index - 16); -} - -inline Xbyak::Reg IndexToReg(int reg_index) { - if (reg_index < 16) { - return IndexToReg64(reg_index); - } else { - return IndexToXmm(reg_index); - } -} - -inline BitSet32 BuildRegSet(std::initializer_list<Xbyak::Reg> regs) { - BitSet32 bits; - for (const Xbyak::Reg& reg : regs) { - bits[RegToIndex(reg)] = true; - } - return bits; -} - -const BitSet32 ABI_ALL_GPRS(0x0000FFFF); -const BitSet32 ABI_ALL_XMMS(0xFFFF0000); - -#ifdef _WIN32 - -// Microsoft x64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9; - -const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rcx, - Xbyak::util::rdx, - Xbyak::util::r8, - Xbyak::util::r9, - Xbyak::util::r10, - Xbyak::util::r11, - // XMMs - Xbyak::util::xmm0, - Xbyak::util::xmm1, - Xbyak::util::xmm2, - Xbyak::util::xmm3, - Xbyak::util::xmm4, - Xbyak::util::xmm5, -}); - -const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rbx, - Xbyak::util::rsi, - Xbyak::util::rdi, - Xbyak::util::rbp, - Xbyak::util::r12, - Xbyak::util::r13, - Xbyak::util::r14, - Xbyak::util::r15, - // XMMs - Xbyak::util::xmm6, - Xbyak::util::xmm7, - Xbyak::util::xmm8, - Xbyak::util::xmm9, - Xbyak::util::xmm10, - Xbyak::util::xmm11, - Xbyak::util::xmm12, - Xbyak::util::xmm13, - Xbyak::util::xmm14, - Xbyak::util::xmm15, -}); - -constexpr std::size_t ABI_SHADOW_SPACE = 0x20; - -#else - -// System V x86-64 ABI -const Xbyak::Reg ABI_RETURN = Xbyak::util::rax; -const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi; -const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi; -const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx; -const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx; - -const BitSet32 ABI_ALL_CALLER_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rcx, - Xbyak::util::rdx, - Xbyak::util::rdi, - Xbyak::util::rsi, - Xbyak::util::r8, - Xbyak::util::r9, - Xbyak::util::r10, - Xbyak::util::r11, - // XMMs - Xbyak::util::xmm0, - Xbyak::util::xmm1, - Xbyak::util::xmm2, - Xbyak::util::xmm3, - Xbyak::util::xmm4, - Xbyak::util::xmm5, - Xbyak::util::xmm6, - Xbyak::util::xmm7, - Xbyak::util::xmm8, - Xbyak::util::xmm9, - Xbyak::util::xmm10, - Xbyak::util::xmm11, - Xbyak::util::xmm12, - Xbyak::util::xmm13, - Xbyak::util::xmm14, - Xbyak::util::xmm15, -}); - -const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ - // GPRs - Xbyak::util::rbx, - Xbyak::util::rbp, - Xbyak::util::r12, - Xbyak::util::r13, - Xbyak::util::r14, - Xbyak::util::r15, -}); - -constexpr std::size_t ABI_SHADOW_SPACE = 0; - -#endif - -inline void ABI_CalculateFrameSize(BitSet32 regs, std::size_t rsp_alignment, - std::size_t needed_frame_size, s32* out_subtraction, - s32* out_xmm_offset) { - int count = (regs & ABI_ALL_GPRS).Count(); - rsp_alignment -= count * 8; - std::size_t subtraction = 0; - int xmm_count = (regs & ABI_ALL_XMMS).Count(); - if (xmm_count) { - // If we have any XMMs to save, we must align the stack here. - subtraction = rsp_alignment & 0xF; - } - subtraction += 0x10 * xmm_count; - std::size_t xmm_base_subtraction = subtraction; - subtraction += needed_frame_size; - subtraction += ABI_SHADOW_SPACE; - // Final alignment. - rsp_alignment -= subtraction; - subtraction += rsp_alignment & 0xF; - - *out_subtraction = (s32)subtraction; - *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction); -} - -inline std::size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - std::size_t rsp_alignment, - std::size_t needed_frame_size = 0) { - s32 subtraction, xmm_offset; - ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); - - for (int reg_index : (regs & ABI_ALL_GPRS)) { - code.push(IndexToReg64(reg_index)); - } - - if (subtraction != 0) { - code.sub(code.rsp, subtraction); - } - - for (int reg_index : (regs & ABI_ALL_XMMS)) { - code.movaps(code.xword[code.rsp + xmm_offset], IndexToXmm(reg_index)); - xmm_offset += 0x10; - } - - return ABI_SHADOW_SPACE; -} - -inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - std::size_t rsp_alignment, - std::size_t needed_frame_size = 0) { - s32 subtraction, xmm_offset; - ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); - - for (int reg_index : (regs & ABI_ALL_XMMS)) { - code.movaps(IndexToXmm(reg_index), code.xword[code.rsp + xmm_offset]); - xmm_offset += 0x10; - } - - if (subtraction != 0) { - code.add(code.rsp, subtraction); - } - - // GPRs need to be popped in reverse order - for (int reg_index = 15; reg_index >= 0; reg_index--) { - if (regs[reg_index]) { - code.pop(IndexToReg64(reg_index)); - } - } -} - -} // namespace Common::X64 diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h deleted file mode 100644 index 5cc8a8c76..000000000 --- a/src/common/x64/xbyak_util.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <type_traits> -#include <xbyak.h> -#include "common/x64/xbyak_abi.h" - -namespace Common::X64 { - -// Constants for use with cmpps/cmpss -enum { - CMP_EQ = 0, - CMP_LT = 1, - CMP_LE = 2, - CMP_UNORD = 3, - CMP_NEQ = 4, - CMP_NLT = 5, - CMP_NLE = 6, - CMP_ORD = 7, -}; - -inline bool IsWithin2G(uintptr_t ref, uintptr_t target) { - u64 distance = target - (ref + 5); - return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL); -} - -inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { - return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target); -} - -template <typename T> -inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { - static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer."); - std::size_t addr = reinterpret_cast<std::size_t>(f); - if (IsWithin2G(code, addr)) { - code.call(f); - } else { - // ABI_RETURN is a safe temp register to use before a call - code.mov(ABI_RETURN, addr); - code.call(ABI_RETURN); - } -} - -} // namespace Common::X64 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 64fdf38cd..a355eaca6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -77,6 +77,8 @@ add_library(core STATIC file_sys/vfs_vector.h file_sys/xts_archive.cpp file_sys/xts_archive.h + frontend/applets/software_keyboard.cpp + frontend/applets/software_keyboard.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp @@ -150,6 +152,10 @@ add_library(core STATIC hle/service/am/applet_ae.h hle/service/am/applet_oe.cpp hle/service/am/applet_oe.h + hle/service/am/applets/applets.cpp + hle/service/am/applets/applets.h + hle/service/am/applets/software_keyboard.cpp + hle/service/am/applets/software_keyboard.h hle/service/am/idle.cpp hle/service/am/idle.h hle/service/am/omm.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 6d5b5a2d0..6c72fdf4a 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -23,12 +23,14 @@ #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" +#include "core/hle/service/am/applets/software_keyboard.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "frontend/applets/software_keyboard.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" @@ -136,6 +138,10 @@ struct System::Impl { if (virtual_filesystem == nullptr) virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>(); + /// Create default implementations of applets if one is not provided. + if (software_keyboard == nullptr) + software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>(); + auto main_process = Kernel::Process::Create(kernel, "main"); kernel.MakeCurrentProcess(main_process.get()); @@ -289,6 +295,9 @@ struct System::Impl { std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; std::size_t active_core{}; ///< Active core, only used in single thread mode + /// Frontend applets + std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; + /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; @@ -488,6 +497,14 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) { + impl->software_keyboard = std::move(applet); +} + +const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const { + return *impl->software_keyboard; +} + System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) { return impl->Init(emu_window); } diff --git a/src/core/core.h b/src/core/core.h index cfacceb81..be71bd437 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -13,6 +13,7 @@ namespace Core::Frontend { class EmuWindow; +class SoftwareKeyboardApplet; } // namespace Core::Frontend namespace FileSys { @@ -236,6 +237,10 @@ public: std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; + void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet); + + const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const; + private: System(); diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 25f5914b6..a350496f7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -32,7 +32,18 @@ enum class GamecardSize : u8 { }; struct GamecardInfo { - std::array<u8, 0x70> data; + u64_le firmware_version; + u32_le access_control_flags; + u32_le read_wait_time1; + u32_le read_wait_time2; + u32_le write_wait_time1; + u32_le write_wait_time2; + u32_le firmware_mode; + u32_le cup_version; + std::array<u8, 4> reserved1; + u64_le update_partition_hash; + u64_le cup_id; + std::array<u8, 0x38> reserved2; }; static_assert(sizeof(GamecardInfo) == 0x70, "GamecardInfo has incorrect size."); diff --git a/src/core/frontend/applets/software_keyboard.cpp b/src/core/frontend/applets/software_keyboard.cpp new file mode 100644 index 000000000..856ed33da --- /dev/null +++ b/src/core/frontend/applets/software_keyboard.cpp @@ -0,0 +1,29 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/backend.h" +#include "common/string_util.h" +#include "core/frontend/applets/software_keyboard.h" + +namespace Core::Frontend { +SoftwareKeyboardApplet::~SoftwareKeyboardApplet() = default; + +void DefaultSoftwareKeyboardApplet::RequestText( + std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const { + if (parameters.initial_text.empty()) + out(u"yuzu"); + + out(parameters.initial_text); +} + +void DefaultSoftwareKeyboardApplet::SendTextCheckDialog( + std::u16string error_message, std::function<void()> finished_check) const { + LOG_WARNING(Service_AM, + "(STUBBED) called - Default fallback software keyboard does not support text " + "check! (error_message={})", + Common::UTF16ToUTF8(error_message)); + finished_check(); +} +} // namespace Core::Frontend diff --git a/src/core/frontend/applets/software_keyboard.h b/src/core/frontend/applets/software_keyboard.h new file mode 100644 index 000000000..f9b202664 --- /dev/null +++ b/src/core/frontend/applets/software_keyboard.h @@ -0,0 +1,54 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <optional> +#include <string> +#include "common/bit_field.h" +#include "common/common_types.h" + +namespace Core::Frontend { +struct SoftwareKeyboardParameters { + std::u16string submit_text; + std::u16string header_text; + std::u16string sub_text; + std::u16string guide_text; + std::u16string initial_text; + std::size_t max_length; + bool password; + bool cursor_at_beginning; + + union { + u8 value; + + BitField<1, 1, u8> disable_space; + BitField<2, 1, u8> disable_address; + BitField<3, 1, u8> disable_percent; + BitField<4, 1, u8> disable_slash; + BitField<6, 1, u8> disable_number; + BitField<7, 1, u8> disable_download_code; + }; +}; + +class SoftwareKeyboardApplet { +public: + virtual ~SoftwareKeyboardApplet(); + + virtual void RequestText(std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const = 0; + virtual void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const = 0; +}; + +class DefaultSoftwareKeyboardApplet final : public SoftwareKeyboardApplet { +public: + void RequestText(std::function<void(std::optional<std::u16string>)> out, + SoftwareKeyboardParameters parameters) const override; + void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const override; +}; + +} // namespace Core::Frontend diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 39bdf4e21..16fdcd376 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -132,4 +132,11 @@ using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float> */ using TouchDevice = InputDevice<std::tuple<float, float, bool>>; +/** + * A mouse device is an input device that returns a tuple of two floats and four ints. + * The first two floats are X and Y device coordinates of the mouse (from 0-1). + * The s32s are the mouse wheel. + */ +using MouseDevice = InputDevice<std::tuple<float, float, s32, s32>>; + } // namespace Input diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index 5ee5c05e3..1bf79b692 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -12,12 +12,23 @@ #include "core/hle/kernel/thread.h" namespace Kernel { +namespace { +constexpr u16 GetSlot(Handle handle) { + return handle >> 15; +} + +constexpr u16 GetGeneration(Handle handle) { + return handle & 0x7FFF; +} +} // Anonymous namespace HandleTable::HandleTable() { next_generation = 1; Clear(); } +HandleTable::~HandleTable() = default; + ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) { DEBUG_ASSERT(obj != nullptr); diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h index 9e2f33e8a..e3f3e3fb8 100644 --- a/src/core/hle/kernel/handle_table.h +++ b/src/core/hle/kernel/handle_table.h @@ -43,6 +43,7 @@ enum KernelHandle : Handle { class HandleTable final : NonCopyable { public: HandleTable(); + ~HandleTable(); /** * Allocates a handle for the given object. @@ -89,18 +90,8 @@ public: void Clear(); private: - /** - * This is the maximum limit of handles allowed per process in CTR-OS. It can be further - * reduced by ExHeader values, but this is not emulated here. - */ - static const std::size_t MAX_COUNT = 4096; - - static u16 GetSlot(Handle handle) { - return handle >> 15; - } - static u16 GetGeneration(Handle handle) { - return handle & 0x7FFF; - } + /// This is the maximum limit of handles allowed per process in Horizon + static constexpr std::size_t MAX_COUNT = 1024; /// Stores the Object referenced by the handle or null if the slot is empty. std::array<SharedPtr<Object>, MAX_COUNT> objects; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1fd4ba5d2..e441c5bc6 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -105,7 +105,7 @@ struct KernelCore::Impl { void Initialize(KernelCore& kernel) { Shutdown(); - InitializeResourceLimits(kernel); + InitializeSystemResourceLimit(kernel); InitializeThreads(); InitializeTimers(); } @@ -118,7 +118,7 @@ struct KernelCore::Impl { process_list.clear(); current_process = nullptr; - resource_limits.fill(nullptr); + system_resource_limit = nullptr; thread_wakeup_callback_handle_table.Clear(); thread_wakeup_event_type = nullptr; @@ -129,63 +129,17 @@ struct KernelCore::Impl { named_ports.clear(); } - void InitializeResourceLimits(KernelCore& kernel) { - // Create the four resource limits that the system uses - // Create the APPLICATION resource limit - SharedPtr<ResourceLimit> resource_limit = ResourceLimit::Create(kernel, "Applications"); - resource_limit->max_priority = 0x18; - resource_limit->max_commit = 0x4000000; - resource_limit->max_threads = 0x20; - resource_limit->max_events = 0x20; - resource_limit->max_mutexes = 0x20; - resource_limit->max_semaphores = 0x8; - resource_limit->max_timers = 0x8; - resource_limit->max_shared_mems = 0x10; - resource_limit->max_address_arbiters = 0x2; - resource_limit->max_cpu_time = 0x1E; - resource_limits[static_cast<u8>(ResourceLimitCategory::APPLICATION)] = resource_limit; - - // Create the SYS_APPLET resource limit - resource_limit = ResourceLimit::Create(kernel, "System Applets"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x5E00000; - resource_limit->max_threads = 0x1D; - resource_limit->max_events = 0xB; - resource_limit->max_mutexes = 0x8; - resource_limit->max_semaphores = 0x4; - resource_limit->max_timers = 0x4; - resource_limit->max_shared_mems = 0x8; - resource_limit->max_address_arbiters = 0x3; - resource_limit->max_cpu_time = 0x2710; - resource_limits[static_cast<u8>(ResourceLimitCategory::SYS_APPLET)] = resource_limit; - - // Create the LIB_APPLET resource limit - resource_limit = ResourceLimit::Create(kernel, "Library Applets"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x600000; - resource_limit->max_threads = 0xE; - resource_limit->max_events = 0x8; - resource_limit->max_mutexes = 0x8; - resource_limit->max_semaphores = 0x4; - resource_limit->max_timers = 0x4; - resource_limit->max_shared_mems = 0x8; - resource_limit->max_address_arbiters = 0x1; - resource_limit->max_cpu_time = 0x2710; - resource_limits[static_cast<u8>(ResourceLimitCategory::LIB_APPLET)] = resource_limit; - - // Create the OTHER resource limit - resource_limit = ResourceLimit::Create(kernel, "Others"); - resource_limit->max_priority = 0x4; - resource_limit->max_commit = 0x2180000; - resource_limit->max_threads = 0xE1; - resource_limit->max_events = 0x108; - resource_limit->max_mutexes = 0x25; - resource_limit->max_semaphores = 0x43; - resource_limit->max_timers = 0x2C; - resource_limit->max_shared_mems = 0x1F; - resource_limit->max_address_arbiters = 0x2D; - resource_limit->max_cpu_time = 0x3E8; - resource_limits[static_cast<u8>(ResourceLimitCategory::OTHER)] = resource_limit; + // Creates the default system resource limit + void InitializeSystemResourceLimit(KernelCore& kernel) { + system_resource_limit = ResourceLimit::Create(kernel, "System"); + + // If setting the default system values fails, then something seriously wrong has occurred. + ASSERT(system_resource_limit->SetLimitValue(ResourceType::PhysicalMemory, 0x200000000) + .IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Threads, 800).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Events, 700).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::TransferMemory, 200).IsSuccess()); + ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess()); } void InitializeThreads() { @@ -208,7 +162,7 @@ struct KernelCore::Impl { std::vector<SharedPtr<Process>> process_list; Process* current_process = nullptr; - std::array<SharedPtr<ResourceLimit>, 4> resource_limits; + SharedPtr<ResourceLimit> system_resource_limit; /// The event type of the generic timer callback event CoreTiming::EventType* timer_callback_event_type = nullptr; @@ -239,9 +193,8 @@ void KernelCore::Shutdown() { impl->Shutdown(); } -SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory( - ResourceLimitCategory category) const { - return impl->resource_limits.at(static_cast<std::size_t>(category)); +SharedPtr<ResourceLimit> KernelCore::GetSystemResourceLimit() const { + return impl->system_resource_limit; } SharedPtr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const { diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 7f822d524..ea00c89f5 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -24,8 +24,6 @@ class ResourceLimit; class Thread; class Timer; -enum class ResourceLimitCategory : u8; - /// Represents a single instance of the kernel. class KernelCore { private: @@ -47,8 +45,8 @@ public: /// Clears all resources in use by the kernel instance. void Shutdown(); - /// Retrieves a shared pointer to a ResourceLimit identified by the given category. - SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const; + /// Retrieves a shared pointer to the system resource limit instance. + SharedPtr<ResourceLimit> GetSystemResourceLimit() const; /// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table. SharedPtr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const; diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index a257c3726..7ca538401 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -4,6 +4,7 @@ #include <algorithm> #include <memory> +#include <random> #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -28,7 +29,7 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { process->name = std::move(name); process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); - process->resource_limit = kernel.ResourceLimitForCategory(ResourceLimitCategory::APPLICATION); + process->resource_limit = kernel.GetSystemResourceLimit(); process->status = ProcessStatus::Created; process->program_id = 0; process->process_id = kernel.CreateNewProcessID(); diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 230e395ff..ada845c7f 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -8,7 +8,6 @@ #include <bitset> #include <cstddef> #include <memory> -#include <random> #include <string> #include <vector> #include <boost/container/static_vector.hpp> diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index b253a680f..2f9695005 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp @@ -2,12 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> -#include "common/assert.h" -#include "common/logging/log.h" +#include "core/hle/kernel/errors.h" #include "core/hle/kernel/resource_limit.h" +#include "core/hle/result.h" namespace Kernel { +namespace { +constexpr std::size_t ResourceTypeToIndex(ResourceType type) { + return static_cast<std::size_t>(type); +} +} // Anonymous namespace ResourceLimit::ResourceLimit(KernelCore& kernel) : Object{kernel} {} ResourceLimit::~ResourceLimit() = default; @@ -19,59 +23,22 @@ SharedPtr<ResourceLimit> ResourceLimit::Create(KernelCore& kernel, std::string n return resource_limit; } -s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { - switch (resource) { - case ResourceType::Commit: - return current_commit; - case ResourceType::Thread: - return current_threads; - case ResourceType::Event: - return current_events; - case ResourceType::Mutex: - return current_mutexes; - case ResourceType::Semaphore: - return current_semaphores; - case ResourceType::Timer: - return current_timers; - case ResourceType::SharedMemory: - return current_shared_mems; - case ResourceType::AddressArbiter: - return current_address_arbiters; - case ResourceType::CPUTime: - return current_cpu_time; - default: - LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); - UNIMPLEMENTED(); - return 0; - } +s64 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const { + return values.at(ResourceTypeToIndex(resource)); +} + +s64 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { + return limits.at(ResourceTypeToIndex(resource)); } -u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const { - switch (resource) { - case ResourceType::Priority: - return max_priority; - case ResourceType::Commit: - return max_commit; - case ResourceType::Thread: - return max_threads; - case ResourceType::Event: - return max_events; - case ResourceType::Mutex: - return max_mutexes; - case ResourceType::Semaphore: - return max_semaphores; - case ResourceType::Timer: - return max_timers; - case ResourceType::SharedMemory: - return max_shared_mems; - case ResourceType::AddressArbiter: - return max_address_arbiters; - case ResourceType::CPUTime: - return max_cpu_time; - default: - LOG_ERROR(Kernel, "Unknown resource type={:08X}", static_cast<u32>(resource)); - UNIMPLEMENTED(); - return 0; +ResultCode ResourceLimit::SetLimitValue(ResourceType resource, s64 value) { + const auto index = ResourceTypeToIndex(resource); + + if (value < values[index]) { + return ERR_INVALID_STATE; } + + values[index] = value; + return RESULT_SUCCESS; } } // namespace Kernel diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 219e49562..bec065543 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h @@ -4,31 +4,25 @@ #pragma once +#include <array> #include "common/common_types.h" #include "core/hle/kernel/object.h" +union ResultCode; + namespace Kernel { class KernelCore; -enum class ResourceLimitCategory : u8 { - APPLICATION = 0, - SYS_APPLET = 1, - LIB_APPLET = 2, - OTHER = 3 -}; - enum class ResourceType { - Priority = 0, - Commit = 1, - Thread = 2, - Event = 3, - Mutex = 4, - Semaphore = 5, - Timer = 6, - SharedMemory = 7, - AddressArbiter = 8, - CPUTime = 9, + PhysicalMemory, + Threads, + Events, + TransferMemory, + Sessions, + + // Used as a count, not an actual type. + ResourceTypeCount }; class ResourceLimit final : public Object { @@ -55,61 +49,51 @@ public: * @param resource Requested resource type * @returns The current value of the resource type */ - s32 GetCurrentResourceValue(ResourceType resource) const; + s64 GetCurrentResourceValue(ResourceType resource) const; /** * Gets the max value for the specified resource. * @param resource Requested resource type * @returns The max value of the resource type */ - u32 GetMaxResourceValue(ResourceType resource) const; - - /// Name of resource limit object. - std::string name; - - /// Max thread priority that a process in this category can create - s32 max_priority = 0; - - /// Max memory that processes in this category can use - s32 max_commit = 0; + s64 GetMaxResourceValue(ResourceType resource) const; - ///< Max number of objects that can be collectively created by the processes in this category - s32 max_threads = 0; - s32 max_events = 0; - s32 max_mutexes = 0; - s32 max_semaphores = 0; - s32 max_timers = 0; - s32 max_shared_mems = 0; - s32 max_address_arbiters = 0; + /** + * Sets the limit value for a given resource type. + * + * @param resource The resource type to apply the limit to. + * @param value The limit to apply to the given resource type. + * + * @return A result code indicating if setting the limit value + * was successful or not. + * + * @note The supplied limit value *must* be greater than or equal to + * the current resource value for the given resource type, + * otherwise ERR_INVALID_STATE will be returned. + */ + ResultCode SetLimitValue(ResourceType resource, s64 value); - /// Max CPU time that the processes in this category can utilize - s32 max_cpu_time = 0; +private: + explicit ResourceLimit(KernelCore& kernel); + ~ResourceLimit() override; - // TODO(Subv): Increment these in their respective Kernel::T::Create functions, keeping in mind - // that APPLICATION resource limits should not be affected by the objects created by service - // modules. + // TODO(Subv): Increment resource limit current values in their respective Kernel::T::Create + // functions + // // Currently we have no way of distinguishing if a Create was called by the running application, // or by a service module. Approach this once we have separated the service modules into their // own processes - /// Current memory that the processes in this category are using - s32 current_commit = 0; + using ResourceArray = + std::array<s64, static_cast<std::size_t>(ResourceType::ResourceTypeCount)>; - ///< Current number of objects among all processes in this category - s32 current_threads = 0; - s32 current_events = 0; - s32 current_mutexes = 0; - s32 current_semaphores = 0; - s32 current_timers = 0; - s32 current_shared_mems = 0; - s32 current_address_arbiters = 0; + /// Maximum values a resource type may reach. + ResourceArray limits{}; + /// Current resource limit values. + ResourceArray values{}; - /// Current CPU time that the processes in this category are utilizing - s32 current_cpu_time = 0; - -private: - explicit ResourceLimit(KernelCore& kernel); - ~ResourceLimit() override; + /// Name of resource limit object. + std::string name; }; } // namespace Kernel diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index a016a86b6..0494581f5 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -61,7 +61,7 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce } SharedPtr<SharedMemory> SharedMemory::CreateForApplet( - KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, u32 offset, u32 size, + KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, std::size_t offset, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name) { SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel)); @@ -78,10 +78,10 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet( return shared_memory; } -ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions, +ResultCode SharedMemory::Map(Process& target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions) { const MemoryPermission own_other_permissions = - target_process == owner_process ? this->permissions : this->other_permissions; + &target_process == owner_process ? this->permissions : this->other_permissions; // Automatically allocated memory blocks can only be mapped with other_permissions = DontCare if (base_address == 0 && other_permissions != MemoryPermission::DontCare) { @@ -106,7 +106,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi VAddr target_address = address; // Map the memory block into the target process - auto result = target_process->VMManager().MapMemoryBlock( + auto result = target_process.VMManager().MapMemoryBlock( target_address, backing_block, backing_block_offset, size, MemoryState::Shared); if (result.Failed()) { LOG_ERROR( @@ -116,14 +116,14 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi return result.Code(); } - return target_process->VMManager().ReprotectRange(target_address, size, - ConvertPermissions(permissions)); + return target_process.VMManager().ReprotectRange(target_address, size, + ConvertPermissions(permissions)); } -ResultCode SharedMemory::Unmap(Process* target_process, VAddr address) { +ResultCode SharedMemory::Unmap(Process& target_process, VAddr address) { // TODO(Subv): Verify what happens if the application tries to unmap an address that is not // mapped to a SharedMemory. - return target_process->VMManager().UnmapRange(address, size); + return target_process.VMManager().UnmapRange(address, size); } VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { @@ -132,7 +132,11 @@ VMAPermission SharedMemory::ConvertPermissions(MemoryPermission permission) { return static_cast<VMAPermission>(masked_permissions); } -u8* SharedMemory::GetPointer(u32 offset) { +u8* SharedMemory::GetPointer(std::size_t offset) { + return backing_block->data() + backing_block_offset + offset; +} + +const u8* SharedMemory::GetPointer(std::size_t offset) const { return backing_block->data() + backing_block_offset + offset; } diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 2c06bb7ce..0b48db699 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -64,7 +64,7 @@ public: */ static SharedPtr<SharedMemory> CreateForApplet(KernelCore& kernel, std::shared_ptr<std::vector<u8>> heap_block, - u32 offset, u32 size, + std::size_t offset, u64 size, MemoryPermission permissions, MemoryPermission other_permissions, std::string name = "Unknown Applet"); @@ -81,6 +81,11 @@ public: return HANDLE_TYPE; } + /// Gets the size of the underlying memory block in bytes. + u64 GetSize() const { + return size; + } + /** * Converts the specified MemoryPermission into the equivalent VMAPermission. * @param permission The MemoryPermission to convert. @@ -94,44 +99,51 @@ public: * @param permissions Memory block map permissions (specified by SVC field) * @param other_permissions Memory block map other permissions (specified by SVC field) */ - ResultCode Map(Process* target_process, VAddr address, MemoryPermission permissions, + ResultCode Map(Process& target_process, VAddr address, MemoryPermission permissions, MemoryPermission other_permissions); /** * Unmaps a shared memory block from the specified address in system memory - * @param target_process Process from which to umap the memory block. + * @param target_process Process from which to unmap the memory block. * @param address Address in system memory where the shared memory block is mapped * @return Result code of the unmap operation */ - ResultCode Unmap(Process* target_process, VAddr address); + ResultCode Unmap(Process& target_process, VAddr address); /** * Gets a pointer to the shared memory block * @param offset Offset from the start of the shared memory block to get pointer - * @return Pointer to the shared memory block from the specified offset + * @return A pointer to the shared memory block from the specified offset */ - u8* GetPointer(u32 offset = 0); + u8* GetPointer(std::size_t offset = 0); + + /** + * Gets a constant pointer to the shared memory block + * @param offset Offset from the start of the shared memory block to get pointer + * @return A constant pointer to the shared memory block from the specified offset + */ + const u8* GetPointer(std::size_t offset = 0) const; + +private: + explicit SharedMemory(KernelCore& kernel); + ~SharedMemory() override; - /// Process that created this shared memory block. - SharedPtr<Process> owner_process; - /// Address of shared memory block in the owner process if specified. - VAddr base_address; /// Backing memory for this shared memory block. std::shared_ptr<std::vector<u8>> backing_block; /// Offset into the backing block for this shared memory. - std::size_t backing_block_offset; + std::size_t backing_block_offset = 0; /// Size of the memory block. Page-aligned. - u64 size; + u64 size = 0; /// Permission restrictions applied to the process which created the block. - MemoryPermission permissions; + MemoryPermission permissions{}; /// Permission restrictions applied to other processes mapping the block. - MemoryPermission other_permissions; + MemoryPermission other_permissions{}; + /// Process that created this shared memory block. + SharedPtr<Process> owner_process; + /// Address of shared memory block in the owner process if specified. + VAddr base_address = 0; /// Name of shared memory object. std::string name; - -private: - explicit SharedMemory(KernelCore& kernel); - ~SharedMemory() override; }; } // namespace Kernel diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 75dbfc31d..b8b6b4d49 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -736,13 +736,6 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { const auto* const current_process = Core::CurrentProcess(); - // Note: The kernel uses the current process's resource limit instead of - // the one from the thread owner's resource limit. - const ResourceLimit& resource_limit = current_process->GetResourceLimit(); - if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { - return ERR_INVALID_THREAD_PRIORITY; - } - SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); if (!thread) { return ERR_INVALID_HANDLE; @@ -796,7 +789,7 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_MEMORY_RANGE; } - return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare); + return shared_memory->Map(*current_process, addr, permissions_type, MemoryPermission::DontCare); } static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { @@ -826,7 +819,7 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 return ERR_INVALID_MEMORY_RANGE; } - return shared_memory->Unmap(current_process, addr); + return shared_memory->Unmap(*current_process, addr); } /// Query process memory @@ -885,10 +878,6 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V } auto* const current_process = Core::CurrentProcess(); - const ResourceLimit& resource_limit = current_process->GetResourceLimit(); - if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { - return ERR_INVALID_THREAD_PRIORITY; - } if (processor_id == THREADPROCESSORID_DEFAULT) { // Set the target CPU to the one specified in the process' exheader. @@ -1194,9 +1183,39 @@ static ResultCode ResetSignal(Handle handle) { /// Creates a TransferMemory object static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, - permissions); - *handle = 0; + LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, + permissions); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr); + return ERR_INVALID_ADDRESS; + } + + if (!Common::Is4KBAligned(size) || size == 0) { + LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size); + return ERR_INVALID_ADDRESS; + } + + if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})", + addr, size); + return ERR_INVALID_ADDRESS_STATE; + } + + const auto perms = static_cast<MemoryPermission>(permissions); + if (perms != MemoryPermission::None && perms != MemoryPermission::Read && + perms != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})", + permissions); + return ERR_INVALID_MEMORY_PERMISSIONS; + } + + auto& kernel = Core::System::GetInstance().Kernel(); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const auto shared_mem_handle = SharedMemory::Create( + kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr); + + CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle)); return RESULT_SUCCESS; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3758ecae1..11181a0af 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -6,10 +6,14 @@ #include <cinttypes> #include <cstring> #include <stack> +#include "applets/applets.h" +#include "applets/software_keyboard.h" +#include "audio_core/audio_renderer.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" @@ -28,6 +32,13 @@ namespace Service::AM { +constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2}; +constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; + +enum class AppletId : u32 { + SoftwareKeyboard = 0x11, +}; + constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; struct LaunchParameters { @@ -481,6 +492,24 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& LOG_DEBUG(Service_AM, "called"); } +IStorage::IStorage(std::vector<u8> buffer) + : ServiceFramework("IStorage"), buffer(std::move(buffer)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IStorage::Open, "Open"}, + {1, nullptr, "OpenTransferStorage"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +IStorage::~IStorage() = default; + +const std::vector<u8>& IStorage::GetData() const { + return buffer; +} + void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; @@ -500,15 +529,30 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); } -class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: - explicit IStorageAccessor(std::vector<u8> buffer) - : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet) + : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)) { // clang-format off static const FunctionInfo functions[] = { - {0, &IStorageAccessor::GetSize, "GetSize"}, - {10, &IStorageAccessor::Write, "Write"}, - {11, &IStorageAccessor::Read, "Read"}, + {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, + {1, &ILibraryAppletAccessor::IsCompleted, "IsCompleted"}, + {10, &ILibraryAppletAccessor::Start, "Start"}, + {20, nullptr, "RequestExit"}, + {25, nullptr, "Terminate"}, + {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, + {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, + {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, + {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, + {102, nullptr, "PushExtraStorage"}, + {103, &ILibraryAppletAccessor::PushInteractiveInData, "PushInteractiveInData"}, + {104, &ILibraryAppletAccessor::PopInteractiveOutData, "PopInteractiveOutData"}, + {105, &ILibraryAppletAccessor::GetPopOutDataEvent, "GetPopOutDataEvent"}, + {106, &ILibraryAppletAccessor::GetPopInteractiveOutDataEvent, "GetPopInteractiveOutDataEvent"}, + {110, nullptr, "NeedsToExitProcess"}, + {120, nullptr, "GetLibraryAppletInfo"}, + {150, nullptr, "RequestForAppletToGetForeground"}, + {160, nullptr, "GetIndirectLayerConsumerHandle"}, }; // clang-format on @@ -516,158 +560,187 @@ public: } private: - std::vector<u8> buffer; + void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { + const auto event = applet->GetBroker().GetStateChangedEvent(); + event->Signal(); - void GetSize(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + + LOG_DEBUG(Service_AM, "called"); + } + void IsCompleted(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u64>(buffer.size())); + rb.Push<u32>(applet->TransactionComplete()); LOG_DEBUG(Service_AM, "called"); } - void Write(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; + void GetResult(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(applet->GetStatus()); - const u64 offset{rp.Pop<u64>()}; - const std::vector<u8> data{ctx.ReadBuffer()}; + LOG_DEBUG(Service_AM, "called"); + } - ASSERT(offset + data.size() <= buffer.size()); + void Start(Kernel::HLERequestContext& ctx) { + ASSERT(applet != nullptr); - std::memcpy(&buffer[offset], data.data(), data.size()); + applet->Initialize(); + applet->Execute(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called, offset={}", offset); + LOG_DEBUG(Service_AM, "called"); } - void Read(Kernel::HLERequestContext& ctx) { + void PushInData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + applet->GetBroker().PushNormalDataFromGame(*rp.PopIpcInterface<IStorage>()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_AM, "called"); + } + + void PopOutData(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - const u64 offset{rp.Pop<u64>()}; - const std::size_t size{ctx.GetWriteBufferSize()}; + const auto storage = applet->GetBroker().PopNormalDataToGame(); + if (storage == nullptr) { + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } - ASSERT(offset + size <= buffer.size()); + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(*storage)); + + LOG_DEBUG(Service_AM, "called"); + } + + void PushInteractiveInData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + applet->GetBroker().PushInteractiveDataFromGame(*rp.PopIpcInterface<IStorage>()); - ctx.WriteBuffer(buffer.data() + offset, size); + ASSERT(applet->IsInitialized()); + applet->ExecuteInteractive(); + applet->Execute(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called, offset={}", offset); + LOG_DEBUG(Service_AM, "called"); } -}; -class IStorage final : public ServiceFramework<IStorage> { -public: - explicit IStorage(std::vector<u8> buffer) - : ServiceFramework("IStorage"), buffer(std::move(buffer)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IStorage::Open, "Open"}, - {1, nullptr, "OpenTransferStorage"}, - }; - // clang-format on + void PopInteractiveOutData(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - RegisterHandlers(functions); + const auto storage = applet->GetBroker().PopInteractiveDataToGame(); + if (storage == nullptr) { + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(*storage)); + + LOG_DEBUG(Service_AM, "called"); } -private: - std::vector<u8> buffer; + void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(applet->GetBroker().GetNormalDataEvent()); - void Open(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + LOG_DEBUG(Service_AM, "called"); + } + void GetPopInteractiveOutDataEvent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + rb.PushCopyObjects(applet->GetBroker().GetInteractiveDataEvent()); LOG_DEBUG(Service_AM, "called"); } + + std::shared_ptr<Applets::Applet> applet; }; -class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { -public: - explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { - // clang-format off +void IStorage::Open(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorageAccessor>(*this); + + LOG_DEBUG(Service_AM, "called"); +} + +IStorageAccessor::IStorageAccessor(IStorage& storage) + : ServiceFramework("IStorageAccessor"), backing(storage) { + // clang-format off static const FunctionInfo functions[] = { - {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, - {1, nullptr, "IsCompleted"}, - {10, &ILibraryAppletAccessor::Start, "Start"}, - {20, nullptr, "RequestExit"}, - {25, nullptr, "Terminate"}, - {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, - {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, - {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, - {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, - {102, nullptr, "PushExtraStorage"}, - {103, nullptr, "PushInteractiveInData"}, - {104, nullptr, "PopInteractiveOutData"}, - {105, nullptr, "GetPopOutDataEvent"}, - {106, nullptr, "GetPopInteractiveOutDataEvent"}, - {110, nullptr, "NeedsToExitProcess"}, - {120, nullptr, "GetLibraryAppletInfo"}, - {150, nullptr, "RequestForAppletToGetForeground"}, - {160, nullptr, "GetIndirectLayerConsumerHandle"}, + {0, &IStorageAccessor::GetSize, "GetSize"}, + {10, &IStorageAccessor::Write, "Write"}, + {11, &IStorageAccessor::Read, "Read"}, }; - // clang-format on + // clang-format on - RegisterHandlers(functions); + RegisterHandlers(functions); +} - auto& kernel = Core::System::GetInstance().Kernel(); - state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "ILibraryAppletAccessor:StateChangedEvent"); - } +IStorageAccessor::~IStorageAccessor() = default; -private: - void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { - state_changed_event->Signal(); +void IStorageAccessor::GetSize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(state_changed_event); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(backing.buffer.size())); - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + LOG_DEBUG(Service_AM, "called"); +} - void GetResult(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); +void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + const u64 offset{rp.Pop<u64>()}; + const std::vector<u8> data{ctx.ReadBuffer()}; - void Start(Kernel::HLERequestContext& ctx) { + if (data.size() > backing.buffer.size() - offset) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - void PushInData(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - storage_stack.push(rp.PopIpcInterface<AM::IStorage>()); + std::memcpy(backing.buffer.data() + offset, data.data(), data.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called"); - } + LOG_DEBUG(Service_AM, "called, offset={}", offset); +} - void PopOutData(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top())); +void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - storage_stack.pop(); + const u64 offset{rp.Pop<u64>()}; + const std::size_t size{ctx.GetWriteBufferSize()}; - LOG_DEBUG(Service_AM, "called"); + if (size > backing.buffer.size() - offset) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - std::stack<std::shared_ptr<AM::IStorage>> storage_stack; - Kernel::SharedPtr<Kernel::Event> state_changed_event; -}; + ctx.WriteBuffer(backing.buffer.data() + offset, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_AM, "called, offset={}", offset); +} ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { static const FunctionInfo functions[] = { @@ -675,7 +748,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, - {11, nullptr, "CreateTransferMemoryStorage"}, + {11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"}, {12, nullptr, "CreateHandleStorage"}, }; RegisterHandlers(functions); @@ -683,11 +756,36 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple ILibraryAppletCreator::~ILibraryAppletCreator() = default; +static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { + switch (id) { + case AppletId::SoftwareKeyboard: + return std::make_shared<Applets::SoftwareKeyboard>(); + default: + UNREACHABLE_MSG("Unimplemented AppletId [{:08X}]!", static_cast<u32>(id)); + return nullptr; + } +} + void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_id = rp.PopRaw<AppletId>(); + const auto applet_mode = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", + static_cast<u32>(applet_id), applet_mode); + + const auto applet = GetAppletFromId(applet_id); + + if (applet == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::ILibraryAppletAccessor>(); + rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet); LOG_DEBUG(Service_AM, "called"); } @@ -704,6 +802,31 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called, size={}", size); } +void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + rp.SetCurrentOffset(3); + const auto handle{rp.Pop<Kernel::Handle>()}; + + const auto shared_mem = + Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::SharedMemory>( + handle); + + if (shared_mem == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + + const u8* mem_begin = shared_mem->GetPointer(); + const u8* mem_end = mem_begin + shared_mem->GetSize(); + std::vector<u8> memory{mem_begin, mem_end}; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); +} + IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { // clang-format off static const FunctionInfo functions[] = { diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 5a3fcba8f..44c1bcde5 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -155,6 +155,34 @@ private: std::shared_ptr<AppletMessageQueue> msg_queue; }; +class IStorage final : public ServiceFramework<IStorage> { +public: + explicit IStorage(std::vector<u8> buffer); + ~IStorage() override; + + const std::vector<u8>& GetData() const; + +private: + void Open(Kernel::HLERequestContext& ctx); + + std::vector<u8> buffer; + + friend class IStorageAccessor; +}; + +class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +public: + explicit IStorageAccessor(IStorage& backing); + ~IStorageAccessor() override; + +private: + void GetSize(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); + void Read(Kernel::HLERequestContext& ctx); + + IStorage& backing; +}; + class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: ILibraryAppletCreator(); @@ -163,6 +191,7 @@ public: private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); + void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp new file mode 100644 index 000000000..becbadd06 --- /dev/null +++ b/src/core/hle/service/am/applets/applets.cpp @@ -0,0 +1,113 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/server_port.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +AppletDataBroker::AppletDataBroker() { + auto& kernel = Core::System::GetInstance().Kernel(); + state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "ILibraryAppletAccessor:StateChangedEvent"); + pop_out_data_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "ILibraryAppletAccessor:PopDataOutEvent"); + pop_interactive_out_data_event = Kernel::Event::Create( + kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); +} + +AppletDataBroker::~AppletDataBroker() = default; + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { + if (out_channel.empty()) + return nullptr; + + auto out = std::move(out_channel.front()); + out_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { + if (in_channel.empty()) + return nullptr; + + auto out = std::move(in_channel.front()); + in_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { + if (out_interactive_channel.empty()) + return nullptr; + + auto out = std::move(out_interactive_channel.front()); + out_interactive_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { + if (in_interactive_channel.empty()) + return nullptr; + + auto out = std::move(in_interactive_channel.front()); + in_interactive_channel.pop(); + return out; +} + +void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { + in_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { + out_channel.push(std::make_unique<IStorage>(storage)); + pop_out_data_event->Signal(); +} + +void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { + in_interactive_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { + out_interactive_channel.push(std::make_unique<IStorage>(storage)); + pop_interactive_out_data_event->Signal(); +} + +void AppletDataBroker::SignalStateChanged() const { + state_changed_event->Signal(); +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetNormalDataEvent() const { + return pop_out_data_event; +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetInteractiveDataEvent() const { + return pop_interactive_out_data_event; +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetStateChangedEvent() const { + return state_changed_event; +} + +Applet::Applet() = default; + +Applet::~Applet() = default; + +void Applet::Initialize() { + const auto common = broker.PopNormalDataToApplet(); + ASSERT(common != nullptr); + + const auto common_data = common->GetData(); + + ASSERT(common_data.size() >= sizeof(CommonArguments)); + std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments)); + + initialized = true; +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h new file mode 100644 index 000000000..f65ea119c --- /dev/null +++ b/src/core/hle/service/am/applets/applets.h @@ -0,0 +1,112 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <queue> +#include "common/swap.h" +#include "core/hle/kernel/kernel.h" + +union ResultCode; + +namespace Kernel { +class Event; +} + +namespace Service::AM { + +class IStorage; + +namespace Applets { + +class AppletDataBroker final { +public: + AppletDataBroker(); + ~AppletDataBroker(); + + std::unique_ptr<IStorage> PopNormalDataToGame(); + std::unique_ptr<IStorage> PopNormalDataToApplet(); + + std::unique_ptr<IStorage> PopInteractiveDataToGame(); + std::unique_ptr<IStorage> PopInteractiveDataToApplet(); + + void PushNormalDataFromGame(IStorage storage); + void PushNormalDataFromApplet(IStorage storage); + + void PushInteractiveDataFromGame(IStorage storage); + void PushInteractiveDataFromApplet(IStorage storage); + + void SignalStateChanged() const; + + Kernel::SharedPtr<Kernel::Event> GetNormalDataEvent() const; + Kernel::SharedPtr<Kernel::Event> GetInteractiveDataEvent() const; + Kernel::SharedPtr<Kernel::Event> GetStateChangedEvent() const; + +private: + // Queues are named from applet's perspective + + // PopNormalDataToApplet and PushNormalDataFromGame + std::queue<std::unique_ptr<IStorage>> in_channel; + + // PopNormalDataToGame and PushNormalDataFromApplet + std::queue<std::unique_ptr<IStorage>> out_channel; + + // PopInteractiveDataToApplet and PushInteractiveDataFromGame + std::queue<std::unique_ptr<IStorage>> in_interactive_channel; + + // PopInteractiveDataToGame and PushInteractiveDataFromApplet + std::queue<std::unique_ptr<IStorage>> out_interactive_channel; + + Kernel::SharedPtr<Kernel::Event> state_changed_event; + + // Signaled on PushNormalDataFromApplet + Kernel::SharedPtr<Kernel::Event> pop_out_data_event; + + // Signaled on PushInteractiveDataFromApplet + Kernel::SharedPtr<Kernel::Event> pop_interactive_out_data_event; +}; + +class Applet { +public: + Applet(); + virtual ~Applet(); + + virtual void Initialize(); + + virtual bool TransactionComplete() const = 0; + virtual ResultCode GetStatus() const = 0; + virtual void ExecuteInteractive() = 0; + virtual void Execute() = 0; + + bool IsInitialized() const { + return initialized; + } + + AppletDataBroker& GetBroker() { + return broker; + } + + const AppletDataBroker& GetBroker() const { + return broker; + } + +protected: + struct CommonArguments { + u32_le arguments_version; + u32_le size; + u32_le library_version; + u32_le theme_color; + u8 play_startup_sound; + u64_le system_tick; + }; + static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); + + CommonArguments common_args{}; + AppletDataBroker broker; + bool initialized = false; +}; + +} // namespace Applets +} // namespace Service::AM diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp new file mode 100644 index 000000000..981bdec51 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -0,0 +1,161 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/software_keyboard.h" + +namespace Service::AM::Applets { + +constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; +constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; +constexpr std::size_t DEFAULT_MAX_LENGTH = 500; +constexpr bool INTERACTIVE_STATUS_OK = false; + +static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( + KeyboardConfig config, std::u16string initial_text) { + Core::Frontend::SoftwareKeyboardParameters params{}; + + params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.submit_text.data(), config.submit_text.size()); + params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.header_text.data(), config.header_text.size()); + params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), + config.sub_text.size()); + params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), + config.guide_text.size()); + params.initial_text = initial_text; + params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; + params.password = static_cast<bool>(config.is_password); + params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position); + params.value = static_cast<u8>(config.keyset_disable_bitmask); + + return params; +} + +SoftwareKeyboard::SoftwareKeyboard() = default; + +SoftwareKeyboard::~SoftwareKeyboard() = default; + +void SoftwareKeyboard::Initialize() { + complete = false; + initial_text.clear(); + final_data.clear(); + + Applet::Initialize(); + + const auto keyboard_config_storage = broker.PopNormalDataToApplet(); + ASSERT(keyboard_config_storage != nullptr); + const auto& keyboard_config = keyboard_config_storage->GetData(); + + ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); + std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); + + const auto work_buffer_storage = broker.PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + const auto& work_buffer = work_buffer_storage->GetData(); + + if (config.initial_string_size == 0) + return; + + std::vector<char16_t> string(config.initial_string_size); + std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, + string.size() * 2); + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); +} + +bool SoftwareKeyboard::TransactionComplete() const { + return complete; +} + +ResultCode SoftwareKeyboard::GetStatus() const { + return RESULT_SUCCESS; +} + +void SoftwareKeyboard::ExecuteInteractive() { + if (complete) + return; + + const auto storage = broker.PopInteractiveDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + const auto status = static_cast<bool>(data[0]); + + if (status == INTERACTIVE_STATUS_OK) { + complete = true; + } else { + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; + std::memcpy(string.data(), data.data() + 4, string.size() * 2); + frontend.SendTextCheckDialog( + Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), + [this] { broker.SignalStateChanged(); }); + } +} + +void SoftwareKeyboard::Execute() { + if (complete) { + broker.PushNormalDataFromApplet(IStorage{final_data}); + return; + } + + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + const auto parameters = ConvertToFrontendParameters(config, initial_text); + + frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); }, + parameters); +} + +void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { + std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE); + + if (text.has_value()) { + std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE); + + if (config.utf_8) { + const u64 size = text->size() + 8; + const auto new_text = Common::UTF16ToUTF8(*text); + + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } else { + const u64 size = text->size() * 2 + 8; + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } + + complete = !config.text_check; + final_data = output_main; + + if (complete) { + broker.PushNormalDataFromApplet(IStorage{output_main}); + } else { + broker.PushInteractiveDataFromApplet(IStorage{output_sub}); + } + + broker.SignalStateChanged(); + } else { + output_main[0] = 1; + complete = true; + broker.PushNormalDataFromApplet(IStorage{output_main}); + broker.SignalStateChanged(); + } +} +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h new file mode 100644 index 000000000..efd5753a1 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -0,0 +1,74 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> + +#include "common/common_funcs.h" +#include "common/swap.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +enum class KeysetDisable : u32 { + Space = 0x02, + Address = 0x04, + Percent = 0x08, + Slashes = 0x10, + Numbers = 0x40, + DownloadCode = 0x80, +}; + +struct KeyboardConfig { + INSERT_PADDING_BYTES(4); + std::array<char16_t, 9> submit_text; + u16_le left_symbol_key; + u16_le right_symbol_key; + INSERT_PADDING_BYTES(1); + KeysetDisable keyset_disable_bitmask; + u32_le initial_cursor_position; + std::array<char16_t, 65> header_text; + std::array<char16_t, 129> sub_text; + std::array<char16_t, 257> guide_text; + u32_le length_limit; + INSERT_PADDING_BYTES(4); + u32_le is_password; + INSERT_PADDING_BYTES(5); + bool utf_8; + bool draw_background; + u32_le initial_string_offset; + u32_le initial_string_size; + u32_le user_dictionary_offset; + u32_le user_dictionary_size; + bool text_check; + u64_le text_check_callback; +}; +static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); + +class SoftwareKeyboard final : public Applet { +public: + SoftwareKeyboard(); + ~SoftwareKeyboard() override; + + void Initialize() override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void WriteText(std::optional<std::u16string> text); + +private: + KeyboardConfig config; + std::u16string initial_text; + bool complete = false; + std::vector<u8> final_data; +}; + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp index 3d100763f..e76c83aee 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.cpp +++ b/src/core/hle/service/hid/controllers/debug_pad.cpp @@ -6,9 +6,14 @@ #include "common/common_types.h" #include "core/core_timing.h" #include "core/hle/service/hid/controllers/debug_pad.h" +#include "core/settings.h" namespace Service::HID { +constexpr s32 HID_JOYSTICK_MAX = 0x7fff; +constexpr s32 HID_JOYSTICK_MIN = -0x7fff; +enum class JoystickId : std::size_t { Joystick_Left, Joystick_Right }; + Controller_DebugPad::Controller_DebugPad() = default; Controller_DebugPad::~Controller_DebugPad() = default; @@ -33,10 +38,43 @@ void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update debug pad states + cur_entry.attribute.connected.Assign(1); + auto& pad = cur_entry.pad_state; + + using namespace Settings::NativeButton; + pad.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); + pad.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); + pad.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); + pad.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); + pad.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); + pad.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + analogs[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + cur_entry.l_stick.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + cur_entry.l_stick.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + cur_entry.r_stick.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); std::memcpy(data, &shared_memory, sizeof(SharedMemory)); } -void Controller_DebugPad::OnLoadInputDevices() {} +void Controller_DebugPad::OnLoadInputDevices() { + std::transform(Settings::values.debug_pad_buttons.begin(), + Settings::values.debug_pad_buttons.end(), buttons.begin(), + Input::CreateDevice<Input::ButtonDevice>); + std::transform(Settings::values.debug_pad_analogs.begin(), + Settings::values.debug_pad_analogs.end(), analogs.begin(), + Input::CreateDevice<Input::AnalogDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h index 62b4f2682..68b734248 100644 --- a/src/core/hle/service/hid/controllers/debug_pad.h +++ b/src/core/hle/service/hid/controllers/debug_pad.h @@ -5,10 +5,13 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_DebugPad final : public ControllerBase { @@ -35,11 +38,40 @@ private: }; static_assert(sizeof(AnalogStick) == 0x8); + struct PadState { + union { + u32_le raw{}; + BitField<0, 1, u32_le> a; + BitField<1, 1, u32_le> b; + BitField<2, 1, u32_le> x; + BitField<3, 1, u32_le> y; + BitField<4, 1, u32_le> l; + BitField<5, 1, u32_le> r; + BitField<6, 1, u32_le> zl; + BitField<7, 1, u32_le> zr; + BitField<8, 1, u32_le> plus; + BitField<9, 1, u32_le> minus; + BitField<10, 1, u32_le> d_left; + BitField<11, 1, u32_le> d_up; + BitField<12, 1, u32_le> d_right; + BitField<13, 1, u32_le> d_down; + }; + }; + static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size"); + + struct Attributes { + union { + u32_le raw{}; + BitField<0, 1, u32_le> connected; + }; + }; + static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + struct PadStates { s64_le sampling_number; s64_le sampling_number2; - u32_le attribute; - u32_le button_state; + Attributes attribute; + PadState pad_state; AnalogStick r_stick; AnalogStick l_stick; }; @@ -52,5 +84,10 @@ private: }; static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; + + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + buttons; + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> + analogs; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp index ccfbce9ac..ca75adc2b 100644 --- a/src/core/hle/service/hid/controllers/keyboard.cpp +++ b/src/core/hle/service/hid/controllers/keyboard.cpp @@ -6,9 +6,11 @@ #include "common/common_types.h" #include "core/core_timing.h" #include "core/hle/service/hid/controllers/keyboard.h" +#include "core/settings.h" namespace Service::HID { constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800; +constexpr u8 KEYS_PER_BYTE = 8; Controller_Keyboard::Controller_Keyboard() = default; Controller_Keyboard::~Controller_Keyboard() = default; @@ -34,10 +36,24 @@ void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update keyboard states + + for (std::size_t i = 0; i < keyboard_keys.size(); ++i) { + for (std::size_t k = 0; k < KEYS_PER_BYTE; ++k) { + cur_entry.key[i / KEYS_PER_BYTE] |= (keyboard_keys[i]->GetStatus() << k); + } + } + + for (std::size_t i = 0; i < keyboard_mods.size(); ++i) { + cur_entry.modifier |= (keyboard_mods[i]->GetStatus() << i); + } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } -void Controller_Keyboard::OnLoadInputDevices() {} +void Controller_Keyboard::OnLoadInputDevices() { + std::transform(Settings::values.keyboard_keys.begin(), Settings::values.keyboard_keys.end(), + keyboard_keys.begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(Settings::values.keyboard_mods.begin(), Settings::values.keyboard_mods.end(), + keyboard_mods.begin(), Input::CreateDevice<Input::ButtonDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h index 493e68fce..f52775456 100644 --- a/src/core/hle/service/hid/controllers/keyboard.h +++ b/src/core/hle/service/hid/controllers/keyboard.h @@ -8,7 +8,9 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_Keyboard final : public ControllerBase { @@ -46,5 +48,10 @@ private: }; static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size"); SharedMemory shared_memory{}; + + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardKeys> + keyboard_keys; + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeKeyboard::NumKeyboardMods> + keyboard_mods; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp index 4e246a57d..63391dbe9 100644 --- a/src/core/hle/service/hid/controllers/mouse.cpp +++ b/src/core/hle/service/hid/controllers/mouse.cpp @@ -5,6 +5,7 @@ #include <cstring> #include "common/common_types.h" #include "core/core_timing.h" +#include "core/frontend/emu_window.h" #include "core/hle/service/hid/controllers/mouse.h" namespace Service::HID { @@ -14,7 +15,6 @@ Controller_Mouse::Controller_Mouse() = default; Controller_Mouse::~Controller_Mouse() = default; void Controller_Mouse::OnInit() {} - void Controller_Mouse::OnRelease() {} void Controller_Mouse::OnUpdate(u8* data, std::size_t size) { @@ -34,10 +34,29 @@ void Controller_Mouse::OnUpdate(u8* data, std::size_t size) { cur_entry.sampling_number = last_entry.sampling_number + 1; cur_entry.sampling_number2 = cur_entry.sampling_number; - // TODO(ogniK): Update mouse states + + if (Settings::values.mouse_enabled) { + const auto [px, py, sx, sy] = mouse_device->GetStatus(); + const auto x = static_cast<s32>(px * Layout::ScreenUndocked::Width); + const auto y = static_cast<s32>(py * Layout::ScreenUndocked::Height); + cur_entry.x = x; + cur_entry.y = y; + cur_entry.delta_x = x - last_entry.x; + cur_entry.delta_y = y - last_entry.y; + cur_entry.mouse_wheel_x = sx; + cur_entry.mouse_wheel_y = sy; + + for (std::size_t i = 0; i < mouse_button_devices.size(); ++i) { + cur_entry.button |= (mouse_button_devices[i]->GetStatus() << i); + } + } std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory)); } -void Controller_Mouse::OnLoadInputDevices() {} +void Controller_Mouse::OnLoadInputDevices() { + mouse_device = Input::CreateDevice<Input::MouseDevice>(Settings::values.mouse_device); + std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), + mouse_button_devices.begin(), Input::CreateDevice<Input::ButtonDevice>); +} } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h index 543b0b71f..70b654d07 100644 --- a/src/core/hle/service/hid/controllers/mouse.h +++ b/src/core/hle/service/hid/controllers/mouse.h @@ -7,7 +7,9 @@ #include <array> #include "common/common_types.h" #include "common/swap.h" +#include "core/frontend/input.h" #include "core/hle/service/hid/controllers/controller_base.h" +#include "core/settings.h" namespace Service::HID { class Controller_Mouse final : public ControllerBase { @@ -35,7 +37,8 @@ private: s32_le y; s32_le delta_x; s32_le delta_y; - s32_le mouse_wheel; + s32_le mouse_wheel_x; + s32_le mouse_wheel_y; s32_le button; s32_le attribute; }; @@ -46,5 +49,9 @@ private: std::array<MouseState, 17> mouse_states; }; SharedMemory shared_memory{}; + + std::unique_ptr<Input::MouseDevice> mouse_device; + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeMouseButton::NumMouseButtons> + mouse_button_devices; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 205e4fd14..46604887c 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -17,22 +17,13 @@ #include "core/settings.h" namespace Service::HID { - -constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; -constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; -constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; -constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; -constexpr u32 NPAD_HANDHELD = 32; -constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? constexpr u32 MAX_NPAD_ID = 7; -constexpr Controller_NPad::NPadControllerType PREFERRED_CONTROLLER = - Controller_NPad::NPadControllerType::JoyDual; constexpr std::array<u32, 10> npad_id_list{ - 0, 1, 2, 3, 4, 5, 6, 7, 32, 16, + 0, 1, 2, 3, 4, 5, 6, 7, NPAD_HANDHELD, NPAD_UNKNOWN, }; enum class JoystickId : std::size_t { @@ -40,7 +31,23 @@ enum class JoystickId : std::size_t { Joystick_Right, }; -static std::size_t NPadIdToIndex(u32 npad_id) { +static Controller_NPad::NPadControllerType MapSettingsTypeToNPad(Settings::ControllerType type) { + switch (type) { + case Settings::ControllerType::ProController: + return Controller_NPad::NPadControllerType::ProController; + case Settings::ControllerType::DualJoycon: + return Controller_NPad::NPadControllerType::JoyDual; + case Settings::ControllerType::LeftJoycon: + return Controller_NPad::NPadControllerType::JoyLeft; + case Settings::ControllerType::RightJoycon: + return Controller_NPad::NPadControllerType::JoyRight; + default: + UNREACHABLE(); + return Controller_NPad::NPadControllerType::JoyDual; + } +} + +std::size_t Controller_NPad::NPadIdToIndex(u32 npad_id) { switch (npad_id) { case 0: case 1: @@ -63,6 +70,27 @@ static std::size_t NPadIdToIndex(u32 npad_id) { } } +u32 Controller_NPad::IndexToNPad(std::size_t index) { + switch (index) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + return static_cast<u32>(index); + case 8: + return NPAD_HANDHELD; + case 9: + return NPAD_UNKNOWN; + default: + UNIMPLEMENTED_MSG("Unknown npad index {}", index); + return 0; + }; +} + Controller_NPad::Controller_NPad() = default; Controller_NPad::~Controller_NPad() = default; @@ -79,22 +107,32 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.joy_styles.handheld.Assign(1); controller.device_type.handheld.Assign(1); controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); break; case NPadControllerType::JoyDual: controller.joy_styles.joycon_dual.Assign(1); controller.device_type.joycon_left.Assign(1); controller.device_type.joycon_right.Assign(1); + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); controller.pad_assignment = NPadAssignments::Dual; break; case NPadControllerType::JoyLeft: controller.joy_styles.joycon_left.Assign(1); controller.device_type.joycon_left.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_horizontal.Assign(1); + controller.properties.use_minus.Assign(1); + controller.pad_assignment = NPadAssignments::Single; break; case NPadControllerType::JoyRight: controller.joy_styles.joycon_right.Assign(1); controller.device_type.joycon_right.Assign(1); - controller.pad_assignment = NPadAssignments::Dual; + controller.properties.is_horizontal.Assign(1); + controller.properties.use_plus.Assign(1); + controller.pad_assignment = NPadAssignments::Single; break; case NPadControllerType::Pokeball: controller.joy_styles.pokeball.Assign(1); @@ -104,6 +142,9 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { case NPadControllerType::ProController: controller.joy_styles.pro_controller.Assign(1); controller.device_type.pro_controller.Assign(1); + controller.properties.is_vertical.Assign(1); + controller.properties.use_plus.Assign(1); + controller.properties.use_minus.Assign(1); controller.pad_assignment = NPadAssignments::Single; break; } @@ -113,14 +154,12 @@ void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) { controller.single_color.button_color = 0; controller.dual_color_error = ColorReadError::ReadOk; - controller.left_color.body_color = JOYCON_BODY_NEON_BLUE; - controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE; - controller.right_color.body_color = JOYCON_BODY_NEON_RED; - controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED; - - controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations - controller.properties.use_plus.Assign(1); - controller.properties.use_minus.Assign(1); + controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left; + controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left; + controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right; + controller.right_color.button_color = + Settings::values.players[controller_idx].button_color_right; + controller.battery_level[0] = BATTERY_FULL; controller.battery_level[1] = BATTERY_FULL; controller.battery_level[2] = BATTERY_FULL; @@ -144,26 +183,109 @@ void Controller_NPad::OnInit() { style.pro_controller.Assign(1); style.pokeball.Assign(1); } + + std::transform( + Settings::values.players.begin(), Settings::values.players.end(), + connected_controllers.begin(), [](const Settings::PlayerInput& player) { + return ControllerHolder{MapSettingsTypeToNPad(player.type), player.connected}; + }); + + std::stable_partition(connected_controllers.begin(), connected_controllers.begin() + 8, + [](const ControllerHolder& holder) { return holder.is_connected; }); + + // Account for handheld + if (connected_controllers[8].is_connected) + connected_controllers[8].type = NPadControllerType::Handheld; + + supported_npad_id_types.resize(npad_id_list.size()); + std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), + npad_id_list.size() * sizeof(u32)); + + // Add a default dual joycon controller if none are present. if (std::none_of(connected_controllers.begin(), connected_controllers.end(), [](const ControllerHolder& controller) { return controller.is_connected; })) { supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), npad_id_list.size() * sizeof(u32)); - AddNewController(PREFERRED_CONTROLLER); + AddNewController(NPadControllerType::JoyDual); + } + + for (std::size_t i = 0; i < connected_controllers.size(); ++i) { + const auto& controller = connected_controllers[i]; + if (controller.is_connected) { + AddNewControllerAt(controller.type, IndexToNPad(i)); + } } } void Controller_NPad::OnLoadInputDevices() { - std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, - Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END, - buttons.begin(), Input::CreateDevice<Input::ButtonDevice>); - std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, - Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END, - sticks.begin(), Input::CreateDevice<Input::AnalogDevice>); + const auto& players = Settings::values.players; + for (std::size_t i = 0; i < players.size(); ++i) { + std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN, + players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END, + buttons[i].begin(), Input::CreateDevice<Input::ButtonDevice>); + std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN, + players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END, + sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>); + } } void Controller_NPad::OnRelease() {} +void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { + const auto controller_idx = NPadIdToIndex(npad_id); + const auto controller_type = connected_controllers[controller_idx].type; + if (!connected_controllers[controller_idx].is_connected) { + return; + } + auto& pad_state = npad_pad_states[controller_idx].pad_states; + auto& lstick_entry = npad_pad_states[controller_idx].l_stick; + auto& rstick_entry = npad_pad_states[controller_idx].r_stick; + const auto& button_state = buttons[controller_idx]; + const auto& analog_state = sticks[controller_idx]; + + using namespace Settings::NativeButton; + pad_state.a.Assign(button_state[A - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.b.Assign(button_state[B - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.x.Assign(button_state[X - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.y.Assign(button_state[Y - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick.Assign(button_state[LStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick.Assign(button_state[RStick - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l.Assign(button_state[L - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r.Assign(button_state[R - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zl.Assign(button_state[ZL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.zr.Assign(button_state[ZR - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.plus.Assign(button_state[Plus - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.minus.Assign(button_state[Minus - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.d_left.Assign(button_state[DLeft - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_up.Assign(button_state[DUp - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_right.Assign(button_state[DRight - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.d_down.Assign(button_state[DDown - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.l_stick_left.Assign(button_state[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_up.Assign(button_state[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_right.Assign(button_state[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.l_stick_down.Assign(button_state[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.r_stick_left.Assign(button_state[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_up.Assign(button_state[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_right.Assign(button_state[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.r_stick_down.Assign(button_state[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); + + pad_state.left_sl.Assign(button_state[SL - BUTTON_HID_BEGIN]->GetStatus()); + pad_state.left_sr.Assign(button_state[SR - BUTTON_HID_BEGIN]->GetStatus()); + + const auto [stick_l_x_f, stick_l_y_f] = + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); + const auto [stick_r_x_f, stick_r_y_f] = + analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); + lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); + lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); + rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); + rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); +} + void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { if (!IsControllerActivated()) return; @@ -199,97 +321,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) { continue; } - - // Pad states - ControllerPadState pad_state{}; - using namespace Settings::NativeButton; - pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus()); - - pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus()); - pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus()); - - AnalogPosition lstick_entry{}; - AnalogPosition rstick_entry{}; - - const auto [stick_l_x_f, stick_l_y_f] = - sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus(); - const auto [stick_r_x_f, stick_r_y_f] = - sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus(); - lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX); - lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX); - rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); - rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); - - if (controller_type == NPadControllerType::JoyLeft || - controller_type == NPadControllerType::JoyRight) { - if (npad.properties.is_horizontal) { - ControllerPadState state{}; - AnalogPosition temp_lstick_entry{}; - AnalogPosition temp_rstick_entry{}; - if (controller_type == NPadControllerType::JoyLeft) { - state.d_down.Assign(pad_state.d_left.Value()); - state.d_left.Assign(pad_state.d_up.Value()); - state.d_right.Assign(pad_state.d_down.Value()); - state.d_up.Assign(pad_state.d_right.Value()); - state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); - state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); - - state.zl.Assign(pad_state.zl.Value()); - state.plus.Assign(pad_state.minus.Value()); - - temp_lstick_entry = lstick_entry; - temp_rstick_entry = rstick_entry; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_lstick_entry.y *= -1; - } else if (controller_type == NPadControllerType::JoyRight) { - state.x.Assign(pad_state.a.Value()); - state.a.Assign(pad_state.b.Value()); - state.b.Assign(pad_state.y.Value()); - state.y.Assign(pad_state.b.Value()); - - state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); - state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); - state.zr.Assign(pad_state.zr.Value()); - state.plus.Assign(pad_state.plus.Value()); - - temp_lstick_entry = lstick_entry; - temp_rstick_entry = rstick_entry; - std::swap(temp_lstick_entry.x, temp_lstick_entry.y); - std::swap(temp_rstick_entry.x, temp_rstick_entry.y); - temp_rstick_entry.x *= -1; - } - pad_state.raw = state.raw; - lstick_entry = temp_lstick_entry; - rstick_entry = temp_rstick_entry; - } - } + const u32 npad_index = static_cast<u32>(i); + RequestPadStateUpdate(npad_index); + auto& pad_state = npad_pad_states[npad_index]; auto& main_controller = npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; @@ -304,8 +338,51 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index]; if (hold_type == NpadHoldType::Horizontal) { - // TODO(ogniK): Remap buttons for different orientations + ControllerPadState state{}; + AnalogPosition temp_lstick_entry{}; + AnalogPosition temp_rstick_entry{}; + if (controller_type == NPadControllerType::JoyLeft) { + state.d_down.Assign(pad_state.pad_states.d_left.Value()); + state.d_left.Assign(pad_state.pad_states.d_up.Value()); + state.d_right.Assign(pad_state.pad_states.d_down.Value()); + state.d_up.Assign(pad_state.pad_states.d_right.Value()); + state.l.Assign(pad_state.pad_states.l.Value() | + pad_state.pad_states.left_sl.Value()); + state.r.Assign(pad_state.pad_states.r.Value() | + pad_state.pad_states.left_sr.Value()); + + state.zl.Assign(pad_state.pad_states.zl.Value()); + state.plus.Assign(pad_state.pad_states.minus.Value()); + + temp_lstick_entry = pad_state.l_stick; + temp_rstick_entry = pad_state.r_stick; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_lstick_entry.y *= -1; + } else if (controller_type == NPadControllerType::JoyRight) { + state.x.Assign(pad_state.pad_states.a.Value()); + state.a.Assign(pad_state.pad_states.b.Value()); + state.b.Assign(pad_state.pad_states.y.Value()); + state.y.Assign(pad_state.pad_states.b.Value()); + + state.l.Assign(pad_state.pad_states.l.Value() | + pad_state.pad_states.right_sl.Value()); + state.r.Assign(pad_state.pad_states.r.Value() | + pad_state.pad_states.right_sr.Value()); + state.zr.Assign(pad_state.pad_states.zr.Value()); + state.plus.Assign(pad_state.pad_states.plus.Value()); + + temp_lstick_entry = pad_state.l_stick; + temp_rstick_entry = pad_state.r_stick; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_rstick_entry.x *= -1; + } + pad_state.pad_states.raw = state.raw; + pad_state.l_stick = temp_lstick_entry; + pad_state.r_stick = temp_rstick_entry; } + libnx_entry.connection_status.raw = 0; switch (controller_type) { @@ -316,9 +393,9 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { handheld_entry.connection_status.IsRightJoyConnected.Assign(1); handheld_entry.connection_status.IsLeftJoyWired.Assign(1); handheld_entry.connection_status.IsRightJoyWired.Assign(1); - handheld_entry.pad_states.raw = pad_state.raw; - handheld_entry.l_stick = lstick_entry; - handheld_entry.r_stick = rstick_entry; + handheld_entry.pad.pad_states.raw = pad_state.pad_states.raw; + handheld_entry.pad.l_stick = pad_state.l_stick; + handheld_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyDual: dual_entry.connection_status.raw = 0; @@ -331,25 +408,25 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { libnx_entry.connection_status.IsRightJoyConnected.Assign(1); libnx_entry.connection_status.IsConnected.Assign(1); - dual_entry.pad_states.raw = pad_state.raw; - dual_entry.l_stick = lstick_entry; - dual_entry.r_stick = rstick_entry; + dual_entry.pad.pad_states.raw = pad_state.pad_states.raw; + dual_entry.pad.l_stick = pad_state.l_stick; + dual_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyLeft: left_entry.connection_status.raw = 0; left_entry.connection_status.IsConnected.Assign(1); - left_entry.pad_states.raw = pad_state.raw; - left_entry.l_stick = lstick_entry; - left_entry.r_stick = rstick_entry; + left_entry.pad.pad_states.raw = pad_state.pad_states.raw; + left_entry.pad.l_stick = pad_state.l_stick; + left_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::JoyRight: right_entry.connection_status.raw = 0; right_entry.connection_status.IsConnected.Assign(1); - right_entry.pad_states.raw = pad_state.raw; - right_entry.l_stick = lstick_entry; - right_entry.r_stick = rstick_entry; + right_entry.pad.pad_states.raw = pad_state.pad_states.raw; + right_entry.pad.l_stick = pad_state.l_stick; + right_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::Pokeball: pokeball_entry.connection_status.raw = 0; @@ -357,30 +434,30 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { pokeball_entry.connection_status.IsConnected.Assign(1); pokeball_entry.connection_status.IsWired.Assign(1); - pokeball_entry.pad_states.raw = pad_state.raw; - pokeball_entry.l_stick = lstick_entry; - pokeball_entry.r_stick = rstick_entry; + pokeball_entry.pad.pad_states.raw = pad_state.pad_states.raw; + pokeball_entry.pad.l_stick = pad_state.l_stick; + pokeball_entry.pad.r_stick = pad_state.r_stick; break; case NPadControllerType::ProController: main_controller.connection_status.raw = 0; main_controller.connection_status.IsConnected.Assign(1); main_controller.connection_status.IsWired.Assign(1); - main_controller.pad_states.raw = pad_state.raw; - main_controller.l_stick = lstick_entry; - main_controller.r_stick = rstick_entry; + main_controller.pad.pad_states.raw = pad_state.pad_states.raw; + main_controller.pad.l_stick = pad_state.l_stick; + main_controller.pad.r_stick = pad_state.r_stick; break; } // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate // any controllers. - libnx_entry.pad_states.raw = pad_state.raw; - libnx_entry.l_stick = lstick_entry; - libnx_entry.r_stick = rstick_entry; + libnx_entry.pad.pad_states.raw = pad_state.pad_states.raw; + libnx_entry.pad.l_stick = pad_state.l_stick; + libnx_entry.pad.r_stick = pad_state.r_stick; } std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(), shared_memory_entries.size() * sizeof(NPadEntry)); -} // namespace Service::HID +} void Controller_NPad::SetSupportedStyleSet(NPadType style_set) { style.raw = style_set.raw; @@ -401,23 +478,24 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { if (!controller.is_connected) { continue; } - if (!IsControllerSupported(PREFERRED_CONTROLLER)) { - const auto best_type = DecideBestController(PREFERRED_CONTROLLER); - const bool is_handheld = (best_type == NPadControllerType::Handheld || - PREFERRED_CONTROLLER == NPadControllerType::Handheld); + const auto requested_controller = + i <= MAX_NPAD_ID ? MapSettingsTypeToNPad(Settings::values.players[i].type) + : NPadControllerType::Handheld; + if (!IsControllerSupported(requested_controller)) { + const auto is_handheld = requested_controller == NPadControllerType::Handheld; if (is_handheld) { controller.type = NPadControllerType::None; controller.is_connected = false; - AddNewController(best_type); + AddNewController(requested_controller); } else { - controller.type = best_type; + controller.type = requested_controller; InitNewlyAddedControler(i); } had_controller_update = true; } - } - if (had_controller_update) { - styleset_changed_event->Signal(); + if (had_controller_update) { + styleset_changed_event->Signal(); + } } } @@ -450,15 +528,7 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, return; } for (std::size_t i = 0; i < controller_ids.size(); i++) { - std::size_t controller_pos = i; - // Handheld controller conversion - if (controller_pos == NPAD_HANDHELD) { - controller_pos = 8; - } - // Unknown controller conversion - if (controller_pos == NPAD_UNKNOWN) { - controller_pos = 9; - } + std::size_t controller_pos = NPadIdToIndex(static_cast<u32>(i)); if (connected_controllers[controller_pos].is_connected) { // TODO(ogniK): Vibrate the physical controller } @@ -477,7 +547,9 @@ Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() cons Controller_NPad::Vibration Controller_NPad::GetLastVibration() const { return last_processed_vibration; } + void Controller_NPad::AddNewController(NPadControllerType controller) { + controller = DecideBestController(controller); if (controller == NPadControllerType::Handheld) { connected_controllers[8] = {controller, true}; InitNewlyAddedControler(8); @@ -495,6 +567,18 @@ void Controller_NPad::AddNewController(NPadControllerType controller) { InitNewlyAddedControler(controller_id); } +void Controller_NPad::AddNewControllerAt(NPadControllerType controller, u32 npad_id) { + controller = DecideBestController(controller); + if (controller == NPadControllerType::Handheld) { + connected_controllers[NPadIdToIndex(NPAD_HANDHELD)] = {controller, true}; + InitNewlyAddedControler(NPadIdToIndex(NPAD_HANDHELD)); + return; + } + + connected_controllers[npad_id] = {controller, true}; + InitNewlyAddedControler(npad_id); +} + void Controller_NPad::ConnectNPad(u32 npad_id) { connected_controllers[NPadIdToIndex(npad_id)].is_connected = true; } @@ -503,6 +587,36 @@ void Controller_NPad::DisconnectNPad(u32 npad_id) { connected_controllers[NPadIdToIndex(npad_id)].is_connected = false; } +bool Controller_NPad::IsControllerSupported(NPadControllerType controller) { + if (controller == NPadControllerType::Handheld) { + // Handheld is not even a supported type, lets stop here + if (std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), + NPAD_HANDHELD) == supported_npad_id_types.end()) { + return false; + } + // Handheld should not be supported in docked mode + if (Settings::values.use_docked_mode) { + return false; + } + } + switch (controller) { + case NPadControllerType::ProController: + return style.pro_controller; + case NPadControllerType::Handheld: + return style.handheld; + case NPadControllerType::JoyDual: + return style.joycon_dual; + case NPadControllerType::JoyLeft: + return style.joycon_left; + case NPadControllerType::JoyRight: + return style.joycon_right; + case NPadControllerType::Pokeball: + return style.pokeball; + default: + return false; + } +} + Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) { // These are controllers without led patterns @@ -534,6 +648,36 @@ void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { can_controllers_vibrate = can_vibrate; } +void Controller_NPad::ClearAllConnectedControllers() { + for (auto& controller : connected_controllers) { + if (controller.is_connected && controller.type != NPadControllerType::None) { + controller.type = NPadControllerType::None; + controller.is_connected = false; + } + } +} +void Controller_NPad::DisconnectAllConnectedControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { controller.is_connected = false; }); +} + +void Controller_NPad::ConnectAllDisconnectedControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { + if (controller.type != NPadControllerType::None && !controller.is_connected) { + controller.is_connected = false; + } + }); +} + +void Controller_NPad::ClearAllControllers() { + std::for_each(connected_controllers.begin(), connected_controllers.end(), + [](ControllerHolder& controller) { + controller.type = NPadControllerType::None; + controller.is_connected = false; + }); +} + bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { const bool support_handheld = std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) != diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index ac86985ff..ea8057b80 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -5,13 +5,18 @@ #pragma once #include <array> +#include "common/bit_field.h" #include "common/common_types.h" #include "core/frontend/input.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/hid/controllers/controller_base.h" #include "core/settings.h" namespace Service::HID { +constexpr u32 NPAD_HANDHELD = 32; +constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? + class Controller_NPad final : public ControllerBase { public: Controller_NPad(); @@ -107,11 +112,19 @@ public: Vibration GetLastVibration() const; void AddNewController(NPadControllerType controller); + void AddNewControllerAt(NPadControllerType controller, u32 npad_id); void ConnectNPad(u32 npad_id); void DisconnectNPad(u32 npad_id); LedPattern GetLedPattern(u32 npad_id); void SetVibrationEnabled(bool can_vibrate); + void ClearAllConnectedControllers(); + void DisconnectAllConnectedControllers(); + void ConnectAllDisconnectedControllers(); + void ClearAllControllers(); + + static std::size_t NPadIdToIndex(u32 npad_id); + static u32 IndexToNPad(std::size_t index); private: struct CommonHeader { @@ -164,8 +177,11 @@ private: BitField<23, 1, u64_le> r_stick_down; // Not always active? - BitField<24, 1, u64_le> sl; - BitField<25, 1, u64_le> sr; + BitField<24, 1, u64_le> left_sl; + BitField<25, 1, u64_le> left_sr; + + BitField<26, 1, u64_le> right_sl; + BitField<27, 1, u64_le> right_sr; }; }; static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size"); @@ -189,12 +205,17 @@ private: }; static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size"); - struct GenericStates { - s64_le timestamp; - s64_le timestamp2; + struct ControllerPad { ControllerPadState pad_states; AnalogPosition l_stick; AnalogPosition r_stick; + }; + static_assert(sizeof(ControllerPad) == 0x18, "ControllerPad is an invalid size"); + + struct GenericStates { + s64_le timestamp; + s64_le timestamp2; + ControllerPad pad; ConnectionState connection_status; }; static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size"); @@ -266,15 +287,20 @@ private: static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size"); struct ControllerHolder { - Controller_NPad::NPadControllerType type; + NPadControllerType type; bool is_connected; }; NPadType style{}; std::array<NPadEntry, 10> shared_memory_entries{}; - std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> + std::array< + std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>, + 10> buttons; - std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; + std::array< + std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>, + 10> + sticks; std::vector<u32> supported_npad_id_types{}; NpadHoldType hold_type{NpadHoldType::Vertical}; Kernel::SharedPtr<Kernel::Event> styleset_changed_event; @@ -285,5 +311,8 @@ private: void InitNewlyAddedControler(std::size_t controller_idx); bool IsControllerSupported(NPadControllerType controller) const; NPadControllerType DecideBestController(NPadControllerType priority) const; + void RequestPadStateUpdate(u32 npad_id); + std::array<ControllerPad, 10> npad_pad_states{}; + bool IsControllerSupported(NPadControllerType controller); }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp index 43efef803..f666b1bd8 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.cpp +++ b/src/core/hle/service/hid/controllers/touchscreen.cpp @@ -41,16 +41,17 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) { const auto [x, y, pressed] = touch_device->GetStatus(); auto& touch_entry = cur_entry.states[0]; - if (pressed) { + touch_entry.attribute.raw = 0; + if (pressed && Settings::values.touchscreen.enabled) { touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width); touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height); - touch_entry.diameter_x = 15; - touch_entry.diameter_y = 15; - touch_entry.rotation_angle = 0; + touch_entry.diameter_x = Settings::values.touchscreen.diameter_x; + touch_entry.diameter_y = Settings::values.touchscreen.diameter_y; + touch_entry.rotation_angle = Settings::values.touchscreen.rotation_angle; const u64 tick = CoreTiming::GetTicks(); touch_entry.delta_time = tick - last_touch; last_touch = tick; - touch_entry.finger = 0; + touch_entry.finger = Settings::values.touchscreen.finger; cur_entry.entry_count = 1; } else { cur_entry.entry_count = 0; @@ -60,6 +61,6 @@ void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) { } void Controller_Touchscreen::OnLoadInputDevices() { - touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); + touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touchscreen.device); } } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e5db6e6ba..94cd0eba9 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -4,6 +4,7 @@ #pragma once +#include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" @@ -29,9 +30,18 @@ public: void OnLoadInputDevices() override; private: + struct Attributes { + union { + u32 raw{}; + BitField<0, 1, u32_le> start_touch; + BitField<1, 1, u32_le> end_touch; + }; + }; + static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size"); + struct TouchState { u64_le delta_time; - u32_le attribute; + Attributes attribute; u32_le finger; u32_le x; u32_le y; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 39631b14f..7c0dac5dc 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -34,8 +34,8 @@ namespace Service::HID { // Updating period for each HID device. -// TODO(shinyquagsire23): These need better values. -constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; +// TODO(ogniK): Find actual polling rate of hid +constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 66; constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100; constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000; diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index b43f1f054..7a9d0d0dd 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -16,35 +16,18 @@ namespace Service::LDR { -namespace ErrCodes { -enum { - InvalidMemoryState = 51, - InvalidNRO = 52, - InvalidNRR = 53, - MissingNRRHash = 54, - MaximumNRO = 55, - MaximumNRR = 56, - AlreadyLoaded = 57, - InvalidAlignment = 81, - InvalidSize = 82, - InvalidNROAddress = 84, - InvalidNRRAddress = 85, - NotInitialized = 87, -}; -} - -constexpr ResultCode ERROR_INVALID_MEMORY_STATE(ErrorModule::Loader, ErrCodes::InvalidMemoryState); -constexpr ResultCode ERROR_INVALID_NRO(ErrorModule::Loader, ErrCodes::InvalidNRO); -constexpr ResultCode ERROR_INVALID_NRR(ErrorModule::Loader, ErrCodes::InvalidNRR); -constexpr ResultCode ERROR_MISSING_NRR_HASH(ErrorModule::Loader, ErrCodes::MissingNRRHash); -constexpr ResultCode ERROR_MAXIMUM_NRO(ErrorModule::Loader, ErrCodes::MaximumNRO); -constexpr ResultCode ERROR_MAXIMUM_NRR(ErrorModule::Loader, ErrCodes::MaximumNRR); -constexpr ResultCode ERROR_ALREADY_LOADED(ErrorModule::Loader, ErrCodes::AlreadyLoaded); -constexpr ResultCode ERROR_INVALID_ALIGNMENT(ErrorModule::Loader, ErrCodes::InvalidAlignment); -constexpr ResultCode ERROR_INVALID_SIZE(ErrorModule::Loader, ErrCodes::InvalidSize); -constexpr ResultCode ERROR_INVALID_NRO_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNROAddress); -constexpr ResultCode ERROR_INVALID_NRR_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNRRAddress); -constexpr ResultCode ERROR_NOT_INITIALIZED(ErrorModule::Loader, ErrCodes::NotInitialized); +constexpr ResultCode ERROR_INVALID_MEMORY_STATE{ErrorModule::Loader, 51}; +constexpr ResultCode ERROR_INVALID_NRO{ErrorModule::Loader, 52}; +constexpr ResultCode ERROR_INVALID_NRR{ErrorModule::Loader, 53}; +constexpr ResultCode ERROR_MISSING_NRR_HASH{ErrorModule::Loader, 54}; +constexpr ResultCode ERROR_MAXIMUM_NRO{ErrorModule::Loader, 55}; +constexpr ResultCode ERROR_MAXIMUM_NRR{ErrorModule::Loader, 56}; +constexpr ResultCode ERROR_ALREADY_LOADED{ErrorModule::Loader, 57}; +constexpr ResultCode ERROR_INVALID_ALIGNMENT{ErrorModule::Loader, 81}; +constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::Loader, 82}; +constexpr ResultCode ERROR_INVALID_NRO_ADDRESS{ErrorModule::Loader, 84}; +constexpr ResultCode ERROR_INVALID_NRR_ADDRESS{ErrorModule::Loader, 85}; +constexpr ResultCode ERROR_NOT_INITIALIZED{ErrorModule::Loader, 87}; constexpr u64 MAXIMUM_LOADED_RO = 0x40; diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index c89157a4d..4e5fdb16e 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -18,7 +18,7 @@ public: ILogger() : ServiceFramework("ILogger") { static const FunctionInfo functions[] = { {0x00000000, &ILogger::Initialize, "Initialize"}, - {0x00000001, nullptr, "SetDestination"}, + {0x00000001, &ILogger::SetDestination, "SetDestination"}, }; RegisterHandlers(functions); } @@ -178,6 +178,17 @@ private: } } + // This service function is intended to be used as a way to + // redirect logging output to different destinations, however, + // given we always want to see the logging output, it's sufficient + // to do nothing and return success here. + void SetDestination(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_LM, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + std::ostringstream log_stream; }; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 0da159559..26fcd3405 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -10,6 +10,56 @@ namespace Settings { +namespace NativeButton { +const std::array<const char*, NumButtons> mapping = {{ + "button_a", + "button_b", + "button_x", + "button_y", + "button_lstick", + "button_rstick", + "button_l", + "button_r", + "button_zl", + "button_zr", + "button_plus", + "button_minus", + "button_dleft", + "button_dup", + "button_dright", + "button_ddown", + "button_lstick_left", + "button_lstick_up", + "button_lstick_right", + "button_lstick_down", + "button_rstick_left", + "button_rstick_up", + "button_rstick_right", + "button_rstick_down", + "button_sl", + "button_sr", + "button_home", + "button_screenshot", +}}; +} + +namespace NativeAnalog { +const std::array<const char*, NumAnalogs> mapping = {{ + "lstick", + "rstick", +}}; +} + +namespace NativeMouseButton { +const std::array<const char*, NumMouseButtons> mapping = {{ + "left", + "right", + "middle", + "forward", + "back", +}}; +} + Values values = {}; void Apply() { diff --git a/src/core/settings.h b/src/core/settings.h index e424479f2..e63134f80 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -60,36 +60,7 @@ constexpr int BUTTON_NS_END = NumButtons; constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; -static const std::array<const char*, NumButtons> mapping = {{ - "button_a", - "button_b", - "button_x", - "button_y", - "button_lstick", - "button_rstick", - "button_l", - "button_r", - "button_zl", - "button_zr", - "button_plus", - "button_minus", - "button_dleft", - "button_dup", - "button_dright", - "button_ddown", - "button_lstick_left", - "button_lstick_up", - "button_lstick_right", - "button_lstick_down", - "button_rstick_left", - "button_rstick_up", - "button_rstick_right", - "button_rstick_down", - "button_sl", - "button_sr", - "button_home", - "button_screenshot", -}}; +extern const std::array<const char*, NumButtons> mapping; } // namespace NativeButton @@ -105,12 +76,273 @@ constexpr int STICK_HID_BEGIN = LStick; constexpr int STICK_HID_END = NumAnalogs; constexpr int NUM_STICKS_HID = NumAnalogs; -static const std::array<const char*, NumAnalogs> mapping = {{ - "lstick", - "rstick", -}}; +extern const std::array<const char*, NumAnalogs> mapping; } // namespace NativeAnalog +namespace NativeMouseButton { +enum Values { + Left, + Right, + Middle, + Forward, + Back, + + NumMouseButtons, +}; + +constexpr int MOUSE_HID_BEGIN = Left; +constexpr int MOUSE_HID_END = NumMouseButtons; +constexpr int NUM_MOUSE_HID = NumMouseButtons; + +extern const std::array<const char*, NumMouseButtons> mapping; +} // namespace NativeMouseButton + +namespace NativeKeyboard { +enum Keys { + None, + Error, + + A = 4, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + N0, + Enter, + Escape, + Backspace, + Tab, + Space, + Minus, + Equal, + LeftBrace, + RightBrace, + Backslash, + Tilde, + Semicolon, + Apostrophe, + Grave, + Comma, + Dot, + Slash, + CapsLockKey, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + SystemRequest, + ScrollLockKey, + Pause, + Insert, + Home, + PageUp, + Delete, + End, + PageDown, + Right, + Left, + Down, + Up, + + NumLockKey, + KPSlash, + KPAsterisk, + KPMinus, + KPPlus, + KPEnter, + KP1, + KP2, + KP3, + KP4, + KP5, + KP6, + KP7, + KP8, + KP9, + KP0, + KPDot, + + Key102, + Compose, + Power, + KPEqual, + + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + Open, + Help, + Properties, + Front, + Stop, + Repeat, + Undo, + Cut, + Copy, + Paste, + Find, + Mute, + VolumeUp, + VolumeDown, + CapsLockActive, + NumLockActive, + ScrollLockActive, + KPComma, + + KPLeftParenthesis, + KPRightParenthesis, + + LeftControlKey = 0xE0, + LeftShiftKey, + LeftAltKey, + LeftMetaKey, + RightControlKey, + RightShiftKey, + RightAltKey, + RightMetaKey, + + MediaPlayPause, + MediaStopCD, + MediaPrevious, + MediaNext, + MediaEject, + MediaVolumeUp, + MediaVolumeDown, + MediaMute, + MediaWebsite, + MediaBack, + MediaForward, + MediaStop, + MediaFind, + MediaScrollUp, + MediaScrollDown, + MediaEdit, + MediaSleep, + MediaCoffee, + MediaRefresh, + MediaCalculator, + + NumKeyboardKeys, +}; + +static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); + +enum Modifiers { + LeftControl, + LeftShift, + LeftAlt, + LeftMeta, + RightControl, + RightShift, + RightAlt, + RightMeta, + CapsLock, + ScrollLock, + NumLock, + + NumKeyboardMods, +}; + +constexpr int KEYBOARD_KEYS_HID_BEGIN = None; +constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; +constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; + +constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; +constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; +constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; + +} // namespace NativeKeyboard + +using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; +using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; +using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; +using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; +using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; + +enum class ControllerType { + ProController, + DualJoycon, + RightJoycon, + LeftJoycon, +}; + +struct PlayerInput { + bool connected; + ControllerType type; + ButtonsRaw buttons; + AnalogsRaw analogs; + + u32 body_color_right; + u32 button_color_right; + u32 body_color_left; + u32 button_color_left; +}; + +struct TouchscreenInput { + bool enabled; + std::string device; + + u32 finger; + u32 diameter_x; + u32 diameter_y; + u32 rotation_angle; +}; + struct Values { // System bool use_docked_mode; @@ -120,10 +352,22 @@ struct Values { s32 language_index; // Controls - std::array<std::string, NativeButton::NumButtons> buttons; - std::array<std::string, NativeAnalog::NumAnalogs> analogs; + std::array<PlayerInput, 10> players; + + bool mouse_enabled; + std::string mouse_device; + MouseButtonsRaw mouse_buttons; + + bool keyboard_enabled; + KeyboardKeysRaw keyboard_keys; + KeyboardModsRaw keyboard_mods; + + bool debug_pad_enabled; + ButtonsRaw debug_pad_buttons; + AnalogsRaw debug_pad_analogs; + std::string motion_device; - std::string touch_device; + TouchscreenInput touchscreen; std::atomic_bool is_device_reload_pending{true}; // Core diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index a04e00ecb..2bc534be3 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -69,6 +69,15 @@ void Maxwell3D::InitializeRegisterDefaults() { // TODO(Rodrigo): Most games do not set a point size. I think this is a case of a // register carrying a default value. Assume it's OpenGL's default (1). regs.point_size = 1.0f; + + // TODO(bunnei): Some games do not initialize the color masks (e.g. Sonic Mania). Assuming a + // default of enabled fixes rendering here. + for (std::size_t color_mask = 0; color_mask < Regs::NumRenderTargets; color_mask++) { + regs.color_mask[color_mask].R.Assign(1); + regs.color_mask[color_mask].G.Assign(1); + regs.color_mask[color_mask].B.Assign(1); + regs.color_mask[color_mask].A.Assign(1); + } } void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 83c7e5b0b..51b3904f6 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -17,6 +17,8 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) { switch (format) { case PixelFormat::ABGR8: return 4; + default: + return 4; } UNREACHABLE(); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 5fde22ad4..90a88b91a 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -49,8 +49,7 @@ static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) { case Tegra::Shader::OutputTopology::TriangleStrip: return "triangle_strip"; default: - LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unknown output topology: {}", static_cast<u32>(topology)); return "points"; } } @@ -167,8 +166,8 @@ private: case OpCode::Id::SSY: case OpCode::Id::PBK: { // The SSY and PBK use a similar encoding as the BRA instruction. - ASSERT_MSG(instr.bra.constant_buffer == 0, - "Constant buffer branching is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer branching is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. @@ -299,8 +298,7 @@ public: // Default - do nothing return value; default: - LOG_CRITICAL(HW_GPU, "Unimplemented conversion size {}", static_cast<u32>(size)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented conversion size: {}", static_cast<u32>(size)); } } @@ -363,7 +361,7 @@ public: u64 value_num_components, bool is_saturated = false, u64 dest_elem = 0, Register::Size size = Register::Size::Word, bool sets_cc = false) { - ASSERT_MSG(!is_saturated, "Unimplemented"); + UNIMPLEMENTED_IF(is_saturated); const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"}; @@ -392,7 +390,7 @@ public: Tegra::Shader::HalfMerge merge, u64 dest_num_components, u64 value_num_components, bool is_saturated = false, u64 dest_elem = 0) { - ASSERT_MSG(!is_saturated, "Unimplemented"); + UNIMPLEMENTED_IF(is_saturated); const std::string result = [&]() { switch (merge) { @@ -461,8 +459,7 @@ public: case Tegra::Shader::ControlCode::NEU: return "!(" + GetInternalFlag(InternalFlag::ZeroFlag) + ')'; default: - LOG_CRITICAL(HW_GPU, "Unimplemented Control Code {}", static_cast<u32>(cc)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented Control Code: {}", static_cast<u32>(cc)); return "false"; } } @@ -761,8 +758,7 @@ private: u64 dest_num_components, u64 value_num_components, u64 dest_elem, bool precise) { if (reg == Register::ZeroIndex) { - LOG_CRITICAL(HW_GPU, "Cannot set Register::ZeroIndex"); - UNREACHABLE(); + // Setting RZ is a nop in hardware. return; } @@ -847,16 +843,13 @@ private: if (declr_input_attribute.count(attribute) == 0) { declr_input_attribute[attribute] = input_mode; } else { - if (declr_input_attribute[attribute] != input_mode) { - LOG_CRITICAL(HW_GPU, "Same Input multiple input modes"); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(declr_input_attribute[attribute] != input_mode, + "Multiple input modes for the same attribute"); } return GeometryPass("input_attribute_" + std::to_string(index)); } - LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); } return "vec4(0, 0, 0, 0)"; @@ -882,24 +875,20 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled Ipa InterpMode: {}", static_cast<u32>(interp_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled IPA interp mode: {}", static_cast<u32>(interp_mode)); } } switch (sample_mode) { - case Tegra::Shader::IpaSampleMode::Centroid: { - // Note not implemented, it can be implemented with the "centroid " keyword in glsl; - LOG_CRITICAL(HW_GPU, "Ipa Sampler Mode: centroid, not implemented"); - UNREACHABLE(); + case Tegra::Shader::IpaSampleMode::Centroid: + // It can be implemented with the "centroid " keyword in glsl + UNIMPLEMENTED_MSG("Unimplemented IPA sampler mode centroid"); break; - } - case Tegra::Shader::IpaSampleMode::Default: { + case Tegra::Shader::IpaSampleMode::Default: // Default, n/a break; - } default: { - LOG_CRITICAL(HW_GPU, "Unhandled Ipa SampleMode: {}", static_cast<u32>(sample_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented IPA sampler mode: {}", static_cast<u32>(sample_mode)); + break; } } return out; @@ -920,8 +909,7 @@ private: return "output_attribute_" + std::to_string(index); } - LOG_CRITICAL(HW_GPU, "Unhandled output attribute: {}", index); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled output attribute={}", index); return {}; } } @@ -1078,8 +1066,8 @@ private: {PredCondition::GreaterThanWithNan, ">"}, {PredCondition::GreaterEqualWithNan, ">="}}; const auto& comparison{PredicateComparisonStrings.find(condition)}; - ASSERT_MSG(comparison != PredicateComparisonStrings.end(), - "Unknown predicate comparison operation"); + UNIMPLEMENTED_IF_MSG(comparison == PredicateComparisonStrings.end(), + "Unknown predicate comparison operation"); std::string predicate{'(' + op_a + ") " + comparison->second + " (" + op_b + ')'}; if (condition == PredCondition::LessThanWithNan || @@ -1107,7 +1095,7 @@ private: }; auto op = PredicateOperationStrings.find(operation); - ASSERT_MSG(op != PredicateOperationStrings.end(), "Unknown predicate operation"); + UNIMPLEMENTED_IF_MSG(op == PredicateOperationStrings.end(), "Unknown predicate operation"); return op->second; } @@ -1205,8 +1193,7 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unimplemented logic operation: {}", static_cast<u32>(logic_op)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented logic operation={}", static_cast<u32>(logic_op)); } if (dest != Tegra::Shader::Register::ZeroIndex) { @@ -1224,9 +1211,8 @@ private: SetPredicate(static_cast<u64>(predicate), '(' + result + ") != 0"); break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented predicate result mode: {}", - static_cast<u32>(predicate_mode)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented predicate result mode: {}", + static_cast<u32>(predicate_mode)); } } @@ -1294,19 +1280,15 @@ private: static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) { switch (texture_type) { - case Tegra::Shader::TextureType::Texture1D: { + case Tegra::Shader::TextureType::Texture1D: return 1; - } - case Tegra::Shader::TextureType::Texture2D: { + case Tegra::Shader::TextureType::Texture2D: return 2; - } case Tegra::Shader::TextureType::Texture3D: - case Tegra::Shader::TextureType::TextureCube: { + case Tegra::Shader::TextureType::TextureCube: return 3; - } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture type: {}", static_cast<u32>(texture_type)); return 0; } } @@ -1342,7 +1324,7 @@ private: void EmitFragmentOutputsWrite() { ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); - ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); + UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Samplemask write is unimplemented"); shader.AddLine("if (alpha_test[0] != 0) {"); ++shader.scope; @@ -1408,7 +1390,7 @@ private: case Tegra::Shader::VideoType::Size32: // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when // this type is used (1 * 1 + 0 == 0x5b800000). Until a better - // explanation is found: assert. + // explanation is found: abort. UNIMPLEMENTED(); return zero; case Tegra::Shader::VideoType::Invalid: @@ -1464,8 +1446,7 @@ private: // Decoding failure if (!opcode) { - LOG_CRITICAL(HW_GPU, "Unhandled instruction: {0:x}", instr.value); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled instruction: {0:x}", instr.value); return offset + 1; } @@ -1473,8 +1454,8 @@ private: fmt::format("// {}: {} (0x{:016x})", offset, opcode->get().GetName(), instr.value)); using Tegra::Shader::Pred; - ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute, - "NeverExecute predicate not implemented"); + UNIMPLEMENTED_IF_MSG(instr.pred.full_pred == Pred::NeverExecute, + "NeverExecute predicate not implemented"); // Some instructions (like SSY) don't have a predicate field, they are always // unconditionally executed. @@ -1517,37 +1498,37 @@ private: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. - ASSERT_MSG(instr.fmul.tab5cb8_2 == 0, "FMUL tab5cb8_2({}) is not implemented", - instr.fmul.tab5cb8_2.Value()); - ASSERT_MSG(instr.fmul.tab5c68_1 == 0, "FMUL tab5cb8_1({}) is not implemented", - instr.fmul.tab5c68_1.Value()); - ASSERT_MSG(instr.fmul.tab5c68_0 == 1, "FMUL tab5cb8_0({}) is not implemented", - instr.fmul.tab5c68_0 - .Value()); // SMO typical sends 1 here which seems to be the default - ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0, + "FMUL tab5cb8_2({}) is not implemented", + instr.fmul.tab5cb8_2.Value()); + UNIMPLEMENTED_IF_MSG(instr.fmul.tab5c68_1 != 0, + "FMUL tab5cb8_1({}) is not implemented", + instr.fmul.tab5c68_1.Value()); + UNIMPLEMENTED_IF_MSG( + instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented", + instr.fmul.tab5c68_0 + .Value()); // SMO typical sends 1 here which seems to be the default + UNIMPLEMENTED_IF_MSG(instr.fmul.cc != 0, "FMUL cc is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "FMUL Generates an unhandled Control Code"); op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMUL Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::FADD_C: case OpCode::Id::FADD_R: case OpCode::Id::FADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "FADD Generates an unhandled Control Code"); + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::MUFU: { @@ -1582,15 +1563,17 @@ private: instr.alu.saturate_d, 0, true); break; default: - LOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}", - static_cast<unsigned>(instr.sub_op.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled MUFU sub op={0:x}", + static_cast<unsigned>(instr.sub_op.Value())); } break; } case OpCode::Id::FMNMX_C: case OpCode::Id::FMNMX_R: case OpCode::Id::FMNMX_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "FMNMX Generates an unhandled Control Code"); + op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a); op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b); @@ -1601,10 +1584,6 @@ private: '(' + condition + ") ? min(" + parameters + ") : max(" + parameters + ')', 1, 1, false, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMNMX Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::RRO_C: @@ -1617,9 +1596,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled arithmetic instruction: {}", opcode->get().GetName()); } } break; @@ -1631,17 +1608,19 @@ private: break; } case OpCode::Id::FMUL32_IMM: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "FMUL32 Generates an unhandled Control Code"); + regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1, instr.fmul32.saturate, 0, true); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "FMUL32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::FADD32I: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "FADD32 Generates an unhandled Control Code"); + std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); std::string op_b = GetImmediate32(instr); @@ -1662,23 +1641,21 @@ private: } regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, false, 0, true); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "FADD32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } } break; } case OpCode::Type::Bfe: { - ASSERT_MSG(!instr.bfe.negate_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.bfe.negate_b); std::string op_a = instr.bfe.negate_a ? "-" : ""; op_a += regs.GetRegisterAsInteger(instr.gpr8); switch (opcode->get().GetId()) { case OpCode::Id::BFE_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "BFE Generates an unhandled Control Code"); + std::string inner_shift = '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')'; std::string outer_shift = @@ -1686,15 +1663,10 @@ private: std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')'; regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "BFE Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled BFE instruction: {}", opcode->get().GetName()); } } @@ -1719,6 +1691,8 @@ private: case OpCode::Id::SHR_C: case OpCode::Id::SHR_R: case OpCode::Id::SHR_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "SHR Generates an unhandled Control Code"); + if (!instr.shift.is_signed) { // Logical shift right op_a = "uint(" + op_a + ')'; @@ -1727,24 +1701,17 @@ private: // Cast to int is superfluous for arithmetic shift, it's only for a logical shift regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "SHR Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::SHL_C: case OpCode::Id::SHL_R: case OpCode::Id::SHL_IMM: + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "SHL Generates an unhandled Control Code"); + regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "SHL Generates an unhandled Control Code"); - UNREACHABLE(); - } break; default: { - LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled shift instruction: {}", opcode->get().GetName()); } } break; @@ -1755,17 +1722,19 @@ private: switch (opcode->get().GetId()) { case OpCode::Id::IADD32I: + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "IADD32 Generates an unhandled Control Code"); + if (instr.iadd32i.negate_a) op_a = "-(" + op_a + ')'; regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.iadd32i.saturate != 0); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD32 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; case OpCode::Id::LOP32I: { + UNIMPLEMENTED_IF_MSG(instr.op_32.generates_cc, + "LOP32I Generates an unhandled Control Code"); + if (instr.alu.lop32i.invert_a) op_a = "~(" + op_a + ')'; @@ -1775,16 +1744,11 @@ private: WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b, Tegra::Shader::PredicateResultMode::None, Tegra::Shader::Pred::UnusedIndex); - if (instr.op_32.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP32I Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled ArithmeticIntegerImmediate instruction: {}", + opcode->get().GetName()); } } break; @@ -1807,6 +1771,9 @@ private: case OpCode::Id::IADD_C: case OpCode::Id::IADD_R: case OpCode::Id::IADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "IADD Generates an unhandled Control Code"); + if (instr.alu_integer.negate_a) op_a = "-(" + op_a + ')'; @@ -1815,15 +1782,14 @@ private: regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1, instr.alu.saturate_d); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::IADD3_C: case OpCode::Id::IADD3_R: case OpCode::Id::IADD3_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "IADD3 Generates an unhandled Control Code"); + std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); auto apply_height = [](auto height, auto& oprand) { @@ -1837,9 +1803,8 @@ private: oprand = "((" + oprand + ") >> 16)"; break; default: - LOG_CRITICAL(HW_GPU, "Unhandled IADD3 height: {}", - static_cast<u32>(height.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled IADD3 height: {}", + static_cast<u32>(height.Value())); } }; @@ -1880,16 +1845,14 @@ private: } regs.SetRegisterToInteger(instr.gpr0, true, 0, result, 1, 1); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IADD3 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::ISCADD_C: case OpCode::Id::ISCADD_R: case OpCode::Id::ISCADD_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "ISCADD Generates an unhandled Control Code"); + if (instr.alu_integer.negate_a) op_a = "-(" + op_a + ')'; @@ -1900,10 +1863,6 @@ private: regs.SetRegisterToInteger(instr.gpr0, true, 0, "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "ISCADD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::POPC_C: @@ -1927,6 +1886,8 @@ private: case OpCode::Id::LOP_C: case OpCode::Id::LOP_R: case OpCode::Id::LOP_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "LOP Generates an unhandled Control Code"); + if (instr.alu.lop.invert_a) op_a = "~(" + op_a + ')'; @@ -1935,15 +1896,14 @@ private: WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b, instr.alu.lop.pred_result_mode, instr.alu.lop.pred48); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::LOP3_C: case OpCode::Id::LOP3_R: case OpCode::Id::LOP3_IMM: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "LOP3 Generates an unhandled Control Code"); + const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); std::string lut; @@ -1954,17 +1914,15 @@ private: } WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "LOP3 Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::IMNMX_C: case OpCode::Id::IMNMX_R: case OpCode::Id::IMNMX_IMM: { - ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.imnmx.exchange != Tegra::Shader::IMinMaxExchange::None); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, + "IMNMX Generates an unhandled Control Code"); + const std::string condition = GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0); const std::string parameters = op_a + ',' + op_b; @@ -1972,10 +1930,6 @@ private: '(' + condition + ") ? min(" + parameters + ") : max(" + parameters + ')', 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "IMNMX Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::LEA_R2: @@ -2030,24 +1984,19 @@ private: op_b = regs.GetRegisterAsInteger(instr.gpr8); op_a = std::to_string(instr.lea.imm.entry_a); op_c = std::to_string(instr.lea.imm.entry_b); - LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled LEA subinstruction: {}", opcode->get().GetName()); } } - if (instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex)) { - LOG_ERROR(HW_GPU, "Unhandled LEA Predicate"); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex), + "Unhandled LEA Predicate"); const std::string value = '(' + op_a + " + (" + op_b + "*(1 << " + op_c + ")))"; regs.SetRegisterToInteger(instr.gpr0, true, 0, value, 1, 1); break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled ArithmeticInteger instruction: {}", + opcode->get().GetName()); } } @@ -2056,7 +2005,7 @@ private: case OpCode::Type::ArithmeticHalf: { if (opcode->get().GetId() == OpCode::Id::HADD2_C || opcode->get().GetId() == OpCode::Id::HADD2_R) { - ASSERT_MSG(instr.alu_half.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half.ftz != 0); } const bool negate_a = opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0; @@ -2094,9 +2043,8 @@ private: case OpCode::Id::HMUL2_R: return '(' + op_a + " * " + op_b + ')'; default: - LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled half float instruction: {}", + opcode->get().GetName()); return std::string("0"); } }(); @@ -2107,10 +2055,10 @@ private: } case OpCode::Type::ArithmeticHalfImmediate: { if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) { - ASSERT_MSG(instr.alu_half_imm.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half_imm.ftz != 0); } else { - ASSERT_MSG(instr.alu_half_imm.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.alu_half_imm.precision != + Tegra::Shader::HalfPrecision::None); } const std::string op_a = GetHalfFloat( @@ -2140,11 +2088,13 @@ private: std::string op_b = instr.ffma.negate_b ? "-" : ""; std::string op_c = instr.ffma.negate_c ? "-" : ""; - ASSERT_MSG(instr.ffma.cc == 0, "FFMA cc not implemented"); - ASSERT_MSG(instr.ffma.tab5980_0 == 1, "FFMA tab5980_0({}) not implemented", - instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO - ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented", - instr.ffma.tab5980_1.Value()); + UNIMPLEMENTED_IF_MSG(instr.ffma.cc != 0, "FFMA cc not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.ffma.tab5980_0 != 1, "FFMA tab5980_0({}) not implemented", + instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO + UNIMPLEMENTED_IF_MSG(instr.ffma.tab5980_1 != 0, "FFMA tab5980_1({}) not implemented", + instr.ffma.tab5980_1.Value()); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "FFMA Generates an unhandled Control Code"); switch (opcode->get().GetId()) { case OpCode::Id::FFMA_CR: { @@ -2170,27 +2120,19 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled FFMA instruction: {}", opcode->get().GetName()); } } regs.SetRegisterToFloat(instr.gpr0, 0, "fma(" + op_a + ", " + op_b + ", " + op_c + ')', 1, 1, instr.alu.saturate_d, 0, true); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "FFMA Generates an unhandled Control Code"); - UNREACHABLE(); - } - break; } case OpCode::Type::Hfma2: { if (opcode->get().GetId() == OpCode::Id::HFMA2_RR) { - ASSERT_MSG(instr.hfma2.rr.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.hfma2.rr.precision != Tegra::Shader::HalfPrecision::None); } else { - ASSERT_MSG(instr.hfma2.precision == Tegra::Shader::HalfPrecision::None, - "Unimplemented"); + UNIMPLEMENTED_IF(instr.hfma2.precision != Tegra::Shader::HalfPrecision::None); } const bool saturate = opcode->get().GetId() == OpCode::Id::HFMA2_RR ? instr.hfma2.rr.saturate != 0 @@ -2240,7 +2182,7 @@ private: case OpCode::Type::Conversion: { switch (opcode->get().GetId()) { case OpCode::Id::I2I_R: { - ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.selector); std::string op_a = regs.GetRegisterAsInteger( instr.gpr20, 0, instr.conversion.is_input_signed, instr.conversion.src_size); @@ -2260,8 +2202,9 @@ private: } case OpCode::Id::I2F_R: case OpCode::Id::I2F_C: { - ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); - ASSERT_MSG(!instr.conversion.selector, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.selector); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "I2F Generates an unhandled Control Code"); std::string op_a{}; @@ -2286,16 +2229,12 @@ private: } regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "I2F Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::F2F_R: { - ASSERT_MSG(instr.conversion.dest_size == Register::Size::Word, "Unimplemented"); - ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.dest_size != Register::Size::Word); + UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "F2F Generates an unhandled Control Code"); std::string op_a = regs.GetRegisterAsFloat(instr.gpr20); if (instr.conversion.abs_a) { @@ -2322,23 +2261,18 @@ private: op_a = "trunc(" + op_a + ')'; break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented f2f rounding mode {}", - static_cast<u32>(instr.conversion.f2f.rounding.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented F2F rounding mode {}", + static_cast<u32>(instr.conversion.f2f.rounding.Value())); break; } regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d); - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "F2F Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } case OpCode::Id::F2I_R: case OpCode::Id::F2I_C: { - ASSERT_MSG(instr.conversion.src_size == Register::Size::Word, "Unimplemented"); + UNIMPLEMENTED_IF(instr.conversion.src_size != Register::Size::Word); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "F2I Generates an unhandled Control Code"); std::string op_a{}; if (instr.is_b_gpr) { @@ -2369,9 +2303,8 @@ private: op_a = "trunc(" + op_a + ')'; break; default: - LOG_CRITICAL(HW_GPU, "Unimplemented f2i rounding mode {}", - static_cast<u32>(instr.conversion.f2i.rounding.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unimplemented F2I rounding mode {}", + static_cast<u32>(instr.conversion.f2i.rounding.Value())); break; } @@ -2383,16 +2316,10 @@ private: regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, 1, false, 0, instr.conversion.dest_size); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "F2I Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); } } break; @@ -2401,10 +2328,10 @@ private: switch (opcode->get().GetId()) { case OpCode::Id::LD_A: { // Note: Shouldn't this be interp mode flat? As in no interpolation made. - ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, - "Indirect attribute loads are not supported"); - ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, - "Unaligned attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex, + "Indirect attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0, + "Unaligned attribute loads are not supported"); Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Perspective, Tegra::Shader::IpaSampleMode::Default}; @@ -2431,7 +2358,7 @@ private: break; } case OpCode::Id::LD_C: { - ASSERT_MSG(instr.ld_c.unknown == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.ld_c.unknown != 0); // Add an extra scope and declare the index register inside to prevent // overwriting it in case it is used as an output of the LD instruction. @@ -2459,9 +2386,8 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled type: {}", - static_cast<unsigned>(instr.ld_c.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled type: {}", + static_cast<unsigned>(instr.ld_c.type.Value())); } --shader.scope; @@ -2469,6 +2395,9 @@ private: break; } case OpCode::Id::LD_L: { + UNIMPLEMENTED_IF_MSG(instr.ld_l.unknown == 1, "LD_L Unhandled mode: {}", + static_cast<unsigned>(instr.ld_l.unknown.Value())); + // Add an extra scope and declare the index register inside to prevent // overwriting it in case it is used as an output of the LD instruction. shader.AddLine('{'); @@ -2481,20 +2410,13 @@ private: const std::string op_a = regs.GetLocalMemoryAsFloat("index"); - if (instr.ld_l.unknown != 1) { - LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}", - static_cast<unsigned>(instr.ld_l.unknown.Value())); - UNREACHABLE(); - } - switch (instr.ldst_sl.type.Value()) { case Tegra::Shader::StoreType::Bytes32: regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); break; default: - LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}", - static_cast<unsigned>(instr.ldst_sl.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("LD_L Unhandled type: {}", + static_cast<unsigned>(instr.ldst_sl.type.Value())); } --shader.scope; @@ -2502,10 +2424,10 @@ private: break; } case OpCode::Id::ST_A: { - ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, - "Indirect attribute loads are not supported"); - ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, - "Unaligned attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG(instr.gpr8.Value() != Register::ZeroIndex, + "Indirect attribute loads are not supported"); + UNIMPLEMENTED_IF_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) != 0, + "Unaligned attribute loads are not supported"); u64 next_element = instr.attribute.fmt20.element; u64 next_index = static_cast<u64>(instr.attribute.fmt20.index.Value()); @@ -2530,6 +2452,9 @@ private: break; } case OpCode::Id::ST_L: { + UNIMPLEMENTED_IF_MSG(instr.st_l.unknown == 0, "ST_L Unhandled mode: {}", + static_cast<unsigned>(instr.st_l.unknown.Value())); + // Add an extra scope and declare the index register inside to prevent // overwriting it in case it is used as an output of the LD instruction. shader.AddLine('{'); @@ -2540,20 +2465,13 @@ private: shader.AddLine("uint index = (" + op + " / 4);"); - if (instr.st_l.unknown != 0) { - LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}", - static_cast<unsigned>(instr.st_l.unknown.Value())); - UNREACHABLE(); - } - switch (instr.ldst_sl.type.Value()) { case Tegra::Shader::StoreType::Bytes32: regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0)); break; default: - LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}", - static_cast<unsigned>(instr.ldst_sl.type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("ST_L Unhandled type: {}", + static_cast<unsigned>(instr.ldst_sl.type.Value())); } --shader.scope; @@ -2565,10 +2483,10 @@ private: std::string coord; const bool is_array = instr.tex.array != 0; - ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); const bool depth_compare = instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); @@ -2634,9 +2552,8 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); // Fallback to interpreting as a 2D texture for now const std::string x = regs.GetRegisterAsFloat(instr.gpr8); @@ -2694,9 +2611,8 @@ private: } default: { texture = "texture(" + sampler + ", coords)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.tex.GetTextureProcessMode())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture process mode {}", + static_cast<u32>(instr.tex.GetTextureProcessMode())); } } if (!depth_compare) { @@ -2721,8 +2637,8 @@ private: Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()}; bool is_array{instr.texs.IsArrayTexture()}; - ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); const bool depth_compare = instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); @@ -2761,9 +2677,8 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); // Fallback to interpreting as a 2D texture for now const std::string x = regs.GetRegisterAsFloat(instr.gpr8); @@ -2795,9 +2710,8 @@ private: } default: { texture = "texture(" + sampler + ", coords)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.texs.GetTextureProcessMode())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture process mode {}", + static_cast<u32>(instr.texs.GetTextureProcessMode())); } } if (!depth_compare) { @@ -2815,12 +2729,12 @@ private: ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D); ASSERT(is_array == false); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), - "MZ is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), + "MZ is not implemented"); u32 op_c_offset = 0; @@ -2831,21 +2745,16 @@ private: break; } case Tegra::Shader::TextureType::Texture2D: { - if (is_array) { - LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture"); - UNREACHABLE(); - } else { - const std::string x = regs.GetRegisterAsInteger(instr.gpr8); - const std::string y = regs.GetRegisterAsInteger(instr.gpr20); - coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; - op_c_offset = 1; - } + UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture"); + + const std::string x = regs.GetRegisterAsInteger(instr.gpr8); + const std::string y = regs.GetRegisterAsInteger(instr.gpr20); + coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + op_c_offset = 1; break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); } const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); @@ -2863,9 +2772,8 @@ private: } default: { texture = "texelFetch(" + sampler + ", coords, 0)"; - LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", - static_cast<u32>(instr.tlds.GetTextureProcessMode())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture process mode {}", + static_cast<u32>(instr.tlds.GetTextureProcessMode())); } } WriteTexsInstruction(instr, coord, texture); @@ -2876,14 +2784,14 @@ private: ASSERT(instr.tld4.array == 0); std::string coord; - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), - "NDV is not implemented"); - ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), - "PTP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), + "PTP is not implemented"); const bool depth_compare = instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); auto texture_type = instr.tld4.texture_type.Value(); @@ -2906,9 +2814,8 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}", - static_cast<u32>(num_coordinates)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled coordinates number {}", + static_cast<u32>(num_coordinates)); const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; @@ -2942,10 +2849,12 @@ private: break; } case OpCode::Id::TLD4S: { - ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), - "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG( + instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); const bool depth_compare = instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC); @@ -2973,8 +2882,8 @@ private: break; } case OpCode::Id::TXQ: { - ASSERT_MSG(!instr.txq.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.txq.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); // TODO: the new commits on the texture refactor, change the way samplers work. // Sadly, not all texture instructions specify the type of texture their sampler @@ -2988,18 +2897,17 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled texture query type: {}", - static_cast<u32>(instr.txq.query_type.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture query type: {}", + static_cast<u32>(instr.txq.query_type.Value())); } } break; } case OpCode::Id::TMML: { - ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), - "NODEP is not implemented"); - ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), - "NDV is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); const std::string x = regs.GetRegisterAsFloat(instr.gpr8); const bool is_array = instr.tmml.array != 0; @@ -3021,9 +2929,7 @@ private: break; } default: - LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", - static_cast<u32>(texture_type)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type)); // Fallback to interpreting as a 2D texture for now const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); @@ -3046,8 +2952,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled memory instruction: {}", opcode->get().GetName()); } } break; @@ -3133,7 +3038,7 @@ private: break; } case OpCode::Type::HalfSetPredicate: { - ASSERT_MSG(instr.hsetp2.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.hsetp2.ftz != 0); const std::string op_a = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hsetp2.type_a, @@ -3178,6 +3083,8 @@ private: break; } case OpCode::Type::PredicateSetRegister: { + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "PSET Generates an unhandled Control Code"); + const std::string op_a = GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0); const std::string op_b = @@ -3198,12 +3105,6 @@ private: const std::string value = '(' + result + ") ? 1.0 : 0.0"; regs.SetRegisterToFloat(instr.gpr0, 0, value, 1, 1); } - - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "PSET Generates an unhandled Control Code"); - UNREACHABLE(); - } - break; } case OpCode::Type::PredicateSetPredicate: { @@ -3253,9 +3154,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}", - opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled predicate instruction: {}", opcode->get().GetName()); } } break; @@ -3335,7 +3234,7 @@ private: break; } case OpCode::Type::HalfSet: { - ASSERT_MSG(instr.hset2.ftz == 0, "Unimplemented"); + UNIMPLEMENTED_IF(instr.hset2.ftz != 0); const std::string op_a = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hset2.type_a, @@ -3379,15 +3278,16 @@ private: break; } case OpCode::Type::Xmad: { - ASSERT_MSG(!instr.xmad.sign_a, "Unimplemented"); - ASSERT_MSG(!instr.xmad.sign_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.xmad.sign_a); + UNIMPLEMENTED_IF(instr.xmad.sign_b); + UNIMPLEMENTED_IF_MSG(instr.generates_cc, "XMAD Generates an unhandled Control Code"); std::string op_a{regs.GetRegisterAsInteger(instr.gpr8, 0, instr.xmad.sign_a)}; std::string op_b; std::string op_c; // TODO(bunnei): Needs to be fixed once op_a or op_b is signed - ASSERT_MSG(instr.xmad.sign_a == instr.xmad.sign_b, "Unimplemented"); + UNIMPLEMENTED_IF(instr.xmad.sign_a != instr.xmad.sign_b); const bool is_signed{instr.xmad.sign_a == 1}; bool is_merge{}; @@ -3420,8 +3320,7 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->get().GetName()); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled XMAD instruction: {}", opcode->get().GetName()); } } @@ -3457,9 +3356,8 @@ private: op_c = "((" + op_c + ") + (" + src2 + "<< 16))"; break; default: { - LOG_CRITICAL(HW_GPU, "Unhandled XMAD mode: {}", - static_cast<u32>(instr.xmad.mode.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled XMAD mode: {}", + static_cast<u32>(instr.xmad.mode.Value())); } } @@ -3469,25 +3367,19 @@ private: } regs.SetRegisterToInteger(instr.gpr0, is_signed, 0, sum, 1, 1); - if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "XMAD Generates an unhandled Control Code"); - UNREACHABLE(); - } break; } default: { switch (opcode->get().GetId()) { case OpCode::Id::EXIT: { + const Tegra::Shader::ControlCode cc = instr.flow_control_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ControlCode::T, + "EXIT Control Code used: {}", static_cast<u32>(cc)); + if (stage == Maxwell3D::Regs::ShaderStage::Fragment) { EmitFragmentOutputsWrite(); } - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "EXIT Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } - switch (instr.flow.cond) { case Tegra::Shader::FlowCondition::Always: shader.AddLine("return true;"); @@ -3502,26 +3394,24 @@ private: case Tegra::Shader::FlowCondition::Fcsm_Tr: // TODO(bunnei): What is this used for? If we assume this conditon is not // satisifed, dual vertex shaders in Farming Simulator make more sense - LOG_CRITICAL(HW_GPU, "Skipping unknown FlowCondition::Fcsm_Tr"); + UNIMPLEMENTED_MSG("Skipping unknown FlowCondition::Fcsm_Tr"); break; default: - LOG_CRITICAL(HW_GPU, "Unhandled flow condition: {}", - static_cast<u32>(instr.flow.cond.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled flow condition: {}", + static_cast<u32>(instr.flow.cond.Value())); } break; } case OpCode::Id::KIL: { - ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); + UNIMPLEMENTED_IF(instr.flow.cond != Tegra::Shader::FlowCondition::Always); + + const Tegra::Shader::ControlCode cc = instr.flow_control_code; + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ControlCode::T, + "KIL Control Code used: {}", static_cast<u32>(cc)); // Enclose "discard" in a conditional, so that GLSL compilation does not complain // about unexecuted instructions that may follow this. - const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "KIL Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } shader.AddLine("if (true) {"); ++shader.scope; shader.AddLine("discard;"); @@ -3531,7 +3421,8 @@ private: break; } case OpCode::Id::OUT_R: { - ASSERT(instr.gpr20.Value() == Register::ZeroIndex); + UNIMPLEMENTED_IF_MSG(instr.gpr20.Value() != Register::ZeroIndex, + "Stream buffer is not supported"); ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, "OUT is expected to be used in a geometry shader."); @@ -3558,18 +3449,17 @@ private: break; } default: { - LOG_CRITICAL(HW_GPU, "Unhandled system move: {}", - static_cast<u32>(instr.sys20.Value())); - UNREACHABLE(); + UNIMPLEMENTED_MSG("Unhandled system move: {}", + static_cast<u32>(instr.sys20.Value())); } } break; } case OpCode::Id::ISBERD: { - ASSERT(instr.isberd.o == 0); - ASSERT(instr.isberd.skew == 0); - ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None); - ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None); + UNIMPLEMENTED_IF(instr.isberd.o != 0); + UNIMPLEMENTED_IF(instr.isberd.skew != 0); + UNIMPLEMENTED_IF(instr.isberd.shift != Tegra::Shader::IsberdShift::None); + UNIMPLEMENTED_IF(instr.isberd.mode != Tegra::Shader::IsberdMode::None); ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry, "ISBERD is expected to be used in a geometry shader."); LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete"); @@ -3577,13 +3467,13 @@ private: break; } case OpCode::Id::BRA: { - ASSERT_MSG(instr.bra.constant_buffer == 0, - "BRA with constant buffers are not implemented"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "BRA with constant buffers are not implemented"); + const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "BRA Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ControlCode::T, + "BRA Control Code used: {}", static_cast<u32>(cc)); + const u32 target = offset + instr.bra.GetBranchTarget(); shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); break; @@ -3606,7 +3496,8 @@ private: // The SSY opcode tells the GPU where to re-converge divergent execution paths, it // sets the target of the jump that the SYNC instruction will make. The SSY opcode // has a similar structure to the BRA opcode. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer flow is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); EmitPushToFlowStack(target); @@ -3616,19 +3507,19 @@ private: // PBK pushes to a stack the address where BRK will jump to. This shares stack with // SSY but using SYNC on a PBK address will kill the shader execution. We don't // emulate this because it's very unlikely a driver will emit such invalid shader. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported"); + UNIMPLEMENTED_IF_MSG(instr.bra.constant_buffer != 0, + "Constant buffer PBK is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); EmitPushToFlowStack(target); break; } case OpCode::Id::SYNC: { - // The SYNC opcode jumps to the address previously set by the SSY opcode const Tegra::Shader::ControlCode cc = instr.flow_control_code; - if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "SYNC Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); - } + UNIMPLEMENTED_IF_MSG(cc != Tegra::Shader::ControlCode::T, + "SYNC Control Code used: {}", static_cast<u32>(cc)); + + // The SYNC opcode jumps to the address previously set by the SSY opcode EmitPopFromFlowStack(); break; } @@ -3636,8 +3527,7 @@ private: // The BRK opcode jumps to the address previously set by the PBK opcode const Tegra::Shader::ControlCode cc = instr.flow_control_code; if (cc != Tegra::Shader::ControlCode::T) { - LOG_CRITICAL(HW_GPU, "BRK Control Code used: {}", static_cast<u32>(cc)); - UNREACHABLE(); + UNIMPLEMENTED_MSG("BRK Control Code used: {}", static_cast<u32>(cc)); } EmitPopFromFlowStack(); break; @@ -3669,8 +3559,7 @@ private: instr.vmad.saturate == 1, 0, Register::Size::Word, instr.vmad.cc); if (instr.generates_cc) { - LOG_CRITICAL(HW_GPU, "VMAD Generates an unhandled Control Code"); - UNREACHABLE(); + UNIMPLEMENTED_MSG("VMAD Generates an unhandled Control Code"); } break; @@ -3699,10 +3588,7 @@ private: } break; } - default: { - LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->get().GetName()); - UNREACHABLE(); - } + default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); } } break; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index ea38da932..27b5b8960 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -304,6 +304,12 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, gl_framebuffer_data.resize(texture.width * texture.height * 4); break; default: + internal_format = GL_RGBA; + texture.gl_format = GL_RGBA; + texture.gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + gl_framebuffer_data.resize(texture.width * texture.height * 4); + LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer pixel format: {}", + static_cast<u32>(framebuffer.pixel_format)); UNREACHABLE(); } diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f9ca2948e..cfca8f4a8 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -7,6 +7,8 @@ add_executable(yuzu Info.plist about_dialog.cpp about_dialog.h + applets/software_keyboard.cpp + applets/software_keyboard.h bootmanager.cpp bootmanager.h compatibility_list.cpp @@ -27,8 +29,14 @@ add_executable(yuzu configuration/configure_graphics.h configuration/configure_input.cpp configuration/configure_input.h + configuration/configure_input_player.cpp + configuration/configure_input_player.h + configuration/configure_mouse_advanced.cpp + configuration/configure_mouse_advanced.h configuration/configure_system.cpp configuration/configure_system.h + configuration/configure_touchscreen_advanced.cpp + configuration/configure_touchscreen_advanced.h configuration/configure_web.cpp configuration/configure_web.h debugger/graphics/graphics_breakpoint_observer.cpp @@ -76,7 +84,10 @@ set(UIS configuration/configure_general.ui configuration/configure_graphics.ui configuration/configure_input.ui + configuration/configure_input_player.ui + configuration/configure_mouse_advanced.ui configuration/configure_system.ui + configuration/configure_touchscreen_advanced.ui configuration/configure_web.ui hotkeys.ui main.ui diff --git a/src/yuzu/applets/software_keyboard.cpp b/src/yuzu/applets/software_keyboard.cpp new file mode 100644 index 000000000..efefb1f99 --- /dev/null +++ b/src/yuzu/applets/software_keyboard.cpp @@ -0,0 +1,152 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <mutex> +#include <QDialogButtonBox> +#include <QFont> +#include <QLabel> +#include <QLineEdit> +#include <QVBoxLayout> +#include "core/hle/lock.h" +#include "yuzu/applets/software_keyboard.h" +#include "yuzu/main.h" + +QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator( + Core::Frontend::SoftwareKeyboardParameters parameters) + : parameters(std::move(parameters)) {} + +QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const { + if (input.size() > parameters.max_length) + return Invalid; + if (parameters.disable_space && input.contains(' ')) + return Invalid; + if (parameters.disable_address && input.contains('@')) + return Invalid; + if (parameters.disable_percent && input.contains('%')) + return Invalid; + if (parameters.disable_slash && (input.contains('/') || input.contains('\\'))) + return Invalid; + if (parameters.disable_number && + std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) { + return Invalid; + } + + if (parameters.disable_download_code && + std::any_of(input.begin(), input.end(), [](QChar c) { return c == 'O' || c == 'I'; })) { + return Invalid; + } + + return Acceptable; +} + +QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog( + QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_) + : QDialog(parent), parameters(std::move(parameters_)) { + layout = new QVBoxLayout; + + header_label = new QLabel(QString::fromStdU16String(parameters.header_text)); + header_label->setFont({header_label->font().family(), 11, QFont::Bold}); + if (header_label->text().isEmpty()) + header_label->setText(tr("Enter text:")); + + sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text)); + sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(), + sub_label->font().weight(), true}); + sub_label->setHidden(parameters.sub_text.empty()); + + guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text)); + guide_label->setHidden(parameters.guide_text.empty()); + + length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length)); + length_label->setAlignment(Qt::AlignRight); + length_label->setFont({length_label->font().family(), 8}); + + line_edit = new QLineEdit; + line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters)); + line_edit->setMaxLength(static_cast<int>(parameters.max_length)); + line_edit->setText(QString::fromStdU16String(parameters.initial_text)); + line_edit->setCursorPosition( + parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size())); + line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal); + + connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) { + length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length)); + }); + + buttons = new QDialogButtonBox; + buttons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); + buttons->addButton(parameters.submit_text.empty() + ? tr("OK") + : QString::fromStdU16String(parameters.submit_text), + QDialogButtonBox::AcceptRole); + + connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::Submit); + connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::Reject); + layout->addWidget(header_label); + layout->addWidget(sub_label); + layout->addWidget(guide_label); + layout->addWidget(length_label); + layout->addWidget(line_edit); + layout->addWidget(buttons); + setLayout(layout); + setWindowTitle(tr("Software Keyboard")); +} + +QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default; + +void QtSoftwareKeyboardDialog::Submit() { + ok = true; + text = line_edit->text().toStdU16String(); + accept(); +} + +void QtSoftwareKeyboardDialog::Reject() { + ok = false; + text.clear(); + accept(); +} + +std::u16string QtSoftwareKeyboardDialog::GetText() const { + return text; +} + +bool QtSoftwareKeyboardDialog::GetStatus() const { + return ok; +} + +QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) { + connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window, + &GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection); + connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window, + &GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection); + connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this, + &QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection); +} + +QtSoftwareKeyboard::~QtSoftwareKeyboard() = default; + +void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out, + Core::Frontend::SoftwareKeyboardParameters parameters) const { + text_output = out; + emit MainWindowGetText(parameters); +} + +void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const { + this->finished_check = finished_check; + emit MainWindowTextCheckDialog(error_message); +} + +void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + text_output(text); +} + +void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() { + // Acquire the HLE mutex + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + finished_check(); +} diff --git a/src/yuzu/applets/software_keyboard.h b/src/yuzu/applets/software_keyboard.h new file mode 100644 index 000000000..73f56714f --- /dev/null +++ b/src/yuzu/applets/software_keyboard.h @@ -0,0 +1,80 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <QDialog> +#include <QValidator> +#include "common/assert.h" +#include "core/frontend/applets/software_keyboard.h" + +class GMainWindow; +class QDialogButtonBox; +class QLabel; +class QLineEdit; +class QVBoxLayout; +class QtSoftwareKeyboard; + +class QtSoftwareKeyboardValidator final : public QValidator { +public: + explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters); + State validate(QString& input, int& pos) const override; + +private: + Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboardDialog final : public QDialog { + Q_OBJECT + +public: + QtSoftwareKeyboardDialog(QWidget* parent, + Core::Frontend::SoftwareKeyboardParameters parameters); + ~QtSoftwareKeyboardDialog() override; + + void Submit(); + void Reject(); + + std::u16string GetText() const; + bool GetStatus() const; + +private: + bool ok = false; + std::u16string text; + + QDialogButtonBox* buttons; + QLabel* header_label; + QLabel* sub_label; + QLabel* guide_label; + QLabel* length_label; + QLineEdit* line_edit; + QVBoxLayout* layout; + + Core::Frontend::SoftwareKeyboardParameters parameters; +}; + +class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet { + Q_OBJECT + +public: + explicit QtSoftwareKeyboard(GMainWindow& parent); + ~QtSoftwareKeyboard() override; + + void RequestText(std::function<void(std::optional<std::u16string>)> out, + Core::Frontend::SoftwareKeyboardParameters parameters) const override; + void SendTextCheckDialog(std::u16string error_message, + std::function<void()> finished_check) const override; + +signals: + void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const; + void MainWindowTextCheckDialog(std::u16string error_message) const; + +public slots: + void MainWindowFinishedText(std::optional<std::u16string> text); + void MainWindowFinishedCheckDialog(); + +private: + mutable std::function<void(std::optional<std::u16string>)> text_output; + mutable std::function<void()> finished_check; +}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index be69fb831..e24ed5f2b 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -5,6 +5,7 @@ #include <QSettings> #include "common/file_util.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/hid/controllers/npad.h" #include "input_common/main.h" #include "yuzu/configuration/config.h" #include "yuzu/ui_settings.h" @@ -47,40 +48,313 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config: }, }}; -void Config::ReadValues() { - qt_config->beginGroup("Controls"); +const std::array<int, Settings::NativeMouseButton::NumMouseButtons> Config::default_mouse_buttons = + { + Qt::Key_BracketLeft, Qt::Key_BracketRight, Qt::Key_Apostrophe, Qt::Key_Minus, Qt::Key_Equal, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> Config::default_keyboard_keys = { + 0, + 0, + 0, + 0, + Qt::Key_A, + Qt::Key_B, + Qt::Key_C, + Qt::Key_D, + Qt::Key_E, + Qt::Key_F, + Qt::Key_G, + Qt::Key_H, + Qt::Key_I, + Qt::Key_J, + Qt::Key_K, + Qt::Key_L, + Qt::Key_M, + Qt::Key_N, + Qt::Key_O, + Qt::Key_P, + Qt::Key_Q, + Qt::Key_R, + Qt::Key_S, + Qt::Key_T, + Qt::Key_U, + Qt::Key_V, + Qt::Key_W, + Qt::Key_X, + Qt::Key_Y, + Qt::Key_Z, + Qt::Key_1, + Qt::Key_2, + Qt::Key_3, + Qt::Key_4, + Qt::Key_5, + Qt::Key_6, + Qt::Key_7, + Qt::Key_8, + Qt::Key_9, + Qt::Key_0, + Qt::Key_Enter, + Qt::Key_Escape, + Qt::Key_Backspace, + Qt::Key_Tab, + Qt::Key_Space, + Qt::Key_Minus, + Qt::Key_Equal, + Qt::Key_BracketLeft, + Qt::Key_BracketRight, + Qt::Key_Backslash, + Qt::Key_Dead_Tilde, + Qt::Key_Semicolon, + Qt::Key_Apostrophe, + Qt::Key_Dead_Grave, + Qt::Key_Comma, + Qt::Key_Period, + Qt::Key_Slash, + Qt::Key_CapsLock, + + Qt::Key_F1, + Qt::Key_F2, + Qt::Key_F3, + Qt::Key_F4, + Qt::Key_F5, + Qt::Key_F6, + Qt::Key_F7, + Qt::Key_F8, + Qt::Key_F9, + Qt::Key_F10, + Qt::Key_F11, + Qt::Key_F12, + + Qt::Key_SysReq, + Qt::Key_ScrollLock, + Qt::Key_Pause, + Qt::Key_Insert, + Qt::Key_Home, + Qt::Key_PageUp, + Qt::Key_Delete, + Qt::Key_End, + Qt::Key_PageDown, + Qt::Key_Right, + Qt::Key_Left, + Qt::Key_Down, + Qt::Key_Up, + + Qt::Key_NumLock, + Qt::Key_Slash, + Qt::Key_Asterisk, + Qt::Key_Minus, + Qt::Key_Plus, + Qt::Key_Enter, + Qt::Key_1, + Qt::Key_2, + Qt::Key_3, + Qt::Key_4, + Qt::Key_5, + Qt::Key_6, + Qt::Key_7, + Qt::Key_8, + Qt::Key_9, + Qt::Key_0, + Qt::Key_Period, + + 0, + 0, + Qt::Key_PowerOff, + Qt::Key_Equal, + + Qt::Key_F13, + Qt::Key_F14, + Qt::Key_F15, + Qt::Key_F16, + Qt::Key_F17, + Qt::Key_F18, + Qt::Key_F19, + Qt::Key_F20, + Qt::Key_F21, + Qt::Key_F22, + Qt::Key_F23, + Qt::Key_F24, + + Qt::Key_Open, + Qt::Key_Help, + Qt::Key_Menu, + 0, + Qt::Key_Stop, + Qt::Key_AudioRepeat, + Qt::Key_Undo, + Qt::Key_Cut, + Qt::Key_Copy, + Qt::Key_Paste, + Qt::Key_Find, + Qt::Key_VolumeMute, + Qt::Key_VolumeUp, + Qt::Key_VolumeDown, + Qt::Key_CapsLock, + Qt::Key_NumLock, + Qt::Key_ScrollLock, + Qt::Key_Comma, + + Qt::Key_ParenLeft, + Qt::Key_ParenRight, +}; + +const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default_keyboard_mods = { + Qt::Key_Control, Qt::Key_Shift, Qt::Key_Alt, Qt::Key_ApplicationLeft, + Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, +}; + +void Config::ReadPlayerValues() { + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + Settings::values.players[p].connected = + qt_config->value(QString("player_%1_connected").arg(p), false).toBool(); + + Settings::values.players[p].type = static_cast<Settings::ControllerType>( + qt_config + ->value(QString("player_%1_type").arg(p), + static_cast<u8>(Settings::ControllerType::DualJoycon)) + .toUInt()); + + Settings::values.players[p].body_color_left = + qt_config + ->value(QString("player_%1_body_color_left").arg(p), + Settings::JOYCON_BODY_NEON_BLUE) + .toUInt(); + Settings::values.players[p].body_color_right = + qt_config + ->value(QString("player_%1_body_color_right").arg(p), + Settings::JOYCON_BODY_NEON_RED) + .toUInt(); + Settings::values.players[p].button_color_left = + qt_config + ->value(QString("player_%1_button_color_left").arg(p), + Settings::JOYCON_BUTTONS_NEON_BLUE) + .toUInt(); + Settings::values.players[p].button_color_right = + qt_config + ->value(QString("player_%1_button_color_right").arg(p), + Settings::JOYCON_BUTTONS_NEON_RED) + .toUInt(); + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.players[p].buttons[i] = + qt_config + ->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.players[p].buttons[i].empty()) + Settings::values.players[p].buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.players[p].analogs[i] = + qt_config + ->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.players[p].analogs[i].empty()) + Settings::values.players[p].analogs[i] = default_param; + } + } + + std::stable_partition( + Settings::values.players.begin(), + Settings::values.players.begin() + + Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), + [](const auto& player) { return player.connected; }); +} + +void Config::ReadDebugValues() { + Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool(); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.buttons[i] = + Settings::values.debug_pad_buttons[i] = qt_config - ->value(Settings::NativeButton::mapping[i], QString::fromStdString(default_param)) + ->value(QString("debug_pad_") + Settings::NativeButton::mapping[i], + QString::fromStdString(default_param)) .toString() .toStdString(); - if (Settings::values.buttons[i].empty()) - Settings::values.buttons[i] = default_param; + if (Settings::values.debug_pad_buttons[i].empty()) + Settings::values.debug_pad_buttons[i] = default_param; } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.analogs[i] = + Settings::values.debug_pad_analogs[i] = qt_config - ->value(Settings::NativeAnalog::mapping[i], QString::fromStdString(default_param)) + ->value(QString("debug_pad_") + Settings::NativeAnalog::mapping[i], + QString::fromStdString(default_param)) .toString() .toStdString(); - if (Settings::values.analogs[i].empty()) - Settings::values.analogs[i] = default_param; + if (Settings::values.debug_pad_analogs[i].empty()) + Settings::values.debug_pad_analogs[i] = default_param; } +} + +void Config::ReadKeyboardValues() { + Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool(); + + std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(), + Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); + std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), + Settings::values.keyboard_keys.begin() + + Settings::NativeKeyboard::LeftControlKey, + InputCommon::GenerateKeyboardParam); + std::transform(default_keyboard_mods.begin(), default_keyboard_mods.end(), + Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); +} + +void Config::ReadMouseValues() { + Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool(); + + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); + Settings::values.mouse_buttons[i] = + qt_config + ->value(QString("mouse_") + Settings::NativeMouseButton::mapping[i], + QString::fromStdString(default_param)) + .toString() + .toStdString(); + if (Settings::values.mouse_buttons[i].empty()) + Settings::values.mouse_buttons[i] = default_param; + } +} + +void Config::ReadTouchscreenValues() { + Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool(); + Settings::values.touchscreen.device = + qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString(); + + Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt(); + Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt(); + Settings::values.touchscreen.diameter_x = + qt_config->value("touchscreen_diameter_x", 15).toUInt(); + Settings::values.touchscreen.diameter_y = + qt_config->value("touchscreen_diameter_y", 15).toUInt(); + qt_config->endGroup(); +} + +void Config::ReadValues() { + qt_config->beginGroup("Controls"); + + ReadPlayerValues(); + ReadDebugValues(); + ReadKeyboardValues(); + ReadMouseValues(); + ReadTouchscreenValues(); Settings::values.motion_device = qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") .toString() .toStdString(); - Settings::values.touch_device = - qt_config->value("touch_device", "engine:emu_window").toString().toStdString(); - - qt_config->endGroup(); qt_config->beginGroup("Core"); Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); @@ -126,6 +400,11 @@ void Config::ReadValues() { .toStdString()); qt_config->endGroup(); + qt_config->beginGroup("Core"); + Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool(); + Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool(); + qt_config->endGroup(); + qt_config->beginGroup("System"); Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); @@ -230,18 +509,81 @@ void Config::ReadValues() { qt_config->endGroup(); } -void Config::SaveValues() { - qt_config->beginGroup("Controls"); +void Config::SavePlayerValues() { + for (int p = 0; p < Settings::values.players.size(); ++p) { + qt_config->setValue(QString("player_%1_connected").arg(p), + Settings::values.players[p].connected); + qt_config->setValue(QString("player_%1_type").arg(p), + static_cast<u8>(Settings::values.players[p].type)); + + qt_config->setValue(QString("player_%1_body_color_left").arg(p), + Settings::values.players[p].body_color_left); + qt_config->setValue(QString("player_%1_body_color_right").arg(p), + Settings::values.players[p].body_color_right); + qt_config->setValue(QString("player_%1_button_color_left").arg(p), + Settings::values.players[p].button_color_left); + qt_config->setValue(QString("player_%1_button_color_right").arg(p), + Settings::values.players[p].button_color_right); + + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + qt_config->setValue(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.players[p].buttons[i])); + } + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + qt_config->setValue(QString("player_%1_").arg(p) + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.players[p].analogs[i])); + } + } +} + +void Config::SaveDebugValues() { + qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeButton::mapping[i]), - QString::fromStdString(Settings::values.buttons[i])); + qt_config->setValue(QString("debug_pad_") + + QString::fromStdString(Settings::NativeButton::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_buttons[i])); } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { - qt_config->setValue(QString::fromStdString(Settings::NativeAnalog::mapping[i]), - QString::fromStdString(Settings::values.analogs[i])); + qt_config->setValue(QString("debug_pad_") + + QString::fromStdString(Settings::NativeAnalog::mapping[i]), + QString::fromStdString(Settings::values.debug_pad_analogs[i])); } +} + +void Config::SaveMouseValues() { + qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled); + + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + qt_config->setValue(QString("mouse_") + + QString::fromStdString(Settings::NativeMouseButton::mapping[i]), + QString::fromStdString(Settings::values.mouse_buttons[i])); + } +} + +void Config::SaveTouchscreenValues() { + qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled); + qt_config->setValue("touchscreen_device", + QString::fromStdString(Settings::values.touchscreen.device)); + + qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger); + qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle); + qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x); + qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y); +} + +void Config::SaveValues() { + qt_config->beginGroup("Controls"); + + SavePlayerValues(); + SaveDebugValues(); + SaveMouseValues(); + SaveTouchscreenValues(); + qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); - qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device)); + qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled); + qt_config->endGroup(); qt_config->beginGroup("Core"); @@ -280,7 +622,6 @@ void Config::SaveValues() { qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); qt_config->setValue("enable_nfc", Settings::values.enable_nfc); qt_config->setValue("current_user", Settings::values.current_user); - qt_config->setValue("language_index", Settings::values.language_index); qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value()); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 9c99c1b75..a1c27bbf9 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -22,10 +22,24 @@ public: static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; + static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> + default_mouse_buttons; + static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys; + static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods; private: void ReadValues(); + void ReadPlayerValues(); + void ReadDebugValues(); + void ReadKeyboardValues(); + void ReadMouseValues(); + void ReadTouchscreenValues(); + void SaveValues(); + void SavePlayerValues(); + void SaveDebugValues(); + void SaveMouseValues(); + void SaveTouchscreenValues(); std::unique_ptr<QSettings> qt_config; std::string qt_config_loc; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index c22742007..92a441308 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -3,10 +3,6 @@ // Refer to the license.txt file included. #include "core/core.h" -#include "core/hle/service/am/am.h" -#include "core/hle/service/am/applet_ae.h" -#include "core/hle/service/am/applet_oe.h" -#include "core/hle/service/sm/sm.h" #include "core/settings.h" #include "ui_configure_general.h" #include "yuzu/configuration/configure_general.h" @@ -36,7 +32,6 @@ void ConfigureGeneral::setConfiguration() { ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); - ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); ui->enable_nfc->setChecked(Settings::values.enable_nfc); } @@ -44,33 +39,6 @@ void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { ui->widget->Populate(registry); } -void ConfigureGeneral::OnDockedModeChanged(bool last_state, bool new_state) { - if (last_state == new_state) { - return; - } - - Core::System& system{Core::System::GetInstance()}; - if (!system.IsPoweredOn()) { - return; - } - Service::SM::ServiceManager& sm = system.ServiceManager(); - - // Message queue is shared between these services, we just need to signal an operation - // change to one and it will handle both automatically - auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); - auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); - bool has_signalled = false; - - if (applet_oe != nullptr) { - applet_oe->GetMessageQueue()->OperationModeChanged(); - has_signalled = true; - } - - if (applet_ae != nullptr && !has_signalled) { - applet_ae->GetMessageQueue()->OperationModeChanged(); - } -} - void ConfigureGeneral::applyConfiguration() { UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); @@ -78,9 +46,5 @@ void ConfigureGeneral::applyConfiguration() { ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); - const bool pre_docked_mode = Settings::values.use_docked_mode; - Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); - OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); - Settings::values.enable_nfc = ui->enable_nfc->isChecked(); } diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 2210d48da..4770034cc 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h @@ -25,7 +25,6 @@ public: private: void setConfiguration(); - void OnDockedModeChanged(bool last_state, bool new_state); std::unique_ptr<Ui::ConfigureGeneral> ui; }; diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index b82fffde8..bf37446c6 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>300</width> - <height>377</height> + <height>407</height> </rect> </property> <property name="windowTitle"> @@ -72,13 +72,6 @@ <item> <layout class="QVBoxLayout" name="EmulationVerticalLayout"> <item> - <widget class="QCheckBox" name="use_docked_mode"> - <property name="text"> - <string>Enable docked mode</string> - </property> - </widget> - </item> - <item> <widget class="QCheckBox" name="enable_nfc"> <property name="text"> <string>Enable NFC</string> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 42a7beac6..7ee572761 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -9,334 +9,202 @@ #include <QMessageBox> #include <QTimer> #include "common/param_package.h" +#include "configuration/configure_touchscreen_advanced.h" +#include "core/core.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applet_ae.h" +#include "core/hle/service/am/applet_oe.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/sm/sm.h" #include "input_common/main.h" +#include "ui_configure_input.h" +#include "ui_configure_input_player.h" +#include "ui_configure_mouse_advanced.h" +#include "ui_configure_touchscreen_advanced.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_input_player.h" +#include "yuzu/configuration/configure_mouse_advanced.h" -const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM> - ConfigureInput::analog_sub_buttons{{ - "up", - "down", - "left", - "right", - "modifier", - }}; - -static QString getKeyName(int key_code) { - switch (key_code) { - case Qt::Key_Shift: - return QObject::tr("Shift"); - case Qt::Key_Control: - return QObject::tr("Ctrl"); - case Qt::Key_Alt: - return QObject::tr("Alt"); - case Qt::Key_Meta: - return ""; - default: - return QKeySequence(key_code).toString(); - } -} +ConfigureInput::ConfigureInput(QWidget* parent) + : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) { + ui->setupUi(this); -static void SetAnalogButton(const Common::ParamPackage& input_param, - Common::ParamPackage& analog_param, const std::string& button_name) { - if (analog_param.Get("engine", "") != "analog_from_button") { - analog_param = { - {"engine", "analog_from_button"}, - {"modifier_scale", "0.5"}, - }; - } - analog_param.Set(button_name, input_param.Serialize()); -} + players_controller = { + ui->player1_combobox, ui->player2_combobox, ui->player3_combobox, ui->player4_combobox, + ui->player5_combobox, ui->player6_combobox, ui->player7_combobox, ui->player8_combobox, + }; -static QString ButtonToText(const Common::ParamPackage& param) { - if (!param.Has("engine")) { - return QObject::tr("[not set]"); - } else if (param.Get("engine", "") == "keyboard") { - return getKeyName(param.Get("code", 0)); - } else if (param.Get("engine", "") == "sdl") { - if (param.Has("hat")) { - return QString(QObject::tr("Hat %1 %2")) - .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); - } - if (param.Has("axis")) { - return QString(QObject::tr("Axis %1%2")) - .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); - } - if (param.Has("button")) { - return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); - } - return QString(); - } else { - return QObject::tr("[unknown]"); - } -}; - -static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { - if (!param.Has("engine")) { - return QObject::tr("[not set]"); - } else if (param.Get("engine", "") == "analog_from_button") { - return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); - } else if (param.Get("engine", "") == "sdl") { - if (dir == "modifier") { - return QString(QObject::tr("[unused]")); - } + players_configure = { + ui->player1_configure, ui->player2_configure, ui->player3_configure, ui->player4_configure, + ui->player5_configure, ui->player6_configure, ui->player7_configure, ui->player8_configure, + }; - if (dir == "left" || dir == "right") { - return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); - } else if (dir == "up" || dir == "down") { - return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); - } - return QString(); - } else { - return QObject::tr("[unknown]"); + for (auto* controller_box : players_controller) { + controller_box->addItems({"None", "Pro Controller", "Dual Joycons", "Single Right Joycon", + "Single Left Joycon"}); } -}; -ConfigureInput::ConfigureInput(QWidget* parent) - : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), - timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { + this->loadConfiguration(); + updateUIEnabled(); - ui->setupUi(this); - setFocusPolicy(Qt::ClickFocus); - - button_map = { - ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, - ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, - ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, - ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, - ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, - ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, - ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, - }; + connect(ui->restore_defaults_button, &QPushButton::pressed, this, + &ConfigureInput::restoreDefaults); - analog_map_buttons = {{ - { - ui->buttonLStickUp, - ui->buttonLStickDown, - ui->buttonLStickLeft, - ui->buttonLStickRight, - ui->buttonLStickMod, - }, - { - ui->buttonRStickUp, - ui->buttonRStickDown, - ui->buttonRStickLeft, - ui->buttonRStickRight, - ui->buttonRStickMod, - }, - }}; - - analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; - - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (!button_map[button_id]) - continue; - button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); - connect(button_map[button_id], &QPushButton::released, [=]() { - handleClick( - button_map[button_id], - [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, - InputCommon::Polling::DeviceType::Button); - }); - connect(button_map[button_id], &QPushButton::customContextMenuRequested, - [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - buttons_param[button_id].Clear(); - button_map[button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; - button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); - }); - context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); - }); - } + for (auto* enabled : players_controller) + connect(enabled, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureInput::updateUIEnabled); + connect(ui->use_docked_mode, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->handheld_connected, &QCheckBox::stateChanged, this, + &ConfigureInput::updateUIEnabled); + connect(ui->mouse_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->keyboard_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->debug_enabled, &QCheckBox::stateChanged, this, &ConfigureInput::updateUIEnabled); + connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this, + &ConfigureInput::updateUIEnabled); - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (!analog_map_buttons[analog_id][sub_button_id]) - continue; - analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( - Qt::CustomContextMenu); - connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { - handleClick(analog_map_buttons[analog_id][sub_button_id], - [=](const Common::ParamPackage& params) { - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - }, - InputCommon::Polling::DeviceType::Button); - }); - connect(analog_map_buttons[analog_id][sub_button_id], - &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { - QMenu context_menu; - context_menu.addAction(tr("Clear"), [&] { - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); - }); - context_menu.addAction(tr("Restore Default"), [&] { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], - analog_sub_buttons[sub_button_id]); - analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( - analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - }); - context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( - menu_location)); - }); - } - connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { - QMessageBox::information(this, tr("Information"), - tr("After pressing OK, first move your joystick horizontally, " - "and then vertically.")); - handleClick( - analog_map_stick[analog_id], - [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, - InputCommon::Polling::DeviceType::Analog); - }); + for (std::size_t i = 0; i < players_configure.size(); ++i) { + connect(players_configure[i], &QPushButton::pressed, this, + [this, i]() { CallConfigureDialog<ConfigureInputPlayer>(i, false); }); } - connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); - connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); - - timeout_timer->setSingleShot(true); - connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + connect(ui->handheld_configure, &QPushButton::pressed, this, + [this]() { CallConfigureDialog<ConfigureInputPlayer>(8, false); }); - connect(poll_timer.get(), &QTimer::timeout, [this]() { - Common::ParamPackage params; - for (auto& poller : device_pollers) { - params = poller->GetNextInput(); - if (params.Has("engine")) { - setPollingResult(params, false); - return; - } - } - }); + connect(ui->debug_configure, &QPushButton::pressed, this, + [this]() { CallConfigureDialog<ConfigureInputPlayer>(9, true); }); - this->loadConfiguration(); + connect(ui->mouse_advanced, &QPushButton::pressed, this, + [this]() { CallConfigureDialog<ConfigureMouseAdvanced>(); }); - // TODO(wwylele): enable this when we actually emulate it - ui->buttonHome->setEnabled(false); + connect(ui->touchscreen_advanced, &QPushButton::pressed, this, + [this]() { CallConfigureDialog<ConfigureTouchscreenAdvanced>(); }); } -void ConfigureInput::applyConfiguration() { - std::transform(buttons_param.begin(), buttons_param.end(), Settings::values.buttons.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); - std::transform(analogs_param.begin(), analogs_param.end(), Settings::values.analogs.begin(), - [](const Common::ParamPackage& param) { return param.Serialize(); }); -} +template <typename Dialog, typename... Args> +void ConfigureInput::CallConfigureDialog(Args&&... args) { + this->applyConfiguration(); + Dialog dialog(this, std::forward<Args>(args)...); -void ConfigureInput::loadConfiguration() { - std::transform(Settings::values.buttons.begin(), Settings::values.buttons.end(), - buttons_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - std::transform(Settings::values.analogs.begin(), Settings::values.analogs.end(), - analogs_param.begin(), - [](const std::string& str) { return Common::ParamPackage(str); }); - updateButtonLabels(); + const auto res = dialog.exec(); + if (res == QDialog::Accepted) { + dialog.applyConfiguration(); + } } -void ConfigureInput::restoreDefaults() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) { + if (last_state == new_state) { + return; } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; - SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); - } + Core::System& system{Core::System::GetInstance()}; + if (!system.IsPoweredOn()) { + return; } - updateButtonLabels(); -} + Service::SM::ServiceManager& sm = system.ServiceManager(); -void ConfigureInput::ClearAll() { - for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { - if (button_map[button_id] && button_map[button_id]->isEnabled()) - buttons_param[button_id].Clear(); - } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (analog_map_buttons[analog_id][sub_button_id] && - analog_map_buttons[analog_id][sub_button_id]->isEnabled()) - analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); - } - } - updateButtonLabels(); -} + // Message queue is shared between these services, we just need to signal an operation + // change to one and it will handle both automatically + auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE"); + auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE"); + bool has_signalled = false; -void ConfigureInput::updateButtonLabels() { - for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { - button_map[button]->setText(ButtonToText(buttons_param[button])); + if (applet_oe != nullptr) { + applet_oe->GetMessageQueue()->OperationModeChanged(); + has_signalled = true; } - for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { - for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { - if (analog_map_buttons[analog_id][sub_button_id]) { - analog_map_buttons[analog_id][sub_button_id]->setText( - AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); - } - } - analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + if (applet_ae != nullptr && !has_signalled) { + applet_ae->GetMessageQueue()->OperationModeChanged(); } } -void ConfigureInput::handleClick(QPushButton* button, - std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type) { - button->setText(tr("[press key]")); - button->setFocus(); - - input_setter = new_input_setter; - - device_pollers = InputCommon::Polling::GetPollers(type); +void ConfigureInput::applyConfiguration() { + for (std::size_t i = 0; i < players_controller.size(); ++i) { + const auto controller_type_index = players_controller[i]->currentIndex(); - // Keyboard keys can only be used as button devices - want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + Settings::values.players[i].connected = controller_type_index != 0; - for (auto& poller : device_pollers) { - poller->Start(); + if (controller_type_index > 0) { + Settings::values.players[i].type = + static_cast<Settings::ControllerType>(controller_type_index - 1); + } else { + Settings::values.players[i].type = Settings::ControllerType::DualJoycon; + } } - grabKeyboard(); - grabMouse(); - timeout_timer->start(5000); // Cancel after 5 seconds - poll_timer->start(200); // Check for new inputs every 200ms + const bool pre_docked_mode = Settings::values.use_docked_mode; + Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); + OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode); + Settings::values + .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] + .connected = ui->handheld_connected->isChecked(); + Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked(); + Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); + Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); + Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); } -void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool abort) { - releaseKeyboard(); - releaseMouse(); - timeout_timer->stop(); - poll_timer->stop(); - for (auto& poller : device_pollers) { - poller->Stop(); +void ConfigureInput::updateUIEnabled() { + bool hit_disabled = false; + for (auto* player : players_controller) { + player->setDisabled(hit_disabled); + if (hit_disabled) + player->setCurrentIndex(0); + if (!hit_disabled && player->currentIndex() == 0) + hit_disabled = true; } - if (!abort) { - (*input_setter)(params); + for (std::size_t i = 0; i < players_controller.size(); ++i) { + players_configure[i]->setEnabled(players_controller[i]->currentIndex() != 0); } - updateButtonLabels(); - input_setter = {}; + ui->handheld_connected->setEnabled(!ui->use_docked_mode->isChecked()); + ui->handheld_configure->setEnabled(ui->handheld_connected->isChecked() && + !ui->use_docked_mode->isChecked()); + ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked()); + ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); + ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); } -void ConfigureInput::keyPressEvent(QKeyEvent* event) { - if (!input_setter || !event) - return; +void ConfigureInput::loadConfiguration() { + std::stable_partition( + Settings::values.players.begin(), + Settings::values.players.begin() + + Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD), + [](const auto& player) { return player.connected; }); + + for (std::size_t i = 0; i < players_controller.size(); ++i) { + const auto connected = Settings::values.players[i].connected; + players_controller[i]->setCurrentIndex( + connected ? static_cast<u8>(Settings::values.players[i].type) + 1 : 0); + } + + ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); + ui->handheld_connected->setChecked( + Settings::values + .players[Service::HID::Controller_NPad::NPadIdToIndex(Service::HID::NPAD_HANDHELD)] + .connected); + ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled); + ui->mouse_enabled->setChecked(Settings::values.mouse_enabled); + ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled); + ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); + + updateUIEnabled(); +} - if (event->key() != Qt::Key_Escape) { - if (want_keyboard_keys) { - setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, - false); - } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling - return; - } +void ConfigureInput::restoreDefaults() { + players_controller[0]->setCurrentIndex(2); + + for (std::size_t i = 1; i < players_controller.size(); ++i) { + players_controller[i]->setCurrentIndex(0); } - setPollingResult({}, true); + + ui->use_docked_mode->setCheckState(Qt::Unchecked); + ui->handheld_connected->setCheckState(Qt::Unchecked); + ui->mouse_enabled->setCheckState(Qt::Unchecked); + ui->keyboard_enabled->setCheckState(Qt::Unchecked); + ui->debug_enabled->setCheckState(Qt::Unchecked); + ui->touchscreen_enabled->setCheckState(Qt::Checked); + updateUIEnabled(); } diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index 32c7183f9..29a8a03f8 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -18,6 +18,7 @@ #include "core/settings.h" #include "input_common/main.h" #include "ui_configure_input.h" +#include "yuzu/configuration/config.h" class QPushButton; class QString; @@ -37,57 +38,20 @@ public: void applyConfiguration(); private: - std::unique_ptr<Ui::ConfigureInput> ui; - - std::unique_ptr<QTimer> timeout_timer; - std::unique_ptr<QTimer> poll_timer; - - /// This will be the the setting function when an input is awaiting configuration. - std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; - - std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; - std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; - - static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; - - /// Each button input is represented by a QPushButton. - std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + void updateUIEnabled(); - /// A group of five QPushButtons represent one analog input. The buttons each represent up, - /// down, left, right, and modifier, respectively. - std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> - analog_map_buttons; + template <typename Dialog, typename... Args> + void CallConfigureDialog(Args&&... args); - /// Analog inputs are also represented each with a single button, used to configure with an - /// actual analog stick - std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; - - static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; - - std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; - - /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, - /// keyboard events are ignored. - bool want_keyboard_keys = false; + void OnDockedModeChanged(bool last_state, bool new_state); /// Load configuration settings. void loadConfiguration(); /// Restore all buttons to their default values. void restoreDefaults(); - /// Clear all input configuration - void ClearAll(); - /// Update UI to reflect current configuration. - void updateButtonLabels(); - - /// Called when the button was pressed. - void handleClick(QPushButton* button, - std::function<void(const Common::ParamPackage&)> new_input_setter, - InputCommon::Polling::DeviceType type); - - /// Finish polling and configure input using the input_setter - void setPollingResult(const Common::ParamPackage& params, bool abort); + std::unique_ptr<Ui::ConfigureInput> ui; - /// Handle key press events. - void keyPressEvent(QKeyEvent* event) override; + std::array<QComboBox*, 8> players_controller; + std::array<QPushButton*, 8> players_configure; }; diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8a019a693..dae8277bc 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -6,8 +6,8 @@ <rect> <x>0</x> <y>0</y> - <width>343</width> - <height>677</height> + <width>473</width> + <height>685</height> </rect> </property> <property name="windowTitle"> @@ -15,740 +15,470 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_5"> <item> - <layout class="QGridLayout" name="buttons"> - <item row="3" column="1"> - <widget class="QGroupBox" name="misc"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Misc.</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> + <string>Players</string> </property> - <layout class="QGridLayout" name="gridLayout_6"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> - <item> - <widget class="QLabel" name="labelMinus"> - <property name="text"> - <string>Minus:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonMinus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="2"> + <widget class="QComboBox" name="player1_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> - <item> - <widget class="QLabel" name="labelPlus"> - <property name="text"> - <string>Plus:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonPlus"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="3"> + <widget class="QPushButton" name="player1_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> - <item> - <widget class="QLabel" name="labelHome"> - <property name="text"> - <string>Home:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonHome"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Controller Type</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> - <item> - <widget class="QLabel" name="labelScrCap"> - <property name="text"> - <string>Screen -Capture:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonScreenshot"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="2"> + <widget class="QComboBox" name="player2_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> </item> - <item row="2" column="1"> - <spacer name="verticalSpacer"> + <item row="3" column="2"> + <widget class="QComboBox" name="player3_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="4" column="2"> + <widget class="QComboBox" name="player4_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="5" column="2"> + <widget class="QComboBox" name="player5_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="6" column="2"> + <widget class="QComboBox" name="player6_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="7" column="2"> + <widget class="QComboBox" name="player7_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="8" column="2"> + <widget class="QComboBox" name="player8_combobox"> + <property name="minimumSize"> + <size> + <width>110</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QPushButton" name="player2_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="3" column="3"> + <widget class="QPushButton" name="player3_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="4" column="3"> + <widget class="QPushButton" name="player4_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="5" column="3"> + <widget class="QPushButton" name="player5_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="6" column="3"> + <widget class="QPushButton" name="player6_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="7" column="3"> + <widget class="QPushButton" name="player7_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="8" column="3"> + <widget class="QPushButton" name="player8_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> <property name="orientation"> - <enum>Qt::Vertical</enum> + <enum>Qt::Horizontal</enum> </property> <property name="sizeHint" stdset="0"> <size> - <width>20</width> - <height>40</height> + <width>40</width> + <height>20</height> </size> </property> </spacer> </item> - </layout> - </widget> - </item> - <item row="0" column="0"> - <widget class="QGroupBox" name="faceButtons"> - <property name="title"> - <string>Face Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> - <item> - <widget class="QLabel" name="labelA"> - <property name="text"> - <string>A:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonA"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="4"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> - <item> - <widget class="QLabel" name="labelB"> - <property name="text"> - <string>B:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonB"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="minimumSize"> + <size> + <width>55</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Player 1</string> + </property> + </widget> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> - <item> - <widget class="QLabel" name="labelX"> - <property name="text"> - <string>X:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonX"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Player 2</string> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> - <item> - <widget class="QLabel" name="labelY"> - <property name="text"> - <string>Y:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonY"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="3" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Player 3</string> + </property> + </widget> </item> - </layout> - </widget> - </item> - <item row="0" column="1"> - <widget class="QGroupBox" name="Dpad"> - <property name="title"> - <string>Directional Pad</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="4" column="1"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Player 4</string> + </property> + </widget> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="5" column="1"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Player 5</string> + </property> + </widget> </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="6" column="1"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Player 6</string> + </property> + </widget> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelDpadRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonDpadRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="7" column="1"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Player 7</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Player 8</string> + </property> + </widget> </item> </layout> </widget> </item> - <item row="3" column="0"> - <widget class="QGroupBox" name="shoulderButtons"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Shoulder Buttons</string> - </property> - <property name="flat"> - <bool>false</bool> + <string>Handheld</string> </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_3"> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> - <item> - <widget class="QLabel" name="labelL"> - <property name="text"> - <string>L:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="2"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>72</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> - <item> - <widget class="QLabel" name="labelR"> - <property name="text"> - <string>R:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="1" column="4"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="3"> + <widget class="QPushButton" name="handheld_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> </item> <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> - <item> - <widget class="QLabel" name="labelZL"> - <property name="text"> - <string>ZL:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonZL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> - <item> - <widget class="QLabel" name="labelZR"> - <property name="text"> - <string>ZR:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonZR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="0"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> - <item> - <widget class="QLabel" name="labelSL"> - <property name="text"> - <string>SL:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonSL"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="handheld_connected"> + <property name="text"> + <string>Joycons Docked</string> + </property> + </widget> </item> - <item row="2" column="1"> - <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> - <item> - <widget class="QLabel" name="labelSR"> - <property name="text"> - <string>SR:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonSR"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="0" column="1"> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Use Docked Mode</string> + </property> + </widget> </item> </layout> </widget> </item> - <item row="1" column="1"> - <widget class="QGroupBox" name="RStick"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> <property name="title"> - <string>Right Stick</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - <property name="flat"> - <bool>false</bool> + <string>Other</string> </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_5"> + <layout class="QGridLayout" name="gridLayout_3"> <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0" colspan="2"> - <widget class="QPushButton" name="buttonRStickAnalog"> + <widget class="QCheckBox" name="keyboard_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> <property name="text"> - <string>Set Analog Stick</string> + <string>Keyboard</string> </property> </widget> </item> - <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="2" column="0"> - <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickPressed"> - <property name="text"> - <string>Pressed:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStick"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> <item row="2" column="1"> - <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> - <item> - <widget class="QLabel" name="labelRStickMod"> - <property name="text"> - <string>Modifier:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="debug_enabled"> + <property name="text"> + <string>Debug Controller</string> + </property> + </widget> </item> - </layout> - </widget> - </item> - <item row="1" column="0"> - <widget class="QGroupBox" name="LStick"> - <property name="title"> - <string>Left Stick</string> - </property> - <property name="flat"> - <bool>false</bool> - </property> - <property name="checkable"> - <bool>false</bool> - </property> - <layout class="QGridLayout" name="gridLayout_4"> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickDown"> - <property name="text"> - <string>Down:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickDown"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="4" column="0" colspan="2"> - <widget class="QPushButton" name="buttonLStickAnalog"> + <item row="3" column="1"> + <widget class="QCheckBox" name="touchscreen_enabled"> <property name="text"> - <string>Set Analog Stick</string> + <string>Touchscreen</string> </property> </widget> </item> <item row="0" column="1"> - <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickRight"> - <property name="text"> - <string>Right:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickRight"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="mouse_enabled"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>23</height> + </size> + </property> + <property name="text"> + <string>Mouse</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>76</width> + <height>20</height> + </size> + </property> + </spacer> </item> <item row="0" column="0"> - <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickLeft"> - <property name="text"> - <string>Left:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickLeft"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> </item> - <item row="1" column="0"> - <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickUp"> - <property name="text"> - <string>Up:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickUp"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item row="3" column="0"> - <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> - <item> - <widget class="QLabel" name="labelLStickMod"> - <property name="text"> - <string>Modifier:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStickMod"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="3" column="3"> + <widget class="QPushButton" name="touchscreen_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> </item> - <item row="3" column="1"> - <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> - <item> - <widget class="QLabel" name="labelLStickPressed"> - <property name="text"> - <string>Pressed:</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonLStick"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> + <item row="2" column="3"> + <widget class="QPushButton" name="debug_configure"> + <property name="text"> + <string>Configure</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QPushButton" name="mouse_advanced"> + <property name="text"> + <string>Advanced</string> + </property> + </widget> </item> </layout> </widget> </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <spacer name="horizontalSpacer"> + <spacer name="verticalSpacer"> <property name="orientation"> - <enum>Qt::Horizontal</enum> + <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> - <width>40</width> - <height>20</height> + <width>20</width> + <height>40</height> </size> </property> </spacer> </item> <item> - <widget class="QPushButton" name="buttonClearAll"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Clear All</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="buttonRestoreDefaults"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="sizeIncrement"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="baseSize"> - <size> - <width>0</width> - <height>0</height> - </size> - </property> - <property name="layoutDirection"> - <enum>Qt::LeftToRight</enum> - </property> - <property name="text"> - <string>Restore Defaults</string> - </property> - </widget> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> </item> </layout> </item> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp new file mode 100644 index 000000000..ba6e09368 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -0,0 +1,508 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QColorDialog> +#include <QMenu> +#include <QMessageBox> +#include <QTimer> +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_input_player.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input_player.h" + +const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM> + ConfigureInputPlayer::analog_sub_buttons{{ + "up", + "down", + "left", + "right", + "modifier", + }}; + +static void MoveGridElement(QGridLayout* grid, int row_old, int column_old, int row_new, + int column_new) { + const auto item = grid->itemAtPosition(row_old, column_old); + // grid->removeItem(item); + grid->addItem(item, row_new, column_new); +} + +static void LayerGridElements(QGridLayout* grid, QWidget* item, QWidget* onTopOf) { + const int index1 = grid->indexOf(item); + const int index2 = grid->indexOf(onTopOf); + int row, column, rowSpan, columnSpan; + grid->getItemPosition(index2, &row, &column, &rowSpan, &columnSpan); + grid->takeAt(index1); + grid->addWidget(item, row, column, rowSpan, columnSpan); +} + +static QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static void SetAnalogButton(const Common::ParamPackage& input_param, + Common::ParamPackage& analog_param, const std::string& button_name) { + if (analog_param.Get("engine", "") != "analog_from_button") { + analog_param = { + {"engine", "analog_from_button"}, + {"modifier_scale", "0.5"}, + }; + } + analog_param.Set(button_name, input_param.Serialize()); +} + +static QString ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "keyboard") { + return GetKeyName(param.Get("code", 0)); + } else if (param.Get("engine", "") == "sdl") { + if (param.Has("hat")) { + return QString(QObject::tr("Hat %1 %2")) + .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("axis")) { + return QString(QObject::tr("Axis %1%2")) + .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("button")) { + return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +}; + +static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "analog_from_button") { + return ButtonToText(Common::ParamPackage{param.Get(dir, "")}); + } else if (param.Get("engine", "") == "sdl") { + if (dir == "modifier") { + return QString(QObject::tr("[unused]")); + } + + if (dir == "left" || dir == "right") { + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); + } else if (dir == "up" || dir == "down") { + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +}; + +ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, u8 player_index, bool debug) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()), + player_index(player_index), debug(debug) { + + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->buttonA, ui->buttonB, ui->buttonX, ui->buttonY, + ui->buttonLStick, ui->buttonRStick, ui->buttonL, ui->buttonR, + ui->buttonZL, ui->buttonZR, ui->buttonPlus, ui->buttonMinus, + ui->buttonDpadLeft, ui->buttonDpadUp, ui->buttonDpadRight, ui->buttonDpadDown, + ui->buttonLStickLeft, ui->buttonLStickUp, ui->buttonLStickRight, ui->buttonLStickDown, + ui->buttonRStickLeft, ui->buttonRStickUp, ui->buttonRStickRight, ui->buttonRStickDown, + ui->buttonSL, ui->buttonSR, ui->buttonHome, ui->buttonScreenshot, + }; + + analog_map_buttons = {{ + { + ui->buttonLStickUp, + ui->buttonLStickDown, + ui->buttonLStickLeft, + ui->buttonLStickRight, + ui->buttonLStickMod, + }, + { + ui->buttonRStickUp, + ui->buttonRStickDown, + ui->buttonRStickLeft, + ui->buttonRStickRight, + ui->buttonRStickMod, + }, + }}; + + debug_hidden = { + ui->buttonSL, ui->labelSL, + ui->buttonSR, ui->labelSR, + ui->buttonLStick, ui->labelLStickPressed, + ui->buttonRStick, ui->labelRStickPressed, + ui->buttonHome, ui->labelHome, + ui->buttonScreenshot, ui->labelScreenshot, + }; + + auto layout = Settings::values.players[player_index].type; + if (debug) + layout = Settings::ControllerType::DualJoycon; + + switch (layout) { + case Settings::ControllerType::ProController: + case Settings::ControllerType::DualJoycon: + layout_hidden = { + ui->buttonSL, + ui->labelSL, + ui->buttonSR, + ui->labelSR, + }; + break; + case Settings::ControllerType::LeftJoycon: + layout_hidden = { + ui->right_body_button, + ui->right_buttons_button, + ui->right_body_label, + ui->right_buttons_label, + ui->buttonR, + ui->labelR, + ui->buttonZR, + ui->labelZR, + ui->labelHome, + ui->buttonHome, + ui->buttonPlus, + ui->labelPlus, + ui->RStick, + ui->faceButtons, + }; + break; + case Settings::ControllerType::RightJoycon: + layout_hidden = { + ui->left_body_button, ui->left_buttons_button, + ui->left_body_label, ui->left_buttons_label, + ui->buttonL, ui->labelL, + ui->buttonZL, ui->labelZL, + ui->labelScreenshot, ui->buttonScreenshot, + ui->buttonMinus, ui->labelMinus, + ui->LStick, ui->Dpad, + }; + break; + } + + if (debug || layout == Settings::ControllerType::ProController) { + ui->controller_color->hide(); + } else { + if (layout == Settings::ControllerType::LeftJoycon || + layout == Settings::ControllerType::RightJoycon) { + ui->horizontalSpacer_4->setGeometry({0, 0, 0, 0}); + + LayerGridElements(ui->buttons, ui->shoulderButtons, ui->Dpad); + LayerGridElements(ui->buttons, ui->misc, ui->RStick); + LayerGridElements(ui->buttons, ui->Dpad, ui->faceButtons); + LayerGridElements(ui->buttons, ui->RStick, ui->LStick); + } + } + + for (auto* widget : layout_hidden) + widget->setVisible(false); + + analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog}; + + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (!button_map[button_id]) + continue; + button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick( + button_map[button_id], + [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + InputCommon::Polling::DeviceType::Button); + }); + connect(button_map[button_id], &QPushButton::customContextMenuRequested, + [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (!analog_map_buttons[analog_id][sub_button_id]) + continue; + analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( + Qt::CustomContextMenu); + connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { + handleClick(analog_map_buttons[analog_id][sub_button_id], + [=](const Common::ParamPackage& params) { + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + }, + InputCommon::Polling::DeviceType::Button); + }); + connect(analog_map_buttons[analog_id][sub_button_id], + &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogButton(params, analogs_param[analog_id], + analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( + analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + }); + context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( + menu_location)); + }); + } + connect(analog_map_stick[analog_id], &QPushButton::released, [=]() { + QMessageBox::information(this, tr("Information"), + tr("After pressing OK, first move your joystick horizontally, " + "and then vertically.")); + handleClick( + analog_map_stick[analog_id], + [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, + InputCommon::Polling::DeviceType::Analog); + }); + } + + connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this]() { + Common::ParamPackage params; + for (auto& poller : device_pollers) { + params = poller->GetNextInput(); + if (params.Has("engine")) { + setPollingResult(params, false); + return; + } + } + }); + + controller_color_buttons = { + ui->left_body_button, + ui->left_buttons_button, + ui->right_body_button, + ui->right_buttons_button, + }; + + for (std::size_t i = 0; i < controller_color_buttons.size(); ++i) { + connect(controller_color_buttons[i], &QPushButton::clicked, this, + std::bind(&ConfigureInputPlayer::OnControllerButtonClick, this, i)); + } + + this->loadConfiguration(); + this->resize(0, 0); + + // TODO(wwylele): enable this when we actually emulate it + ui->buttonHome->setEnabled(false); +} + +ConfigureInputPlayer::~ConfigureInputPlayer() = default; + +void ConfigureInputPlayer::applyConfiguration() { + auto& buttons = + debug ? Settings::values.debug_pad_buttons : Settings::values.players[player_index].buttons; + auto& analogs = + debug ? Settings::values.debug_pad_analogs : Settings::values.players[player_index].analogs; + + std::transform(buttons_param.begin(), buttons_param.end(), buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + std::transform(analogs_param.begin(), analogs_param.end(), analogs.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); + + if (debug) + return; + + std::array<u32, 4> colors{}; + std::transform(controller_colors.begin(), controller_colors.end(), colors.begin(), + [](QColor color) { return color.rgb(); }); + + Settings::values.players[player_index].body_color_left = colors[0]; + Settings::values.players[player_index].button_color_left = colors[1]; + Settings::values.players[player_index].body_color_right = colors[2]; + Settings::values.players[player_index].button_color_right = colors[3]; +} + +void ConfigureInputPlayer::OnControllerButtonClick(int i) { + const QColor new_bg_color = QColorDialog::getColor(controller_colors[i]); + if (!new_bg_color.isValid()) + return; + controller_colors[i] = new_bg_color; + controller_color_buttons[i]->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); +} + +void ConfigureInputPlayer::loadConfiguration() { + if (debug) { + std::transform(Settings::values.debug_pad_buttons.begin(), + Settings::values.debug_pad_buttons.end(), buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.debug_pad_analogs.begin(), + Settings::values.debug_pad_analogs.end(), analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + } else { + std::transform(Settings::values.players[player_index].buttons.begin(), + Settings::values.players[player_index].buttons.end(), buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + std::transform(Settings::values.players[player_index].analogs.begin(), + Settings::values.players[player_index].analogs.end(), analogs_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + } + + updateButtonLabels(); + + if (debug) + return; + + std::array<u32, 4> colors = { + Settings::values.players[player_index].body_color_left, + Settings::values.players[player_index].button_color_left, + Settings::values.players[player_index].body_color_right, + Settings::values.players[player_index].button_color_right, + }; + + std::transform(colors.begin(), colors.end(), controller_colors.begin(), + [](u32 rgb) { return QColor::fromRgb(rgb); }); + + for (std::size_t i = 0; i < colors.size(); ++i) { + controller_color_buttons[i]->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(controller_colors[i].name())); + } +} + +void ConfigureInputPlayer::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + Common::ParamPackage params{InputCommon::GenerateKeyboardParam( + Config::default_analogs[analog_id][sub_button_id])}; + SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); + } + } + updateButtonLabels(); +} + +void ConfigureInputPlayer::ClearAll() { + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + if (button_map[button_id] && button_map[button_id]->isEnabled()) + buttons_param[button_id].Clear(); + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map_buttons[analog_id][sub_button_id] && + analog_map_buttons[analog_id][sub_button_id]->isEnabled()) + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + } + } + + updateButtonLabels(); +} + +void ConfigureInputPlayer::updateButtonLabels() { + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + button_map[button]->setText(ButtonToText(buttons_param[button])); + } + + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { + if (analog_map_buttons[analog_id][sub_button_id]) { + analog_map_buttons[analog_id][sub_button_id]->setText( + AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id])); + } + } + analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); + } +} + +void ConfigureInputPlayer::handleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + button->setText(tr("[press key]")); + button->setFocus(); + + const auto iter = std::find(button_map.begin(), button_map.end(), button); + ASSERT(iter != button_map.end()); + const auto index = std::distance(button_map.begin(), iter); + ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + + input_setter = new_input_setter; + + device_pollers = InputCommon::Polling::GetPollers(type); + + // Keyboard keys can only be used as button devices + want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + + for (auto& poller : device_pollers) { + poller->Start(); + } + + grabKeyboard(); + grabMouse(); + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + +void ConfigureInputPlayer::setPollingResult(const Common::ParamPackage& params, bool abort) { + releaseKeyboard(); + releaseMouse(); + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + if (!abort) { + (*input_setter)(params); + } + + updateButtonLabels(); + input_setter = std::nullopt; +} + +void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_keys) { + setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h new file mode 100644 index 000000000..b0e5550c5 --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.h @@ -0,0 +1,103 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <functional> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> +#include <QDialog> +#include <QKeyEvent> +#include "common/param_package.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_input.h" + +class QPushButton; +class QString; +class QTimer; + +namespace Ui { +class ConfigureInputPlayer; +} + +class ConfigureInputPlayer : public QDialog { + Q_OBJECT + +public: + explicit ConfigureInputPlayer(QWidget* parent, u8 player_index, bool debug = false); + ~ConfigureInputPlayer() override; + + /// Save all button configurations to settings file + void applyConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureInputPlayer> ui; + + u8 player_index; + bool debug; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param; + std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param; + + static constexpr int ANALOG_SUB_BUTTONS_NUM = 5; + + /// Each button input is represented by a QPushButton. + std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map; + + std::vector<QWidget*> debug_hidden; + std::vector<QWidget*> layout_hidden; + + /// A group of five QPushButtons represent one analog input. The buttons each represent up, + /// down, left, right, and modifier, respectively. + std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs> + analog_map_buttons; + + /// Analog inputs are also represented each with a single button, used to configure with an + /// actual analog stick + std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_stick; + + static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons; + + std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, + /// keyboard events are ignored. + bool want_keyboard_keys = false; + + std::array<QPushButton*, 4> controller_color_buttons; + std::array<QColor, 4> controller_colors; + + void OnControllerButtonClick(int i); + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Clear all input configuration + void ClearAll(); + + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type); + + /// Finish polling and configure input using the input_setter + void setPollingResult(const Common::ParamPackage& params, bool abort); + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; +}; diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui new file mode 100644 index 000000000..42db020be --- /dev/null +++ b/src/yuzu/configuration/configure_input_player.ui @@ -0,0 +1,1164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureInputPlayer</class> + <widget class="QDialog" name="ConfigureInputPlayer"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>408</width> + <height>731</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Input</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QGridLayout" name="buttons"> + <item row="1" column="1"> + <widget class="QGroupBox" name="RStick"> + <property name="title"> + <string>Right Stick</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonRStickDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonRStickRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickRight"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="0" colspan="2"> + <widget class="QPushButton" name="buttonRStickAnalog"> + <property name="text"> + <string>Set Analog Stick</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonRStickLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickLeftHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickLeft"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonRStickUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <layout class="QVBoxLayout" name="buttonRStickPressedVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickPressedHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickPressed"> + <property name="text"> + <string>Pressed:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStick"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="buttonRStickModVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonRStickModHorizontalLayout"> + <item> + <widget class="QLabel" name="labelRStickMod"> + <property name="text"> + <string>Modifier:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonRStickMod"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="Dpad"> + <property name="title"> + <string>Directional Pad</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonDpadUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonDpadDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonDpadLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadLeftHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadLeft"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonDpadRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonDpadRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelDpadRight"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonDpadRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="faceButtons"> + <property name="title"> + <string>Face Buttons</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonFaceButtonsAVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsAHorizontalLayout"> + <item> + <widget class="QLabel" name="labelA"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>A:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonA"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonFaceButtonsBVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsBHorizontalLayout"> + <item> + <widget class="QLabel" name="labelB"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>B:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonB"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonFaceButtonsXVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsXHorizontalLayout"> + <item> + <widget class="QLabel" name="labelX"> + <property name="text"> + <string>X:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonX"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonFaceButtonsYVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonFaceButtonsYHorizontalLayout"> + <item> + <widget class="QLabel" name="labelY"> + <property name="text"> + <string>Y:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonY"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="5" column="0" colspan="2"> + <widget class="QGroupBox" name="controller_color"> + <property name="title"> + <string>Controller Color</string> + </property> + <layout class="QGridLayout" name="gridLayout_10" columnstretch="0,0,0,0,0,0,0"> + <item row="0" column="0"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="left_body_label"> + <property name="text"> + <string>Left Body</string> + </property> + </widget> + </item> + <item row="0" column="6"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="left_buttons_label"> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Left Buttons</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QPushButton" name="right_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="right_body_label"> + <property name="text"> + <string>Right Body</string> + </property> + </widget> + </item> + <item row="1" column="4"> + <widget class="QLabel" name="right_buttons_label"> + <property name="minimumSize"> + <size> + <width>90</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Right Buttons</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="left_buttons_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="left_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QPushButton" name="right_body_button"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="LStick"> + <property name="title"> + <string>Left Stick</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonLStickUpVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickUpHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickUp"> + <property name="text"> + <string>Up:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickUp"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="2"> + <layout class="QVBoxLayout" name="buttonLStickRightVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickRightHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickRight"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickRight"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QPushButton" name="buttonLStickAnalog"> + <property name="text"> + <string>Set Analog Stick</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonLStickLeftVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickLeftHorizontalLayout_2"> + <item> + <widget class="QLabel" name="labelLStickLeft"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickLeft"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="2"> + <layout class="QVBoxLayout" name="buttonLStickDownVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickDownHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickDown"> + <property name="text"> + <string>Down:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickDown"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="2"> + <layout class="QVBoxLayout" name="buttonLStickModVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonLStickModHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickMod"> + <property name="text"> + <string>Modifier:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStickMod"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="buttonLStickPressedVerticalLayout" stretch="0,0"> + <item> + <layout class="QHBoxLayout" name="buttonLStickPressedHorizontalLayout"> + <item> + <widget class="QLabel" name="labelLStickPressed"> + <property name="text"> + <string>Pressed:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonLStick"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="3" column="0"> + <widget class="QGroupBox" name="shoulderButtons"> + <property name="title"> + <string>Shoulder Buttons</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="3" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsSLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelSL"> + <property name="text"> + <string>SL:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonSL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsZRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelZR"> + <property name="text"> + <string>ZR:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonZR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsSRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsSRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelSR"> + <property name="text"> + <string>SR:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonSR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsZLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsZLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelZL"> + <property name="text"> + <string>ZL:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonZL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsLVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsLHorizontalLayout"> + <item> + <widget class="QLabel" name="labelL"> + <property name="text"> + <string>L:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonL"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="2" column="0"> + <layout class="QVBoxLayout" name="buttonShoulderButtonsRVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonShoulderButtonsRHorizontalLayout"> + <item> + <widget class="QLabel" name="labelR"> + <property name="text"> + <string>R:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonR"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="3" column="1"> + <widget class="QGroupBox" name="misc"> + <property name="title"> + <string>Misc.</string> + </property> + <property name="flat"> + <bool>false</bool> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="1" column="0"> + <layout class="QVBoxLayout" name="buttonMiscMinusVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscMinusHorizontalLayout"> + <item> + <widget class="QLabel" name="labelMinus"> + <property name="text"> + <string>Minus:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonMinus"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <layout class="QVBoxLayout" name="buttonMiscPlusVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscPlusHorizontalLayout"> + <item> + <widget class="QLabel" name="labelPlus"> + <property name="text"> + <string>Plus:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonPlus"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="buttonMiscHomeVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscHomeHorizontalLayout"> + <item> + <widget class="QLabel" name="labelHome"> + <property name="text"> + <string>Home:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonHome"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="buttonMiscScrCapVerticalLayout"> + <item> + <layout class="QHBoxLayout" name="buttonMiscScrCapHorizontalLayout"> + <item> + <widget class="QLabel" name="labelScreenshot"> + <property name="text"> + <string>Screen Capture:</string> + </property> + <property name="wordWrap"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="buttonScreenshot"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>80</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"/> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QPushButton" name="buttonClearAll"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="sizeIncrement"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="baseSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureInputPlayer</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>371</x> + <y>730</y> + </hint> + <hint type="destinationlabel"> + <x>229</x> + <y>375</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureInputPlayer</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>371</x> + <y>730</y> + </hint> + <hint type="destinationlabel"> + <x>229</x> + <y>375</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_mouse_advanced.cpp b/src/yuzu/configuration/configure_mouse_advanced.cpp new file mode 100644 index 000000000..dab58fbaa --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.cpp @@ -0,0 +1,213 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <memory> +#include <utility> +#include <QKeyEvent> +#include <QMenu> +#include <QMessageBox> +#include <QTimer> +#include "common/assert.h" +#include "common/param_package.h" +#include "input_common/main.h" +#include "ui_configure_mouse_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_mouse_advanced.h" + +static QString GetKeyName(int key_code) { + switch (key_code) { + case Qt::Key_Shift: + return QObject::tr("Shift"); + case Qt::Key_Control: + return QObject::tr("Ctrl"); + case Qt::Key_Alt: + return QObject::tr("Alt"); + case Qt::Key_Meta: + return ""; + default: + return QKeySequence(key_code).toString(); + } +} + +static QString ButtonToText(const Common::ParamPackage& param) { + if (!param.Has("engine")) { + return QObject::tr("[not set]"); + } else if (param.Get("engine", "") == "keyboard") { + return GetKeyName(param.Get("code", 0)); + } else if (param.Get("engine", "") == "sdl") { + if (param.Has("hat")) { + return QString(QObject::tr("Hat %1 %2")) + .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("axis")) { + return QString(QObject::tr("Axis %1%2")) + .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); + } + if (param.Has("button")) { + return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); + } + return QString(); + } else { + return QObject::tr("[unknown]"); + } +} + +ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), + timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) { + ui->setupUi(this); + setFocusPolicy(Qt::ClickFocus); + + button_map = { + ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button, + }; + + for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { + if (!button_map[button_id]) + continue; + button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); + connect(button_map[button_id], &QPushButton::released, [=]() { + handleClick( + button_map[button_id], + [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, + InputCommon::Polling::DeviceType::Button); + }); + connect(button_map[button_id], &QPushButton::customContextMenuRequested, + [=](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction(tr("Clear"), [&] { + buttons_param[button_id].Clear(); + button_map[button_id]->setText(tr("[not set]")); + }); + context_menu.addAction(tr("Restore Default"), [&] { + buttons_param[button_id] = + Common::ParamPackage{InputCommon::GenerateKeyboardParam( + Config::default_mouse_buttons[button_id])}; + button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); + }); + context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); + }); + } + + connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); }); + connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); }); + + timeout_timer->setSingleShot(true); + connect(timeout_timer.get(), &QTimer::timeout, [this]() { setPollingResult({}, true); }); + + connect(poll_timer.get(), &QTimer::timeout, [this]() { + Common::ParamPackage params; + for (auto& poller : device_pollers) { + params = poller->GetNextInput(); + if (params.Has("engine")) { + setPollingResult(params, false); + return; + } + } + }); + + loadConfiguration(); + resize(0, 0); +} + +ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default; + +void ConfigureMouseAdvanced::applyConfiguration() { + std::transform(buttons_param.begin(), buttons_param.end(), + Settings::values.mouse_buttons.begin(), + [](const Common::ParamPackage& param) { return param.Serialize(); }); +} + +void ConfigureMouseAdvanced::loadConfiguration() { + std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(), + buttons_param.begin(), + [](const std::string& str) { return Common::ParamPackage(str); }); + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::restoreDefaults() { + for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) { + buttons_param[button_id] = Common::ParamPackage{ + InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])}; + } + + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::ClearAll() { + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + if (button_map[i] && button_map[i]->isEnabled()) + buttons_param[i].Clear(); + } + + updateButtonLabels(); +} + +void ConfigureMouseAdvanced::updateButtonLabels() { + for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) { + button_map[button]->setText(ButtonToText(buttons_param[button])); + } +} + +void ConfigureMouseAdvanced::handleClick( + QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type) { + button->setText(tr("[press key]")); + button->setFocus(); + + const auto iter = std::find(button_map.begin(), button_map.end(), button); + ASSERT(iter != button_map.end()); + const auto index = std::distance(button_map.begin(), iter); + ASSERT(index < Settings::NativeButton::NumButtons && index >= 0); + + input_setter = new_input_setter; + + device_pollers = InputCommon::Polling::GetPollers(type); + + // Keyboard keys can only be used as button devices + want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button; + + for (auto& poller : device_pollers) { + poller->Start(); + } + + grabKeyboard(); + grabMouse(); + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + +void ConfigureMouseAdvanced::setPollingResult(const Common::ParamPackage& params, bool abort) { + releaseKeyboard(); + releaseMouse(); + timeout_timer->stop(); + poll_timer->stop(); + for (auto& poller : device_pollers) { + poller->Stop(); + } + + if (!abort) { + (*input_setter)(params); + } + + updateButtonLabels(); + input_setter = std::nullopt; +} + +void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) { + if (!input_setter || !event) + return; + + if (event->key() != Qt::Key_Escape) { + if (want_keyboard_keys) { + setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, + false); + } else { + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + return; + } + } + setPollingResult({}, true); +} diff --git a/src/yuzu/configuration/configure_mouse_advanced.h b/src/yuzu/configuration/configure_mouse_advanced.h new file mode 100644 index 000000000..218df2bda --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.h @@ -0,0 +1,68 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <optional> +#include <QDialog> +#include <QWidget> +#include "core/settings.h" + +class QCheckBox; +class QPushButton; +class QTimer; + +namespace Ui { +class ConfigureMouseAdvanced; +} + +class ConfigureMouseAdvanced : public QDialog { + Q_OBJECT + +public: + explicit ConfigureMouseAdvanced(QWidget* parent); + ~ConfigureMouseAdvanced() override; + + void applyConfiguration(); + +private: + std::unique_ptr<Ui::ConfigureMouseAdvanced> ui; + + /// This will be the the setting function when an input is awaiting configuration. + std::optional<std::function<void(const Common::ParamPackage&)>> input_setter; + + std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map; + std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param; + + std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers; + + std::unique_ptr<QTimer> timeout_timer; + std::unique_ptr<QTimer> poll_timer; + + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, + /// keyboard events are ignored. + bool want_keyboard_keys = false; + + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + /// Clear all input configuration + void ClearAll(); + + /// Update UI to reflect current configuration. + void updateButtonLabels(); + + /// Called when the button was pressed. + void handleClick(QPushButton* button, + std::function<void(const Common::ParamPackage&)> new_input_setter, + InputCommon::Polling::DeviceType type); + + /// Finish polling and configure input using the input_setter + void setPollingResult(const Common::ParamPackage& params, bool abort); + + /// Handle key press events. + void keyPressEvent(QKeyEvent* event) override; +}; diff --git a/src/yuzu/configuration/configure_mouse_advanced.ui b/src/yuzu/configuration/configure_mouse_advanced.ui new file mode 100644 index 000000000..08245ecf0 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_advanced.ui @@ -0,0 +1,261 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureMouseAdvanced</class> + <widget class="QDialog" name="ConfigureMouseAdvanced"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>250</width> + <height>261</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Mouse</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Mouse Buttons</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="4"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="3"> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Right:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="right_button"> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Middle:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="middle_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QLabel" name="label_4"> + <property name="minimumSize"> + <size> + <width>54</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Back:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="back_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Left:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="left_button"> + <property name="minimumSize"> + <size> + <width>75</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item row="3" column="3"> + <layout class="QVBoxLayout" name="verticalLayout_6"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Forward:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QPushButton" name="forward_button"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QPushButton" name="buttonClearAll"> + <property name="text"> + <string>Clear All</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonRestoreDefaults"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureMouseAdvanced</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>124</x> + <y>266</y> + </hint> + <hint type="destinationlabel"> + <x>124</x> + <y>143</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureMouseAdvanced</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>124</x> + <y>266</y> + </hint> + <hint type="destinationlabel"> + <x>124</x> + <y>143</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.cpp b/src/yuzu/configuration/configure_touchscreen_advanced.cpp new file mode 100644 index 000000000..9c1561e9d --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.cpp @@ -0,0 +1,42 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include "ui_configure_touchscreen_advanced.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_touchscreen_advanced.h" + +ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent) + : QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) { + ui->setupUi(this); + + connect(ui->restore_defaults_button, &QPushButton::pressed, this, + &ConfigureTouchscreenAdvanced::restoreDefaults); + + loadConfiguration(); + resize(0, 0); +} + +ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default; + +void ConfigureTouchscreenAdvanced::applyConfiguration() { + Settings::values.touchscreen.finger = ui->finger_box->value(); + Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value(); + Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value(); + Settings::values.touchscreen.rotation_angle = ui->angle_box->value(); +} + +void ConfigureTouchscreenAdvanced::loadConfiguration() { + ui->finger_box->setValue(Settings::values.touchscreen.finger); + ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x); + ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y); + ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle); +} + +void ConfigureTouchscreenAdvanced::restoreDefaults() { + ui->finger_box->setValue(0); + ui->diameter_x_box->setValue(15); + ui->diameter_y_box->setValue(15); + ui->angle_box->setValue(0); +} diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.h b/src/yuzu/configuration/configure_touchscreen_advanced.h new file mode 100644 index 000000000..41cd255fb --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.h @@ -0,0 +1,32 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QDialog> +#include <QWidget> +#include "yuzu/configuration/config.h" + +namespace Ui { +class ConfigureTouchscreenAdvanced; +} + +class ConfigureTouchscreenAdvanced : public QDialog { + Q_OBJECT + +public: + explicit ConfigureTouchscreenAdvanced(QWidget* parent); + ~ConfigureTouchscreenAdvanced() override; + + void applyConfiguration(); + +private: + /// Load configuration settings. + void loadConfiguration(); + /// Restore all buttons to their default values. + void restoreDefaults(); + + std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui; +}; diff --git a/src/yuzu/configuration/configure_touchscreen_advanced.ui b/src/yuzu/configuration/configure_touchscreen_advanced.ui new file mode 100644 index 000000000..1171c2dd1 --- /dev/null +++ b/src/yuzu/configuration/configure_touchscreen_advanced.ui @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureTouchscreenAdvanced</class> + <widget class="QDialog" name="ConfigureTouchscreenAdvanced"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>298</width> + <height>339</height> + </rect> + </property> + <property name="windowTitle"> + <string>Configure Touchscreen</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="label_2"> + <property name="minimumSize"> + <size> + <width>280</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>Warning: The settings in this page affect the inner workings of yuzu's emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Touch Parameters</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Touch Diameter Y</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Finger</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Touch Diameter X</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QSpinBox" name="finger_box"> + <property name="minimumSize"> + <size> + <width>80</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Rotational Angle</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QSpinBox" name="diameter_x_box"/> + </item> + <item row="2" column="2"> + <widget class="QSpinBox" name="diameter_y_box"/> + </item> + <item row="3" column="2"> + <widget class="QSpinBox" name="angle_box"/> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="restore_defaults_button"> + <property name="text"> + <string>Restore Defaults</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>ConfigureTouchscreenAdvanced</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>140</x> + <y>318</y> + </hint> + <hint type="destinationlabel"> + <x>140</x> + <y>169</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>ConfigureTouchscreenAdvanced</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>140</x> + <y>318</y> + </hint> + <hint type="destinationlabel"> + <x>140</x> + <y>169</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4b969119c..9e13bbf7c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -8,9 +8,11 @@ #include <thread> // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. +#include "applets/software_keyboard.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/am/applets/applets.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. @@ -59,6 +61,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/file_sys/romfs.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/submission_package.h" +#include "core/frontend/applets/software_keyboard.h" #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_ldr.h" @@ -204,6 +207,27 @@ GMainWindow::~GMainWindow() { delete render_window; } +void GMainWindow::SoftwareKeyboardGetText( + const Core::Frontend::SoftwareKeyboardParameters& parameters) { + QtSoftwareKeyboardDialog dialog(this, parameters); + dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::WindowModal); + dialog.exec(); + + if (!dialog.GetStatus()) { + emit SoftwareKeyboardFinishedText(std::nullopt); + return; + } + + emit SoftwareKeyboardFinishedText(dialog.GetText()); +} + +void GMainWindow::SoftwareKeyboardInvokeCheckDialog(std::u16string error_message) { + QMessageBox::warning(this, tr("Text Check Failed"), QString::fromStdU16String(error_message)); + emit SoftwareKeyboardFinishedCheckDialog(); +} + void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui.action_Report_Compatibility->setVisible(true); @@ -559,6 +583,8 @@ bool GMainWindow::LoadROM(const QString& filename) { system.SetGPUDebugContext(debug_context); + system.SetSoftwareKeyboard(std::make_unique<QtSoftwareKeyboard>(*this)); + const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; const auto drd_callout = @@ -1228,8 +1254,13 @@ void GMainWindow::OnMenuRecentFile() { void GMainWindow::OnStartGame() { emu_thread->SetRunning(true); + + qRegisterMetaType<Core::Frontend::SoftwareKeyboardParameters>( + "Core::Frontend::SoftwareKeyboardParameters"); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<std::string>("std::string"); + qRegisterMetaType<std::optional<std::u16string>>("std::optional<std::u16string>"); + connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError); ui.action_Start->setEnabled(false); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 929250e8c..674e73412 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -29,6 +29,10 @@ class ProfilerWidget; class WaitTreeWidget; enum class GameListOpenTarget; +namespace Core::Frontend { +struct SoftwareKeyboardParameters; +} // namespace Core::Frontend + namespace FileSys { class RegisteredCacheUnion; class VfsFilesystem; @@ -95,6 +99,13 @@ signals: // Signal that tells widgets to update icons to use the current theme void UpdateThemedIcons(); + void SoftwareKeyboardFinishedText(std::optional<std::u16string> text); + void SoftwareKeyboardFinishedCheckDialog(); + +public slots: + void SoftwareKeyboardGetText(const Core::Frontend::SoftwareKeyboardParameters& parameters); + void SoftwareKeyboardInvokeCheckDialog(std::u16string error_message); + private: void InitializeWidgets(); void InitializeDebugWidgets(); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 96f1ef636..c66353a65 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -65,54 +65,246 @@ static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> }, }}; +static const std::array<int, Settings::NativeMouseButton::NumMouseButtons> default_mouse_buttons = { + SDL_SCANCODE_LEFTBRACKET, SDL_SCANCODE_RIGHTBRACKET, SDL_SCANCODE_APOSTROPHE, + SDL_SCANCODE_MINUS, SDL_SCANCODE_EQUALS, +}; + +static const std::array<int, 0x8A> keyboard_keys = { + 0, + 0, + 0, + 0, + SDL_SCANCODE_A, + SDL_SCANCODE_B, + SDL_SCANCODE_C, + SDL_SCANCODE_D, + SDL_SCANCODE_E, + SDL_SCANCODE_F, + SDL_SCANCODE_G, + SDL_SCANCODE_H, + SDL_SCANCODE_I, + SDL_SCANCODE_J, + SDL_SCANCODE_K, + SDL_SCANCODE_L, + SDL_SCANCODE_M, + SDL_SCANCODE_N, + SDL_SCANCODE_O, + SDL_SCANCODE_P, + SDL_SCANCODE_Q, + SDL_SCANCODE_R, + SDL_SCANCODE_S, + SDL_SCANCODE_T, + SDL_SCANCODE_U, + SDL_SCANCODE_V, + SDL_SCANCODE_W, + SDL_SCANCODE_X, + SDL_SCANCODE_Y, + SDL_SCANCODE_Z, + SDL_SCANCODE_1, + SDL_SCANCODE_2, + SDL_SCANCODE_3, + SDL_SCANCODE_4, + SDL_SCANCODE_5, + SDL_SCANCODE_6, + SDL_SCANCODE_7, + SDL_SCANCODE_8, + SDL_SCANCODE_9, + SDL_SCANCODE_0, + SDL_SCANCODE_RETURN, + SDL_SCANCODE_ESCAPE, + SDL_SCANCODE_BACKSPACE, + SDL_SCANCODE_TAB, + SDL_SCANCODE_SPACE, + SDL_SCANCODE_MINUS, + SDL_SCANCODE_EQUALS, + SDL_SCANCODE_LEFTBRACKET, + SDL_SCANCODE_RIGHTBRACKET, + SDL_SCANCODE_BACKSLASH, + 0, + SDL_SCANCODE_SEMICOLON, + SDL_SCANCODE_APOSTROPHE, + SDL_SCANCODE_GRAVE, + SDL_SCANCODE_COMMA, + SDL_SCANCODE_PERIOD, + SDL_SCANCODE_SLASH, + SDL_SCANCODE_CAPSLOCK, + + SDL_SCANCODE_F1, + SDL_SCANCODE_F2, + SDL_SCANCODE_F3, + SDL_SCANCODE_F4, + SDL_SCANCODE_F5, + SDL_SCANCODE_F6, + SDL_SCANCODE_F7, + SDL_SCANCODE_F8, + SDL_SCANCODE_F9, + SDL_SCANCODE_F10, + SDL_SCANCODE_F11, + SDL_SCANCODE_F12, + + 0, + SDL_SCANCODE_SCROLLLOCK, + SDL_SCANCODE_PAUSE, + SDL_SCANCODE_INSERT, + SDL_SCANCODE_HOME, + SDL_SCANCODE_PAGEUP, + SDL_SCANCODE_DELETE, + SDL_SCANCODE_END, + SDL_SCANCODE_PAGEDOWN, + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_UP, + + SDL_SCANCODE_NUMLOCKCLEAR, + SDL_SCANCODE_KP_DIVIDE, + SDL_SCANCODE_KP_MULTIPLY, + SDL_SCANCODE_KP_MINUS, + SDL_SCANCODE_KP_PLUS, + SDL_SCANCODE_KP_ENTER, + SDL_SCANCODE_KP_1, + SDL_SCANCODE_KP_2, + SDL_SCANCODE_KP_3, + SDL_SCANCODE_KP_4, + SDL_SCANCODE_KP_5, + SDL_SCANCODE_KP_6, + SDL_SCANCODE_KP_7, + SDL_SCANCODE_KP_8, + SDL_SCANCODE_KP_9, + SDL_SCANCODE_KP_0, + SDL_SCANCODE_KP_PERIOD, + + 0, + 0, + SDL_SCANCODE_POWER, + SDL_SCANCODE_KP_EQUALS, + + SDL_SCANCODE_F13, + SDL_SCANCODE_F14, + SDL_SCANCODE_F15, + SDL_SCANCODE_F16, + SDL_SCANCODE_F17, + SDL_SCANCODE_F18, + SDL_SCANCODE_F19, + SDL_SCANCODE_F20, + SDL_SCANCODE_F21, + SDL_SCANCODE_F22, + SDL_SCANCODE_F23, + SDL_SCANCODE_F24, + + 0, + SDL_SCANCODE_HELP, + SDL_SCANCODE_MENU, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SDL_SCANCODE_KP_COMMA, + SDL_SCANCODE_KP_LEFTPAREN, + SDL_SCANCODE_KP_RIGHTPAREN, + 0, + 0, + 0, + 0, +}; + +static const std::array<int, 8> keyboard_mods{ + SDL_SCANCODE_LCTRL, SDL_SCANCODE_LSHIFT, SDL_SCANCODE_LALT, SDL_SCANCODE_LGUI, + SDL_SCANCODE_RCTRL, SDL_SCANCODE_RSHIFT, SDL_SCANCODE_RALT, SDL_SCANCODE_RGUI, +}; + void Config::ReadValues() { // Controls + for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { + const auto group = fmt::format("ControlsP{}", p); + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.players[p].buttons[i] = + sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param); + if (Settings::values.players[p].buttons[i].empty()) + Settings::values.players[p].buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.players[p].analogs[i] = + sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param); + if (Settings::values.players[p].analogs[i].empty()) + Settings::values.players[p].analogs[i] = default_param; + } + } + + Settings::values.mouse_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "mouse_enabled", false); + for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]); + Settings::values.mouse_buttons[i] = sdl2_config->Get( + "ControlsGeneral", std::string("mouse_") + Settings::NativeMouseButton::mapping[i], + default_param); + if (Settings::values.mouse_buttons[i].empty()) + Settings::values.mouse_buttons[i] = default_param; + } + + Settings::values.motion_device = sdl2_config->Get( + "ControlsGeneral", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); + + Settings::values.keyboard_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "keyboard_enabled", false); + + Settings::values.debug_pad_enabled = + sdl2_config->GetBoolean("ControlsGeneral", "debug_pad_enabled", false); for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); - Settings::values.buttons[i] = - sdl2_config->Get("Controls", Settings::NativeButton::mapping[i], default_param); - if (Settings::values.buttons[i].empty()) - Settings::values.buttons[i] = default_param; + Settings::values.debug_pad_buttons[i] = sdl2_config->Get( + "ControlsGeneral", std::string("debug_pad_") + Settings::NativeButton::mapping[i], + default_param); + if (Settings::values.debug_pad_buttons[i].empty()) + Settings::values.debug_pad_buttons[i] = default_param; } for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { std::string default_param = InputCommon::GenerateAnalogParamFromKeys( default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], default_analogs[i][3], default_analogs[i][4], 0.5f); - Settings::values.analogs[i] = - sdl2_config->Get("Controls", Settings::NativeAnalog::mapping[i], default_param); - if (Settings::values.analogs[i].empty()) - Settings::values.analogs[i] = default_param; + Settings::values.debug_pad_analogs[i] = sdl2_config->Get( + "ControlsGeneral", std::string("debug_pad_") + Settings::NativeAnalog::mapping[i], + default_param); + if (Settings::values.debug_pad_analogs[i].empty()) + Settings::values.debug_pad_analogs[i] = default_param; } - Settings::values.motion_device = sdl2_config->Get( - "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); - Settings::values.touch_device = - sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); - - // Core - Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); - Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); - - // Renderer - Settings::values.resolution_factor = - (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); - Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); - Settings::values.frame_limit = - static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); - Settings::values.use_accurate_gpu_emulation = - sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); - - Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); - Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); - Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); + Settings::values.touchscreen.enabled = + sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true); + Settings::values.touchscreen.device = + sdl2_config->Get("ControlsGeneral", "touch_device", "engine:emu_window"); + Settings::values.touchscreen.finger = + sdl2_config->GetInteger("ControlsGeneral", "touch_finger", 0); + Settings::values.touchscreen.rotation_angle = + sdl2_config->GetInteger("ControlsGeneral", "touch_angle", 0); + Settings::values.touchscreen.diameter_x = + sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); + Settings::values.touchscreen.diameter_y = + sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); - // Audio - Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); - Settings::values.enable_audio_stretching = - sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); - Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); - Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); + std::transform(keyboard_keys.begin(), keyboard_keys.end(), + Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); + std::transform(keyboard_mods.begin(), keyboard_mods.end(), + Settings::values.keyboard_keys.begin() + + Settings::NativeKeyboard::LeftControlKey, + InputCommon::GenerateKeyboardParam); + std::transform(keyboard_mods.begin(), keyboard_mods.end(), + Settings::values.keyboard_mods.begin(), InputCommon::GenerateKeyboardParam); // Data Storage Settings::values.use_virtual_sd = @@ -139,6 +331,30 @@ void Config::ReadValues() { Settings::values.rng_seed = std::nullopt; } + // Core + Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); + Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); + + // Renderer + Settings::values.resolution_factor = + (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0); + Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true); + Settings::values.frame_limit = + static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100)); + Settings::values.use_accurate_gpu_emulation = + sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); + + Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0); + Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0); + Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0); + + // Audio + Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); + Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); + Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); // Miscellaneous |