Compare commits
26 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8178fe8960 | ||
|
|
acfb0b4852 | ||
|
|
2a822f3378 | ||
|
|
05df4a8c94 | ||
|
|
2b1d66eda3 | ||
|
|
dfd998216c | ||
|
|
a104b985a8 | ||
|
|
f64adcfc37 | ||
|
|
deb97f6a8e | ||
|
|
d0e4f1c6f4 | ||
|
|
a31ed02ae4 | ||
|
|
d01eb12f36 | ||
|
|
bbd85a495a | ||
|
|
0fe11746fc | ||
|
|
ac3690f205 | ||
|
|
a167da4278 | ||
|
|
9c6b5cae68 | ||
|
|
ed76c71319 | ||
|
|
5a7eecc3ad | ||
|
|
89b326e396 | ||
|
|
d8e0d839bd | ||
|
|
c7055f3670 | ||
|
|
9a22b6dced | ||
|
|
3ce28342a2 | ||
|
|
f98cd210ab | ||
|
|
a1667a7b46 |
@@ -350,6 +350,13 @@ function(create_target_directory_groups target_name)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Prevent boost from linking against libs when building
|
||||
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
|
||||
-DBOOST_SYSTEM_NO_LIB
|
||||
-DBOOST_DATE_TIME_NO_LIB
|
||||
-DBOOST_REGEX_NO_LIB
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(externals)
|
||||
add_subdirectory(src)
|
||||
|
||||
33
dist/qt_themes/qdarkstyle/style.qss
vendored
33
dist/qt_themes/qdarkstyle/style.qss
vendored
@@ -181,7 +181,7 @@ QMenu::icon {
|
||||
}
|
||||
|
||||
QMenu::item {
|
||||
padding: 5px 30px 5px 30px;
|
||||
padding: 5px 16px 5px 40px;
|
||||
border: 1px solid transparent;
|
||||
/* reserve space for selection border */
|
||||
}
|
||||
@@ -192,12 +192,13 @@ QMenu::item:selected {
|
||||
|
||||
QMenu::separator {
|
||||
height: 2px;
|
||||
background: lightblue;
|
||||
background: #76797C;
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
QMenu::indicator {
|
||||
margin: 0 -26px 0 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
@@ -252,7 +253,7 @@ QWidget:disabled {
|
||||
}
|
||||
|
||||
QAbstractItemView {
|
||||
alternate-background-color: #31363b;
|
||||
alternate-background-color: #2c2f32;
|
||||
color: #eff0f1;
|
||||
border: 1px solid #3A3939;
|
||||
border-radius: 2px;
|
||||
@@ -577,8 +578,6 @@ QTreeView:hover {
|
||||
}
|
||||
|
||||
QComboBox:on {
|
||||
padding-top: 3px;
|
||||
padding-left: 4px;
|
||||
selection-background-color: #4a4a4a;
|
||||
}
|
||||
|
||||
@@ -703,10 +702,10 @@ QTabBar::close-button:pressed {
|
||||
QTabBar::tab:top {
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border-bottom: 1px transparent black;
|
||||
border-bottom: 2px transparent;
|
||||
background-color: #31363b;
|
||||
padding: 5px;
|
||||
min-width: 50px;
|
||||
padding: 4px 16px 2px;
|
||||
min-width: 38px;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
@@ -1078,7 +1077,7 @@ QListView::item:selected:active {
|
||||
}
|
||||
|
||||
QHeaderView {
|
||||
background-color: #31363b;
|
||||
background-color: #403F3F;
|
||||
border: 1px transparent;
|
||||
border-radius: 0px;
|
||||
margin: 0px;
|
||||
@@ -1086,30 +1085,32 @@ QHeaderView {
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #31363b;
|
||||
background-color: #232629;
|
||||
color: #eff0f1;
|
||||
padding: 5px;
|
||||
border: 1px solid #76797C;
|
||||
padding: 0 5px;
|
||||
border: 1px solid #403F3F;
|
||||
border-bottom: 0;
|
||||
border-radius: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
QHeaderView::section::vertical::first,
|
||||
QHeaderView::section::vertical::only-one {
|
||||
border-top: 1px solid #76797C;
|
||||
border-top: 1px solid #31363b;
|
||||
}
|
||||
|
||||
QHeaderView::section::vertical {
|
||||
border-top: transparent;
|
||||
}
|
||||
|
||||
QHeaderView::section::horizontal,
|
||||
QHeaderView::section::horizontal::first,
|
||||
QHeaderView::section::horizontal::only-one {
|
||||
border-left: 1px solid #76797C;
|
||||
border-left: transparent;
|
||||
}
|
||||
|
||||
QHeaderView::section::horizontal {
|
||||
border-left: transparent;
|
||||
QHeaderView::section::horizontal::last {
|
||||
border-right: transparent;
|
||||
}
|
||||
|
||||
QHeaderView::section:checked {
|
||||
|
||||
@@ -77,6 +77,15 @@ else()
|
||||
add_compile_options("-static")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
|
||||
# GNU ar: Create thin archive files.
|
||||
# Requires binutils-2.19 or later.
|
||||
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_C_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(common)
|
||||
|
||||
@@ -120,7 +120,7 @@ private:
|
||||
duration_cast<std::chrono::microseconds>(steady_clock::now() - time_origin);
|
||||
entry.log_class = log_class;
|
||||
entry.log_level = log_level;
|
||||
entry.filename = Common::TrimSourcePath(filename);
|
||||
entry.filename = filename;
|
||||
entry.line_num = line_nr;
|
||||
entry.function = function;
|
||||
entry.message = std::move(message);
|
||||
|
||||
@@ -23,7 +23,7 @@ struct Entry {
|
||||
std::chrono::microseconds timestamp;
|
||||
Class log_class;
|
||||
Level log_level;
|
||||
std::string filename;
|
||||
const char* filename;
|
||||
unsigned int line_num;
|
||||
std::string function;
|
||||
std::string message;
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
|
||||
namespace Log {
|
||||
|
||||
// trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||
constexpr const char* TrimSourcePath(std::string_view source) {
|
||||
const auto rfind = [source](const std::string_view match) {
|
||||
return source.rfind(match) == source.npos ? 0 : (source.rfind(match) + match.size());
|
||||
};
|
||||
auto idx = std::max({rfind("src/"), rfind("src\\"), rfind("../"), rfind("..\\")});
|
||||
return source.data() + idx;
|
||||
}
|
||||
|
||||
/// Specifies the severity or level of detail of the log message.
|
||||
enum class Level : u8 {
|
||||
Trace, ///< Extremely detailed and repetitive debugging information that is likely to
|
||||
@@ -141,24 +150,24 @@ void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsig
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define LOG_TRACE(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Trace, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
#else
|
||||
#define LOG_TRACE(log_class, fmt, ...) (void(0))
|
||||
#endif
|
||||
|
||||
#define LOG_DEBUG(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Debug, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_INFO(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Info, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_WARNING(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Warning, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_ERROR(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Error, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
#define LOG_CRITICAL(log_class, ...) \
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, __FILE__, __LINE__, \
|
||||
__func__, __VA_ARGS__)
|
||||
::Log::FmtLogMessage(::Log::Class::log_class, ::Log::Level::Critical, \
|
||||
::Log::TrimSourcePath(__FILE__), __LINE__, __func__, __VA_ARGS__)
|
||||
|
||||
@@ -223,26 +223,4 @@ std::u16string UTF16StringFromFixedZeroTerminatedBuffer(std::u16string_view buff
|
||||
return std::u16string(buffer.begin(), buffer.begin() + len);
|
||||
}
|
||||
|
||||
const char* TrimSourcePath(const char* path, const char* root) {
|
||||
const char* p = path;
|
||||
|
||||
while (*p != '\0') {
|
||||
const char* next_slash = p;
|
||||
while (*next_slash != '\0' && *next_slash != '/' && *next_slash != '\\') {
|
||||
++next_slash;
|
||||
}
|
||||
|
||||
bool is_src = Common::ComparePartialString(p, next_slash, root);
|
||||
p = next_slash;
|
||||
|
||||
if (*p != '\0') {
|
||||
++p;
|
||||
}
|
||||
if (is_src) {
|
||||
path = p;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -28,6 +28,15 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
bool WaitFor(const std::chrono::duration<Duration>& time) {
|
||||
std::unique_lock lk{mutex};
|
||||
if (!condvar.wait_for(lk, time, [this] { return is_set; }))
|
||||
return false;
|
||||
is_set = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Clock, class Duration>
|
||||
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
|
||||
std::unique_lock lk{mutex};
|
||||
|
||||
@@ -42,6 +42,26 @@ void BSD::Socket(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Select(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Bind(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Connect(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -52,6 +72,26 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::Listen(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::SetSockOpt(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(0); // ret
|
||||
rb.Push<u32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void BSD::SendTo(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -80,7 +120,7 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
|
||||
{2, &BSD::Socket, "Socket"},
|
||||
{3, nullptr, "SocketExempt"},
|
||||
{4, nullptr, "Open"},
|
||||
{5, nullptr, "Select"},
|
||||
{5, &BSD::Select, "Select"},
|
||||
{6, nullptr, "Poll"},
|
||||
{7, nullptr, "Sysctl"},
|
||||
{8, nullptr, "Recv"},
|
||||
@@ -88,15 +128,15 @@ BSD::BSD(const char* name) : ServiceFramework(name) {
|
||||
{10, nullptr, "Send"},
|
||||
{11, &BSD::SendTo, "SendTo"},
|
||||
{12, nullptr, "Accept"},
|
||||
{13, nullptr, "Bind"},
|
||||
{13, &BSD::Bind, "Bind"},
|
||||
{14, &BSD::Connect, "Connect"},
|
||||
{15, nullptr, "GetPeerName"},
|
||||
{16, nullptr, "GetSockName"},
|
||||
{17, nullptr, "GetSockOpt"},
|
||||
{18, nullptr, "Listen"},
|
||||
{18, &BSD::Listen, "Listen"},
|
||||
{19, nullptr, "Ioctl"},
|
||||
{20, nullptr, "Fcntl"},
|
||||
{21, nullptr, "SetSockOpt"},
|
||||
{21, &BSD::SetSockOpt, "SetSockOpt"},
|
||||
{22, nullptr, "Shutdown"},
|
||||
{23, nullptr, "ShutdownAllSockets"},
|
||||
{24, nullptr, "Write"},
|
||||
|
||||
@@ -18,7 +18,11 @@ private:
|
||||
void RegisterClient(Kernel::HLERequestContext& ctx);
|
||||
void StartMonitoring(Kernel::HLERequestContext& ctx);
|
||||
void Socket(Kernel::HLERequestContext& ctx);
|
||||
void Select(Kernel::HLERequestContext& ctx);
|
||||
void Bind(Kernel::HLERequestContext& ctx);
|
||||
void Connect(Kernel::HLERequestContext& ctx);
|
||||
void Listen(Kernel::HLERequestContext& ctx);
|
||||
void SetSockOpt(Kernel::HLERequestContext& ctx);
|
||||
void SendTo(Kernel::HLERequestContext& ctx);
|
||||
void Close(Kernel::HLERequestContext& ctx);
|
||||
|
||||
|
||||
@@ -1019,6 +1019,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time,
|
||||
s64& posix_time) const {
|
||||
if (is_initialized) {
|
||||
return ToPosixTime(time_zone_rule, calendar_time, posix_time);
|
||||
}
|
||||
posix_time = 0;
|
||||
return ERROR_UNINITIALIZED_CLOCK;
|
||||
}
|
||||
|
||||
ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
|
||||
if (!is_initialized) {
|
||||
return ERROR_UNINITIALIZED_CLOCK;
|
||||
|
||||
@@ -39,6 +39,7 @@ public:
|
||||
ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;
|
||||
ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
|
||||
s64& posix_time) const;
|
||||
ResultCode ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const;
|
||||
|
||||
private:
|
||||
bool is_initialized{};
|
||||
|
||||
@@ -22,7 +22,7 @@ ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_
|
||||
{100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
|
||||
{101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
|
||||
{201, &ITimeZoneService::ToPosixTime, "ToPosixTime"},
|
||||
{202, nullptr, "ToPosixTimeWithMyRule"},
|
||||
{202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
@@ -145,4 +145,26 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
|
||||
ctx.WriteBuffer(&posix_time, sizeof(s64));
|
||||
}
|
||||
|
||||
void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Time, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()};
|
||||
|
||||
s64 posix_time{};
|
||||
if (const ResultCode result{
|
||||
time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(calendar_time,
|
||||
posix_time)};
|
||||
result != RESULT_SUCCESS) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushRaw<u32>(1); // Number of times we're returning
|
||||
ctx.WriteBuffer(&posix_time, sizeof(s64));
|
||||
}
|
||||
|
||||
} // namespace Service::Time
|
||||
|
||||
@@ -22,6 +22,7 @@ private:
|
||||
void ToCalendarTime(Kernel::HLERequestContext& ctx);
|
||||
void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx);
|
||||
void ToPosixTime(Kernel::HLERequestContext& ctx);
|
||||
void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx);
|
||||
|
||||
private:
|
||||
TimeZone::TimeZoneContentManager& time_zone_content_manager;
|
||||
|
||||
@@ -97,7 +97,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
if (nso_header.IsSegmentCompressed(i)) {
|
||||
data = DecompressSegment(data, nso_header.segments[i]);
|
||||
}
|
||||
program_image.resize(nso_header.segments[i].location + data.size());
|
||||
program_image.resize(nso_header.segments[i].location +
|
||||
PageAlignSize(static_cast<u32>(data.size())));
|
||||
std::memcpy(program_image.data() + nso_header.segments[i].location, data.data(),
|
||||
data.size());
|
||||
codeset.segments[i].addr = nso_header.segments[i].location;
|
||||
@@ -105,8 +106,12 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
|
||||
codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
|
||||
}
|
||||
|
||||
if (should_pass_arguments && !Settings::values.program_args.empty()) {
|
||||
const auto arg_data = Settings::values.program_args;
|
||||
if (should_pass_arguments) {
|
||||
std::vector<u8> arg_data{Settings::values.program_args.begin(),
|
||||
Settings::values.program_args.end()};
|
||||
if (arg_data.empty()) {
|
||||
arg_data.resize(NSO_ARGUMENT_DEFAULT_SIZE);
|
||||
}
|
||||
codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
|
||||
NSOArgumentHeader args_header{
|
||||
NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
|
||||
|
||||
@@ -56,6 +56,8 @@ static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
|
||||
|
||||
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
|
||||
// NOTE: Official software default argument state is unverified.
|
||||
constexpr u64 NSO_ARGUMENT_DEFAULT_SIZE = 1;
|
||||
|
||||
struct NSOArgumentHeader {
|
||||
u32_le allocated_size;
|
||||
|
||||
@@ -401,6 +401,9 @@ struct Values {
|
||||
std::string motion_device;
|
||||
TouchscreenInput touchscreen;
|
||||
std::atomic_bool is_device_reload_pending{true};
|
||||
std::string udp_input_address;
|
||||
u16 udp_input_port;
|
||||
u8 udp_pad_index;
|
||||
|
||||
// Core
|
||||
bool use_multi_core;
|
||||
|
||||
@@ -9,6 +9,12 @@ add_library(input_common STATIC
|
||||
motion_emu.h
|
||||
sdl/sdl.cpp
|
||||
sdl/sdl.h
|
||||
udp/client.cpp
|
||||
udp/client.h
|
||||
udp/protocol.cpp
|
||||
udp/protocol.h
|
||||
udp/udp.cpp
|
||||
udp/udp.h
|
||||
)
|
||||
|
||||
if(SDL2_FOUND)
|
||||
@@ -21,4 +27,4 @@ if(SDL2_FOUND)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(input_common)
|
||||
target_link_libraries(input_common PUBLIC core PRIVATE common)
|
||||
target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES})
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "input_common/keyboard.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/motion_emu.h"
|
||||
#include "input_common/udp/udp.h"
|
||||
#ifdef HAVE_SDL2
|
||||
#include "input_common/sdl/sdl.h"
|
||||
#endif
|
||||
@@ -18,6 +19,7 @@ namespace InputCommon {
|
||||
static std::shared_ptr<Keyboard> keyboard;
|
||||
static std::shared_ptr<MotionEmu> motion_emu;
|
||||
static std::unique_ptr<SDL::State> sdl;
|
||||
static std::unique_ptr<CemuhookUDP::State> udp;
|
||||
|
||||
void Init() {
|
||||
keyboard = std::make_shared<Keyboard>();
|
||||
@@ -28,6 +30,8 @@ void Init() {
|
||||
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
|
||||
|
||||
sdl = SDL::Init();
|
||||
|
||||
udp = CemuhookUDP::Init();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
|
||||
namespace Polling {
|
||||
|
||||
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
|
||||
std::vector<std::unique_ptr<DevicePoller>> pollers;
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
return sdl->GetPollers(type);
|
||||
#else
|
||||
return {};
|
||||
pollers = sdl->GetPollers(type);
|
||||
#endif
|
||||
|
||||
return pollers;
|
||||
}
|
||||
|
||||
} // namespace Polling
|
||||
|
||||
287
src/input_common/udp/client.cpp
Normal file
287
src/input_common/udp/client.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "input_common/udp/protocol.h"
|
||||
|
||||
using boost::asio::ip::address_v4;
|
||||
using boost::asio::ip::udp;
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
struct SocketCallback {
|
||||
std::function<void(Response::Version)> version;
|
||||
std::function<void(Response::PortInfo)> port_info;
|
||||
std::function<void(Response::PadData)> pad_data;
|
||||
};
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
using clock = std::chrono::system_clock;
|
||||
|
||||
explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||
SocketCallback callback)
|
||||
: client_id(client_id), timer(io_service),
|
||||
send_endpoint(udp::endpoint(address_v4::from_string(host), port)),
|
||||
socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index),
|
||||
callback(std::move(callback)) {}
|
||||
|
||||
void Stop() {
|
||||
io_service.stop();
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
io_service.run();
|
||||
}
|
||||
|
||||
void StartSend(const clock::time_point& from) {
|
||||
timer.expires_at(from + std::chrono::seconds(3));
|
||||
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
|
||||
}
|
||||
|
||||
void StartReceive() {
|
||||
socket.async_receive_from(
|
||||
boost::asio::buffer(receive_buffer), receive_endpoint,
|
||||
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
|
||||
HandleReceive(error, bytes_transferred);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
|
||||
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
|
||||
switch (*type) {
|
||||
case Type::Version: {
|
||||
Response::Version version;
|
||||
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
|
||||
callback.version(std::move(version));
|
||||
break;
|
||||
}
|
||||
case Type::PortInfo: {
|
||||
Response::PortInfo port_info;
|
||||
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
|
||||
sizeof(Response::PortInfo));
|
||||
callback.port_info(std::move(port_info));
|
||||
break;
|
||||
}
|
||||
case Type::PadData: {
|
||||
Response::PadData pad_data;
|
||||
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
|
||||
callback.pad_data(std::move(pad_data));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
StartReceive();
|
||||
}
|
||||
|
||||
void HandleSend(const boost::system::error_code& error) {
|
||||
// Send a request for getting port info for the pad
|
||||
Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
|
||||
const auto port_message = Request::Create(port_info, client_id);
|
||||
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
|
||||
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
|
||||
|
||||
// Send a request for getting pad data for the pad
|
||||
Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
|
||||
const auto pad_message = Request::Create(pad_data, client_id);
|
||||
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
|
||||
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
|
||||
StartSend(timer.expiry());
|
||||
}
|
||||
|
||||
SocketCallback callback;
|
||||
boost::asio::io_service io_service;
|
||||
boost::asio::basic_waitable_timer<clock> timer;
|
||||
udp::socket socket;
|
||||
|
||||
u32 client_id{};
|
||||
u8 pad_index{};
|
||||
|
||||
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
|
||||
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
|
||||
std::array<u8, PORT_INFO_SIZE> send_buffer1;
|
||||
std::array<u8, PAD_DATA_SIZE> send_buffer2;
|
||||
udp::endpoint send_endpoint;
|
||||
|
||||
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
|
||||
udp::endpoint receive_endpoint;
|
||||
};
|
||||
|
||||
static void SocketLoop(Socket* socket) {
|
||||
socket->StartReceive();
|
||||
socket->StartSend(Socket::clock::now());
|
||||
socket->Loop();
|
||||
}
|
||||
|
||||
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
|
||||
u8 pad_index, u32 client_id)
|
||||
: status(status) {
|
||||
StartCommunication(host, port, pad_index, client_id);
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
socket->Stop();
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
||||
socket->Stop();
|
||||
thread.join();
|
||||
StartCommunication(host, port, pad_index, client_id);
|
||||
}
|
||||
|
||||
void Client::OnVersion(Response::Version data) {
|
||||
LOG_TRACE(Input, "Version packet received: {}", data.version);
|
||||
}
|
||||
|
||||
void Client::OnPortInfo(Response::PortInfo data) {
|
||||
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
|
||||
}
|
||||
|
||||
void Client::OnPadData(Response::PadData data) {
|
||||
LOG_TRACE(Input, "PadData packet received");
|
||||
if (data.packet_counter <= packet_sequence) {
|
||||
LOG_WARNING(
|
||||
Input,
|
||||
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
|
||||
packet_sequence, data.packet_counter);
|
||||
return;
|
||||
}
|
||||
packet_sequence = data.packet_counter;
|
||||
// TODO: Check how the Switch handles motions and how the CemuhookUDP motion
|
||||
// directions correspond to the ones of the Switch
|
||||
Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
|
||||
Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
|
||||
{
|
||||
std::lock_guard guard(status->update_mutex);
|
||||
|
||||
status->motion_status = {accel, gyro};
|
||||
|
||||
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
|
||||
// between a simple "tap" and a hard press that causes the touch screen to click.
|
||||
const bool is_active = data.touch_1.is_active != 0;
|
||||
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
|
||||
if (is_active && status->touch_calibration) {
|
||||
const u16 min_x = status->touch_calibration->min_x;
|
||||
const u16 max_x = status->touch_calibration->max_x;
|
||||
const u16 min_y = status->touch_calibration->min_y;
|
||||
const u16 max_y = status->touch_calibration->max_y;
|
||||
|
||||
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
|
||||
static_cast<float>(max_x - min_x);
|
||||
y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
|
||||
static_cast<float>(max_y - min_y);
|
||||
}
|
||||
|
||||
status->touch_status = {x, y, is_active};
|
||||
}
|
||||
}
|
||||
|
||||
void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
|
||||
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
|
||||
[this](Response::PortInfo info) { OnPortInfo(info); },
|
||||
[this](Response::PadData data) { OnPadData(data); }};
|
||||
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
|
||||
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
|
||||
thread = std::thread{SocketLoop, this->socket.get()};
|
||||
}
|
||||
|
||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||
std::function<void()> success_callback,
|
||||
std::function<void()> failure_callback) {
|
||||
std::thread([=] {
|
||||
Common::Event success_event;
|
||||
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
|
||||
[&](Response::PadData data) { success_event.Set(); }};
|
||||
Socket socket{host, port, pad_index, client_id, callback};
|
||||
std::thread worker_thread{SocketLoop, &socket};
|
||||
bool result = success_event.WaitFor(std::chrono::seconds(8));
|
||||
socket.Stop();
|
||||
worker_thread.join();
|
||||
if (result) {
|
||||
success_callback();
|
||||
} else {
|
||||
failure_callback();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::CalibrationConfigurationJob(
|
||||
const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||
std::function<void(Status)> status_callback,
|
||||
std::function<void(u16, u16, u16, u16)> data_callback) {
|
||||
|
||||
std::thread([=] {
|
||||
constexpr u16 CALIBRATION_THRESHOLD = 100;
|
||||
|
||||
u16 min_x{UINT16_MAX};
|
||||
u16 min_y{UINT16_MAX};
|
||||
u16 max_x{};
|
||||
u16 max_y{};
|
||||
|
||||
Status current_status{Status::Initialized};
|
||||
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
|
||||
[&](Response::PadData data) {
|
||||
if (current_status == Status::Initialized) {
|
||||
// Receiving data means the communication is ready now
|
||||
current_status = Status::Ready;
|
||||
status_callback(current_status);
|
||||
}
|
||||
if (!data.touch_1.is_active) {
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
|
||||
data.touch_1.y);
|
||||
min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
|
||||
min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
|
||||
if (current_status == Status::Ready) {
|
||||
// First touch - min data (min_x/min_y)
|
||||
current_status = Status::Stage1Completed;
|
||||
status_callback(current_status);
|
||||
}
|
||||
if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
|
||||
data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
|
||||
// Set the current position as max value and finishes
|
||||
// configuration
|
||||
max_x = data.touch_1.x;
|
||||
max_y = data.touch_1.y;
|
||||
current_status = Status::Completed;
|
||||
data_callback(min_x, min_y, max_x, max_y);
|
||||
status_callback(current_status);
|
||||
|
||||
complete_event.Set();
|
||||
}
|
||||
}};
|
||||
Socket socket{host, port, pad_index, client_id, callback};
|
||||
std::thread worker_thread{SocketLoop, &socket};
|
||||
complete_event.Wait();
|
||||
socket.Stop();
|
||||
worker_thread.join();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void CalibrationConfigurationJob::Stop() {
|
||||
complete_event.Set();
|
||||
}
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
96
src/input_common/udp/client.h
Normal file
96
src/input_common/udp/client.h
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
constexpr u16 DEFAULT_PORT = 26760;
|
||||
constexpr char DEFAULT_ADDR[] = "127.0.0.1";
|
||||
|
||||
class Socket;
|
||||
|
||||
namespace Response {
|
||||
struct PadData;
|
||||
struct PortInfo;
|
||||
struct Version;
|
||||
} // namespace Response
|
||||
|
||||
struct DeviceStatus {
|
||||
std::mutex update_mutex;
|
||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
|
||||
std::tuple<float, float, bool> touch_status;
|
||||
|
||||
// calibration data for scaling the device's touch area to 3ds
|
||||
struct CalibrationData {
|
||||
u16 min_x{};
|
||||
u16 min_y{};
|
||||
u16 max_x{};
|
||||
u16 max_y{};
|
||||
};
|
||||
std::optional<CalibrationData> touch_calibration;
|
||||
};
|
||||
|
||||
class Client {
|
||||
public:
|
||||
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
|
||||
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
|
||||
~Client();
|
||||
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
|
||||
u32 client_id = 24872);
|
||||
|
||||
private:
|
||||
void OnVersion(Response::Version);
|
||||
void OnPortInfo(Response::PortInfo);
|
||||
void OnPadData(Response::PadData);
|
||||
void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
|
||||
|
||||
std::unique_ptr<Socket> socket;
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
std::thread thread;
|
||||
u64 packet_sequence = 0;
|
||||
};
|
||||
|
||||
/// An async job allowing configuration of the touchpad calibration.
|
||||
class CalibrationConfigurationJob {
|
||||
public:
|
||||
enum class Status {
|
||||
Initialized,
|
||||
Ready,
|
||||
Stage1Completed,
|
||||
Completed,
|
||||
};
|
||||
/**
|
||||
* Constructs and starts the job with the specified parameter.
|
||||
*
|
||||
* @param status_callback Callback for job status updates
|
||||
* @param data_callback Called when calibration data is ready
|
||||
*/
|
||||
explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
|
||||
u32 client_id, std::function<void(Status)> status_callback,
|
||||
std::function<void(u16, u16, u16, u16)> data_callback);
|
||||
~CalibrationConfigurationJob();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
Common::Event complete_event;
|
||||
};
|
||||
|
||||
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
|
||||
std::function<void()> success_callback,
|
||||
std::function<void()> failure_callback);
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
79
src/input_common/udp/protocol.cpp
Normal file
79
src/input_common/udp/protocol.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/udp/protocol.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
static constexpr std::size_t GetSizeOfResponseType(Type t) {
|
||||
switch (t) {
|
||||
case Type::Version:
|
||||
return sizeof(Response::Version);
|
||||
case Type::PortInfo:
|
||||
return sizeof(Response::PortInfo);
|
||||
case Type::PadData:
|
||||
return sizeof(Response::PadData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace Response {
|
||||
|
||||
/**
|
||||
* Returns Type if the packet is valid, else none
|
||||
*
|
||||
* Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
|
||||
* copying the buffer)
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size) {
|
||||
if (size < sizeof(Header)) {
|
||||
LOG_DEBUG(Input, "Invalid UDP packet received");
|
||||
return std::nullopt;
|
||||
}
|
||||
Header header{};
|
||||
std::memcpy(&header, data, sizeof(Header));
|
||||
if (header.magic != SERVER_MAGIC) {
|
||||
LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.protocol_version != PROTOCOL_VERSION) {
|
||||
LOG_ERROR(Input, "UDP Packet protocol mismatch");
|
||||
return std::nullopt;
|
||||
}
|
||||
if (header.type < Type::Version || header.type > Type::PadData) {
|
||||
LOG_ERROR(Input, "UDP Packet is an unknown type");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Packet size must equal sizeof(Header) + sizeof(Data)
|
||||
// and also verify that the packet info mentions the correct size. Since the spec includes the
|
||||
// type of the packet as part of the data, we need to include it in size calculations here
|
||||
// ie: payload_length == sizeof(T) + sizeof(Type)
|
||||
const std::size_t data_len = GetSizeOfResponseType(header.type);
|
||||
if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
|
||||
LOG_ERROR(
|
||||
Input,
|
||||
"UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
|
||||
size, header.payload_length, data_len + sizeof(Type));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const u32 crc32 = header.crc;
|
||||
boost::crc_32_type result;
|
||||
// zero out the crc in the buffer and then run the crc against it
|
||||
std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
|
||||
|
||||
result.process_bytes(data, data_len + sizeof(Header));
|
||||
if (crc32 != result.checksum()) {
|
||||
LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
|
||||
return std::nullopt;
|
||||
}
|
||||
return header.type;
|
||||
}
|
||||
} // namespace Response
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
256
src/input_common/udp/protocol.h
Normal file
256
src/input_common/udp/protocol.h
Normal file
@@ -0,0 +1,256 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <boost/crc.hpp>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
constexpr std::size_t MAX_PACKET_SIZE = 100;
|
||||
constexpr u16 PROTOCOL_VERSION = 1001;
|
||||
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
|
||||
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
|
||||
|
||||
enum class Type : u32 {
|
||||
Version = 0x00100000,
|
||||
PortInfo = 0x00100001,
|
||||
PadData = 0x00100002,
|
||||
};
|
||||
|
||||
struct Header {
|
||||
u32_le magic{};
|
||||
u16_le protocol_version{};
|
||||
u16_le payload_length{};
|
||||
u32_le crc{};
|
||||
u32_le id{};
|
||||
///> In the protocol, the type of the packet is not part of the header, but its convenient to
|
||||
///> include in the header so the callee doesn't have to duplicate the type twice when building
|
||||
///> the data
|
||||
Type type{};
|
||||
};
|
||||
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
template <typename T>
|
||||
struct Message {
|
||||
Header header{};
|
||||
T data;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
template <typename T>
|
||||
constexpr Type GetMessageType();
|
||||
|
||||
namespace Request {
|
||||
|
||||
struct Version {};
|
||||
/**
|
||||
* Requests the server to send information about what controllers are plugged into the ports
|
||||
* In citra's case, we only have one controller, so for simplicity's sake, we can just send a
|
||||
* request explicitly for the first controller port and leave it at that. In the future it would be
|
||||
* nice to make this configurable
|
||||
*/
|
||||
constexpr u32 MAX_PORTS = 4;
|
||||
struct PortInfo {
|
||||
u32_le pad_count{}; ///> Number of ports to request data for
|
||||
std::array<u8, MAX_PORTS> port;
|
||||
};
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"UDP Request PortInfo is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Request the latest pad information from the server. If the server hasn't received this message
|
||||
* from the client in a reasonable time frame, the server will stop sending updates. The default
|
||||
* timeout seems to be 5 seconds.
|
||||
*/
|
||||
struct PadData {
|
||||
enum class Flags : u8 {
|
||||
AllPorts,
|
||||
Id,
|
||||
Mac,
|
||||
};
|
||||
/// Determines which method will be used as a look up for the controller
|
||||
Flags flags{};
|
||||
/// Index of the port of the controller to retrieve data about
|
||||
u8 port_id{};
|
||||
/// Mac address of the controller to retrieve data about
|
||||
MacAddress mac;
|
||||
};
|
||||
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Request PadData is not trivially copyable");
|
||||
|
||||
/**
|
||||
* Creates a message with the proper header data that can be sent to the server.
|
||||
* @param T data Request body to send
|
||||
* @param client_id ID of the udp client (usually not checked on the server)
|
||||
*/
|
||||
template <typename T>
|
||||
Message<T> Create(const T data, const u32 client_id = 0) {
|
||||
boost::crc_32_type crc;
|
||||
Header header{
|
||||
CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
|
||||
};
|
||||
Message<T> message{header, data};
|
||||
crc.process_bytes(&message, sizeof(Message<T>));
|
||||
message.header.crc = crc.checksum();
|
||||
return message;
|
||||
}
|
||||
} // namespace Request
|
||||
|
||||
namespace Response {
|
||||
|
||||
struct Version {
|
||||
u16_le version{};
|
||||
};
|
||||
static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<Version>,
|
||||
"UDP Response Version is not trivially copyable");
|
||||
|
||||
struct PortInfo {
|
||||
u8 id{};
|
||||
u8 state{};
|
||||
u8 model{};
|
||||
u8 connection_type{};
|
||||
MacAddress mac;
|
||||
u8 battery{};
|
||||
u8 is_pad_active{};
|
||||
};
|
||||
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
|
||||
static_assert(std::is_trivially_copyable_v<PortInfo>,
|
||||
"UDP Response PortInfo is not trivially copyable");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PadData {
|
||||
PortInfo info{};
|
||||
u32_le packet_counter{};
|
||||
|
||||
u16_le digital_button{};
|
||||
// The following union isn't trivially copyable but we don't use this input anyway.
|
||||
// union DigitalButton {
|
||||
// u16_le button;
|
||||
// BitField<0, 1, u16> button_1; // Share
|
||||
// BitField<1, 1, u16> button_2; // L3
|
||||
// BitField<2, 1, u16> button_3; // R3
|
||||
// BitField<3, 1, u16> button_4; // Options
|
||||
// BitField<4, 1, u16> button_5; // Up
|
||||
// BitField<5, 1, u16> button_6; // Right
|
||||
// BitField<6, 1, u16> button_7; // Down
|
||||
// BitField<7, 1, u16> button_8; // Left
|
||||
// BitField<8, 1, u16> button_9; // L2
|
||||
// BitField<9, 1, u16> button_10; // R2
|
||||
// BitField<10, 1, u16> button_11; // L1
|
||||
// BitField<11, 1, u16> button_12; // R1
|
||||
// BitField<12, 1, u16> button_13; // Triangle
|
||||
// BitField<13, 1, u16> button_14; // Circle
|
||||
// BitField<14, 1, u16> button_15; // Cross
|
||||
// BitField<15, 1, u16> button_16; // Square
|
||||
// } digital_button;
|
||||
|
||||
u8 home;
|
||||
/// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
|
||||
u8 touch_hard_press{};
|
||||
u8 left_stick_x{};
|
||||
u8 left_stick_y{};
|
||||
u8 right_stick_x{};
|
||||
u8 right_stick_y{};
|
||||
|
||||
struct AnalogButton {
|
||||
u8 button_8{};
|
||||
u8 button_7{};
|
||||
u8 button_6{};
|
||||
u8 button_5{};
|
||||
u8 button_12{};
|
||||
u8 button_11{};
|
||||
u8 button_10{};
|
||||
u8 button_9{};
|
||||
u8 button_16{};
|
||||
u8 button_15{};
|
||||
u8 button_14{};
|
||||
u8 button_13{};
|
||||
} analog_button;
|
||||
|
||||
struct TouchPad {
|
||||
u8 is_active{};
|
||||
u8 id{};
|
||||
u16_le x{};
|
||||
u16_le y{};
|
||||
} touch_1, touch_2;
|
||||
|
||||
u64_le motion_timestamp;
|
||||
|
||||
struct Accelerometer {
|
||||
float x{};
|
||||
float y{};
|
||||
float z{};
|
||||
} accel;
|
||||
|
||||
struct Gyroscope {
|
||||
float pitch{};
|
||||
float yaw{};
|
||||
float roll{};
|
||||
} gyro;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
|
||||
static_assert(std::is_trivially_copyable_v<PadData>,
|
||||
"UDP Response PadData is not trivially copyable");
|
||||
|
||||
static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
|
||||
"UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
|
||||
|
||||
static_assert(sizeof(PadData::AnalogButton) == 12,
|
||||
"UDP Response AnalogButton struct has wrong size ");
|
||||
static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Accelerometer) == 12,
|
||||
"UDP Response Accelerometer struct has wrong size ");
|
||||
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
|
||||
|
||||
/**
|
||||
* Create a Response Message from the data
|
||||
* @param data array of bytes sent from the server
|
||||
* @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
|
||||
* copy the data into the appropriate struct for that Type
|
||||
*/
|
||||
std::optional<Type> Validate(u8* data, std::size_t size);
|
||||
|
||||
} // namespace Response
|
||||
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Request::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::Version>() {
|
||||
return Type::Version;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PortInfo>() {
|
||||
return Type::PortInfo;
|
||||
}
|
||||
template <>
|
||||
constexpr Type GetMessageType<Response::PadData>() {
|
||||
return Type::PadData;
|
||||
}
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
96
src/input_common/udp/udp.cpp
Normal file
96
src/input_common/udp/udp.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/param_package.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "input_common/udp/udp.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
class UDPTouchDevice final : public Input::TouchDevice {
|
||||
public:
|
||||
explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
||||
std::tuple<float, float, bool> GetStatus() const {
|
||||
std::lock_guard guard(status->update_mutex);
|
||||
return status->touch_status;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
};
|
||||
|
||||
class UDPMotionDevice final : public Input::MotionDevice {
|
||||
public:
|
||||
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
||||
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const {
|
||||
std::lock_guard guard(status->update_mutex);
|
||||
return status->motion_status;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
};
|
||||
|
||||
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
|
||||
public:
|
||||
explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
||||
|
||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
|
||||
{
|
||||
std::lock_guard guard(status->update_mutex);
|
||||
status->touch_calibration.emplace();
|
||||
// These default values work well for DS4 but probably not other touch inputs
|
||||
status->touch_calibration->min_x = params.Get("min_x", 100);
|
||||
status->touch_calibration->min_y = params.Get("min_y", 50);
|
||||
status->touch_calibration->max_x = params.Get("max_x", 1800);
|
||||
status->touch_calibration->max_y = params.Get("max_y", 850);
|
||||
}
|
||||
return std::make_unique<UDPTouchDevice>(status);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
};
|
||||
|
||||
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
|
||||
public:
|
||||
explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
|
||||
|
||||
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
|
||||
return std::make_unique<UDPMotionDevice>(status);
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<DeviceStatus> status;
|
||||
};
|
||||
|
||||
State::State() {
|
||||
auto status = std::make_shared<DeviceStatus>();
|
||||
client =
|
||||
std::make_unique<Client>(status, Settings::values.udp_input_address,
|
||||
Settings::values.udp_input_port, Settings::values.udp_pad_index);
|
||||
|
||||
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
|
||||
std::make_shared<UDPTouchFactory>(status));
|
||||
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
|
||||
std::make_shared<UDPMotionFactory>(status));
|
||||
}
|
||||
|
||||
State::~State() {
|
||||
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
|
||||
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
|
||||
}
|
||||
|
||||
void State::ReloadUDPClient() {
|
||||
client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
|
||||
Settings::values.udp_pad_index);
|
||||
}
|
||||
|
||||
std::unique_ptr<State> Init() {
|
||||
return std::make_unique<State>();
|
||||
}
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
27
src/input_common/udp/udp.h
Normal file
27
src/input_common/udp/udp.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/udp/client.h"
|
||||
|
||||
namespace InputCommon::CemuhookUDP {
|
||||
|
||||
class UDPTouchDevice;
|
||||
class UDPMotionDevice;
|
||||
|
||||
class State {
|
||||
public:
|
||||
State();
|
||||
~State();
|
||||
void ReloadUDPClient();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Client> client;
|
||||
};
|
||||
|
||||
std::unique_ptr<State> Init();
|
||||
|
||||
} // namespace InputCommon::CemuhookUDP
|
||||
@@ -260,6 +260,10 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp
|
||||
"#extension GL_NV_shader_thread_group : require\n"
|
||||
"#extension GL_NV_shader_thread_shuffle : require\n";
|
||||
}
|
||||
// This pragma stops Nvidia's driver from over optimizing math (probably using fp16 operations)
|
||||
// on places where we don't want to.
|
||||
// Thanks to Ryujinx for finding this workaround.
|
||||
source += "#pragma optionNV(fastmath off)\n";
|
||||
|
||||
if (shader_type == ShaderType::Geometry) {
|
||||
const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode);
|
||||
|
||||
@@ -1019,7 +1019,6 @@ private:
|
||||
}
|
||||
return {{"gl_ViewportIndex", Type::Int}};
|
||||
case 3:
|
||||
UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
|
||||
return {{"gl_PointSize", Type::Float}};
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -161,16 +161,16 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
case OpCode::Id::TXD: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.txd.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.txd.is_array != 0, "TXD Array is not implemented");
|
||||
|
||||
const bool is_array = instr.txd.is_array != 0;
|
||||
u64 base_reg = instr.gpr8.Value();
|
||||
const auto derivate_reg = instr.gpr20.Value();
|
||||
const auto texture_type = instr.txd.texture_type.Value();
|
||||
const auto coord_count = GetCoordCount(texture_type);
|
||||
|
||||
const Sampler* sampler = is_bindless
|
||||
? GetBindlessSampler(base_reg, {{texture_type, false, false}})
|
||||
: GetSampler(instr.sampler, {{texture_type, false, false}});
|
||||
const Sampler* sampler =
|
||||
is_bindless ? GetBindlessSampler(base_reg, {{texture_type, is_array, false}})
|
||||
: GetSampler(instr.sampler, {{texture_type, is_array, false}});
|
||||
Node4 values;
|
||||
if (sampler == nullptr) {
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
@@ -179,6 +179,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
WriteTexInstructionFloat(bb, instr, values);
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_bindless) {
|
||||
base_reg++;
|
||||
}
|
||||
@@ -192,8 +193,14 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
derivates.push_back(GetRegister(derivate_reg + derivate + 1));
|
||||
}
|
||||
|
||||
Node array_node = {};
|
||||
if (is_array) {
|
||||
const Node info_reg = GetRegister(base_reg + coord_count);
|
||||
array_node = BitfieldExtract(info_reg, 0, 16);
|
||||
}
|
||||
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
MetaTexture meta{*sampler, {}, {}, {}, {}, derivates, {}, {}, {}, element};
|
||||
MetaTexture meta{*sampler, array_node, {}, {}, {}, derivates, {}, {}, {}, element};
|
||||
values[element] = Operation(OperationCode::TextureGradient, std::move(meta), coords);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ add_executable(yuzu
|
||||
configuration/configure_filesystem.cpp
|
||||
configuration/configure_filesystem.h
|
||||
configuration/configure_filesystem.ui
|
||||
configuration/configure_gamelist.cpp
|
||||
configuration/configure_gamelist.h
|
||||
configuration/configure_gamelist.ui
|
||||
configuration/configure_general.cpp
|
||||
configuration/configure_general.h
|
||||
configuration/configure_general.ui
|
||||
@@ -75,6 +72,9 @@ add_executable(yuzu
|
||||
configuration/configure_touchscreen_advanced.cpp
|
||||
configuration/configure_touchscreen_advanced.h
|
||||
configuration/configure_touchscreen_advanced.ui
|
||||
configuration/configure_ui.cpp
|
||||
configuration/configure_ui.h
|
||||
configuration/configure_ui.ui
|
||||
configuration/configure_web.cpp
|
||||
configuration/configure_web.h
|
||||
configuration/configure_web.ui
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/hid/controllers/npad.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
|
||||
@@ -429,6 +430,16 @@ void Config::ReadControlValues() {
|
||||
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
|
||||
.toString()
|
||||
.toStdString();
|
||||
Settings::values.udp_input_address =
|
||||
ReadSetting(QStringLiteral("udp_input_address"),
|
||||
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
|
||||
.toString()
|
||||
.toStdString();
|
||||
Settings::values.udp_input_port = static_cast<u16>(
|
||||
ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
|
||||
.toInt());
|
||||
Settings::values.udp_pad_index =
|
||||
static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
@@ -911,6 +922,12 @@ void Config::SaveControlValues() {
|
||||
QString::fromStdString(Settings::values.motion_device),
|
||||
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
|
||||
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
|
||||
WriteSetting(QStringLiteral("udp_input_address"),
|
||||
QString::fromStdString(Settings::values.udp_input_address),
|
||||
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
|
||||
WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
|
||||
InputCommon::CemuhookUDP::DEFAULT_PORT);
|
||||
WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
|
||||
|
||||
qt_config->endGroup();
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="ConfigureGameList" name="gameListTab">
|
||||
<widget class="ConfigureUi" name="uiTab">
|
||||
<attribute name="title">
|
||||
<string>Game List</string>
|
||||
</attribute>
|
||||
@@ -166,9 +166,9 @@
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ConfigureGameList</class>
|
||||
<class>ConfigureUi</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>configuration/configure_gamelist.h</header>
|
||||
<header>configuration/configure_ui.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
|
||||
@@ -34,7 +34,7 @@ void ConfigureDialog::SetConfiguration() {}
|
||||
|
||||
void ConfigureDialog::ApplyConfiguration() {
|
||||
ui->generalTab->ApplyConfiguration();
|
||||
ui->gameListTab->ApplyConfiguration();
|
||||
ui->uiTab->ApplyConfiguration();
|
||||
ui->systemTab->ApplyConfiguration();
|
||||
ui->profileManagerTab->ApplyConfiguration();
|
||||
ui->filesystemTab->applyConfiguration();
|
||||
@@ -74,7 +74,7 @@ Q_DECLARE_METATYPE(QList<QWidget*>);
|
||||
|
||||
void ConfigureDialog::PopulateSelectionList() {
|
||||
const std::array<std::pair<QString, QList<QWidget*>>, 5> items{
|
||||
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
|
||||
{{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->uiTab}},
|
||||
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
|
||||
{tr("Graphics"), {ui->graphicsTab}},
|
||||
{tr("Audio"), {ui->audioTab}},
|
||||
@@ -108,7 +108,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
|
||||
{ui->audioTab, tr("Audio")},
|
||||
{ui->debugTab, tr("Debug")},
|
||||
{ui->webTab, tr("Web")},
|
||||
{ui->gameListTab, tr("Game List")},
|
||||
{ui->uiTab, tr("UI")},
|
||||
{ui->filesystemTab, tr("Filesystem")},
|
||||
{ui->serviceTab, tr("Services")},
|
||||
};
|
||||
|
||||
@@ -15,11 +15,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
||||
|
||||
ui->setupUi(this);
|
||||
|
||||
for (const auto& theme : UISettings::themes) {
|
||||
ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
|
||||
QString::fromUtf8(theme.second));
|
||||
}
|
||||
|
||||
SetConfiguration();
|
||||
|
||||
connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
|
||||
@@ -30,7 +25,6 @@ ConfigureGeneral::~ConfigureGeneral() = default;
|
||||
void ConfigureGeneral::SetConfiguration() {
|
||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
|
||||
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
|
||||
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
|
||||
|
||||
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
|
||||
@@ -41,8 +35,6 @@ void ConfigureGeneral::SetConfiguration() {
|
||||
void ConfigureGeneral::ApplyConfiguration() {
|
||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
|
||||
UISettings::values.theme =
|
||||
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
|
||||
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
|
||||
|
||||
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
|
||||
|
||||
@@ -65,39 +65,12 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_background_pause">
|
||||
<property name="text">
|
||||
<string>Pause emulation when in background</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="theme_group_box">
|
||||
<property name="title">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="theme_qhbox_layout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="theme_qvbox_layout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="theme_qhbox_layout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="theme_label">
|
||||
<property name="text">
|
||||
<string>Theme:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="theme_combobox"/>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QCheckBox" name="toggle_background_pause">
|
||||
<property name="text">
|
||||
<string>Pause emulation when in background</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/settings.h"
|
||||
#include "ui_configure_gamelist.h"
|
||||
#include "yuzu/configuration/configure_gamelist.h"
|
||||
#include "ui_configure_ui.h"
|
||||
#include "yuzu/configuration/configure_ui.h"
|
||||
#include "yuzu/uisettings.h"
|
||||
|
||||
namespace {
|
||||
@@ -26,35 +26,40 @@ constexpr std::array row_text_names{
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureGameList::ConfigureGameList(QWidget* parent)
|
||||
: QWidget(parent), ui(new Ui::ConfigureGameList) {
|
||||
ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
|
||||
ui->setupUi(this);
|
||||
|
||||
for (const auto& theme : UISettings::themes) {
|
||||
ui->theme_combobox->addItem(QString::fromUtf8(theme.first),
|
||||
QString::fromUtf8(theme.second));
|
||||
}
|
||||
|
||||
InitializeIconSizeComboBox();
|
||||
InitializeRowComboBoxes();
|
||||
|
||||
SetConfiguration();
|
||||
|
||||
// Force game list reload if any of the relevant settings are changed.
|
||||
connect(ui->show_unknown, &QCheckBox::stateChanged, this,
|
||||
&ConfigureGameList::RequestGameListUpdate);
|
||||
connect(ui->show_unknown, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGameList::RequestGameListUpdate);
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGameList::RequestGameListUpdate);
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureGameList::RequestGameListUpdate);
|
||||
&ConfigureUi::RequestGameListUpdate);
|
||||
|
||||
// Update text ComboBoxes after user interaction.
|
||||
connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::activated),
|
||||
[=]() { ConfigureGameList::UpdateSecondRowComboBox(); });
|
||||
[=]() { ConfigureUi::UpdateSecondRowComboBox(); });
|
||||
connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::activated),
|
||||
[=]() { ConfigureGameList::UpdateFirstRowComboBox(); });
|
||||
[=]() { ConfigureUi::UpdateFirstRowComboBox(); });
|
||||
}
|
||||
|
||||
ConfigureGameList::~ConfigureGameList() = default;
|
||||
ConfigureUi::~ConfigureUi() = default;
|
||||
|
||||
void ConfigureGameList::ApplyConfiguration() {
|
||||
void ConfigureUi::ApplyConfiguration() {
|
||||
UISettings::values.theme =
|
||||
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
|
||||
UISettings::values.show_unknown = ui->show_unknown->isChecked();
|
||||
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
|
||||
UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt();
|
||||
@@ -63,18 +68,19 @@ void ConfigureGameList::ApplyConfiguration() {
|
||||
Settings::Apply();
|
||||
}
|
||||
|
||||
void ConfigureGameList::RequestGameListUpdate() {
|
||||
void ConfigureUi::RequestGameListUpdate() {
|
||||
UISettings::values.is_game_list_reload_pending.exchange(true);
|
||||
}
|
||||
|
||||
void ConfigureGameList::SetConfiguration() {
|
||||
void ConfigureUi::SetConfiguration() {
|
||||
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
|
||||
ui->show_unknown->setChecked(UISettings::values.show_unknown);
|
||||
ui->show_add_ons->setChecked(UISettings::values.show_add_ons);
|
||||
ui->icon_size_combobox->setCurrentIndex(
|
||||
ui->icon_size_combobox->findData(UISettings::values.icon_size));
|
||||
}
|
||||
|
||||
void ConfigureGameList::changeEvent(QEvent* event) {
|
||||
void ConfigureUi::changeEvent(QEvent* event) {
|
||||
if (event->type() == QEvent::LanguageChange) {
|
||||
RetranslateUI();
|
||||
}
|
||||
@@ -82,7 +88,7 @@ void ConfigureGameList::changeEvent(QEvent* event) {
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void ConfigureGameList::RetranslateUI() {
|
||||
void ConfigureUi::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
|
||||
for (int i = 0; i < ui->icon_size_combobox->count(); i++) {
|
||||
@@ -97,18 +103,18 @@ void ConfigureGameList::RetranslateUI() {
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGameList::InitializeIconSizeComboBox() {
|
||||
void ConfigureUi::InitializeIconSizeComboBox() {
|
||||
for (const auto& size : default_icon_sizes) {
|
||||
ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureGameList::InitializeRowComboBoxes() {
|
||||
void ConfigureUi::InitializeRowComboBoxes() {
|
||||
UpdateFirstRowComboBox(true);
|
||||
UpdateSecondRowComboBox(true);
|
||||
}
|
||||
|
||||
void ConfigureGameList::UpdateFirstRowComboBox(bool init) {
|
||||
void ConfigureUi::UpdateFirstRowComboBox(bool init) {
|
||||
const int currentIndex =
|
||||
init ? UISettings::values.row_1_text_id
|
||||
: ui->row_1_text_combobox->findData(ui->row_1_text_combobox->currentData());
|
||||
@@ -127,7 +133,7 @@ void ConfigureGameList::UpdateFirstRowComboBox(bool init) {
|
||||
ui->row_1_text_combobox->findData(ui->row_2_text_combobox->currentData()));
|
||||
}
|
||||
|
||||
void ConfigureGameList::UpdateSecondRowComboBox(bool init) {
|
||||
void ConfigureUi::UpdateSecondRowComboBox(bool init) {
|
||||
const int currentIndex =
|
||||
init ? UISettings::values.row_2_text_id
|
||||
: ui->row_2_text_combobox->findData(ui->row_2_text_combobox->currentData());
|
||||
@@ -8,15 +8,15 @@
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class ConfigureGameList;
|
||||
class ConfigureUi;
|
||||
}
|
||||
|
||||
class ConfigureGameList : public QWidget {
|
||||
class ConfigureUi : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigureGameList(QWidget* parent = nullptr);
|
||||
~ConfigureGameList() override;
|
||||
explicit ConfigureUi(QWidget* parent = nullptr);
|
||||
~ConfigureUi() override;
|
||||
|
||||
void ApplyConfiguration();
|
||||
|
||||
@@ -34,5 +34,5 @@ private:
|
||||
void UpdateFirstRowComboBox(bool init = false);
|
||||
void UpdateSecondRowComboBox(bool init = false);
|
||||
|
||||
std::unique_ptr<Ui::ConfigureGameList> ui;
|
||||
std::unique_ptr<Ui::ConfigureUi> ui;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureGameList</class>
|
||||
<widget class="QWidget" name="ConfigureGameList">
|
||||
<class>ConfigureUi</class>
|
||||
<widget class="QWidget" name="ConfigureUi">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@@ -21,7 +21,34 @@
|
||||
<property name="title">
|
||||
<string>General</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="theme_label">
|
||||
<property name="text">
|
||||
<string>Theme:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="theme_combobox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="GameListGroupBox">
|
||||
<property name="title">
|
||||
<string>Game List</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="GameListHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
|
||||
<item>
|
||||
@@ -38,19 +65,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="IconSizeGroupBox">
|
||||
<property name="title">
|
||||
<string>Icon Size</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="icon_size_qhbox_layout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="icon_size_qvbox_layout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="icon_size_qhbox_layout_2">
|
||||
<item>
|
||||
@@ -65,19 +79,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="RowGroupBox">
|
||||
<property name="title">
|
||||
<string>Row Text</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="RowHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="RowVerticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="row_1_qhbox_layout">
|
||||
<item>
|
||||
@@ -7,10 +7,10 @@
|
||||
namespace UISettings {
|
||||
|
||||
const Themes themes{{
|
||||
{"Default", "default"},
|
||||
{"Light", "default"},
|
||||
{"Light Colorful", "colorful"},
|
||||
{"Dark", "qdarkstyle"},
|
||||
{"Colorful", "colorful"},
|
||||
{"Colorful Dark", "colorful_dark"},
|
||||
{"Dark Colorful", "colorful_dark"},
|
||||
}};
|
||||
|
||||
Values values = {};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/settings.h"
|
||||
#include "input_common/main.h"
|
||||
#include "input_common/udp/client.h"
|
||||
#include "yuzu_cmd/config.h"
|
||||
#include "yuzu_cmd/default_ini.h"
|
||||
|
||||
@@ -297,6 +298,10 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
|
||||
Settings::values.touchscreen.diameter_y =
|
||||
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
|
||||
Settings::values.udp_input_address =
|
||||
sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
|
||||
Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
|
||||
"Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
|
||||
|
||||
std::transform(keyboard_keys.begin(), keyboard_keys.end(),
|
||||
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
|
||||
|
||||
@@ -69,12 +69,29 @@ rstick=
|
||||
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
|
||||
# - "update_period": update period in milliseconds (default to 100)
|
||||
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
|
||||
# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
|
||||
motion_device=
|
||||
|
||||
# for touch input, the following devices are available:
|
||||
# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
|
||||
# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
|
||||
# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
|
||||
touch_device=
|
||||
|
||||
# Most desktop operating systems do not expose a way to poll the motion state of the controllers
|
||||
# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
|
||||
# from a controller device to the client program. Citra has a client that can connect and read
|
||||
# from any cemuhook compatible motion program.
|
||||
|
||||
# IPv4 address of the udp input server (Default "127.0.0.1")
|
||||
udp_input_address=
|
||||
|
||||
# Port of the udp input server. (Default 26760)
|
||||
udp_input_port=
|
||||
|
||||
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
|
||||
udp_pad_index=
|
||||
|
||||
[Core]
|
||||
# Whether to use multi-core for CPU emulation
|
||||
# 0 (default): Disabled, 1: Enabled
|
||||
|
||||
Reference in New Issue
Block a user