Compare commits

...

37 Commits

Author SHA1 Message Date
Valeri
84786dde00 hle: remove no-op code
Found by static analysis with PVS-Studio. Nobody seems to really know what was it doing there.
2022-01-17 13:51:12 +03:00
bunnei
ca2d904770 Merge pull request #7719 from gidoly/patch-6
Change default theme to Dark colorful
2022-01-17 01:04:47 -08:00
Morph
78e233c460 uisettings: Add enumeration type for themes
Eliminates the usage of a magic number to indicate the default index of the themes array,
2022-01-17 02:46:30 -05:00
gidoly
789af19c60 config: Change default theme to Dark Colorful 2022-01-17 02:41:53 -05:00
bunnei
480b03b645 Merge pull request #7713 from gidoly/patch-3
Change default name for playstation controllers
2022-01-15 02:39:58 -08:00
bunnei
5eda606952 Merge pull request #7711 from bunnei/fix-service-thread-race-v2
hle: kernel: Fix service_threads access to be thread safe V2.
2022-01-14 22:22:39 -08:00
gidoly
7978ea4e8c Change default name for ps controllers
Minor nitpick
Code is from narr
2022-01-15 10:29:42 +09:00
bunnei
4064e03568 Merge pull request #7707 from german77/slow-update
service/hid: Decrease motion update rate
2022-01-14 17:13:30 -08:00
bunnei
cc112f971e hle: kernel: Fix service_threads access to be thread safe V2.
- PR #7699 attempted to fix CreateServiceThread and ReleaseServiceThread to be thread safe, but inadvertently introduced a possible dead-lock.
- With this PR, we use a worker thread to manage the service thread list, allowing it only to be accessed by a single thread, and guaranteeing threads will not destroy themselves.
- Fixes a rare crash in Pokemon Sword/Shield, I've now run this game for ~12 hours non-stop and am quite confident this is a good solution for this issue.
2022-01-14 16:02:57 -08:00
Mai M
b2d45a4072 Merge pull request #7699 from bunnei/fix-service-thread-race
hle: kernel: Fix service_threads access to be thread safe.
2022-01-14 00:46:16 -05:00
Mai M
07e477f891 Merge pull request #7698 from bunnei/mem-code-memory-updates
Kernel Memory Updates (Part 2): SetProcessMemoryPermission, update permissions, and other minor changes.
2022-01-14 00:41:17 -05:00
bunnei
2147240e47 hle: kernel: Fix service_threads access to be thread safe.
- CreateServiceThread and ReleaseServiceThread can be accessed by different threads, uses a lock to make this thread safe.
- Fixes a rare crash in Pokemon Sword/Shield that can occur when a new service thread is being created while an old one is being destroyed.
2022-01-13 21:26:10 -08:00
bunnei
f1aa7ff893 Merge pull request #7690 from Morph1984/increase-file-limit-win
yuzu: main: Increase the open file limit on Windows to 8192
2022-01-13 21:25:06 -08:00
bunnei
b3bcf0fa88 Merge pull request #7700 from german77/no-gyro
core/hid: Reduce gyro threshold even more
2022-01-13 21:24:41 -08:00
Narr the Reg
8185509683 service/hid: Decrease motion update rate
Motion stops working in Mario Tennis in swing mode if the update rate is too fast even when HW it updates at the same speed. 10ms it's the minimum period that the game needs to start working again.
2022-01-12 22:55:33 -06:00
Narr the Reg
d348070226 core/hid: Reduce gyro threshold even more 2022-01-11 23:15:39 -06:00
bunnei
eb7197eb47 Merge pull request #7697 from abouvier/opt-tests
cmake: make tests optional
2022-01-11 20:58:16 -08:00
bunnei
49a0e4330e hle: kernel: k_page_table: Update SetProcessMemoryPermission. 2022-01-11 16:28:11 -08:00
bunnei
6ac44f3bdc hle: service: ldr: UnmapCodeMemory BSS only when set. 2022-01-11 16:28:11 -08:00
bunnei
6123b6ea45 hle: kernel: k_page_table: ReadAndWrite -> UserReadWrite. 2022-01-11 16:28:11 -08:00
bunnei
081669c334 hle: kernel: k_page_table: Rename *ProcessCodeMemory -> *CodeMemory. 2022-01-11 16:28:11 -08:00
bunnei
599c0763e5 Merge pull request #7684 from bunnei/set-mem-perm-attr
Kernel Memory Updates (Part 1): SetMemoryAttribute, and other minor changes.
2022-01-11 16:26:17 -08:00
Alexandre Bouvier
c8b358dba2 cmake: make tests optional 2022-01-12 00:36:20 +01:00
bunnei
c65c651b6f Merge pull request #7633 from german77/hotkeys
yuzu: Add controller hotkeys
2022-01-11 10:49:23 -08:00
Morph
36da4d1121 yuzu: main: Increase the open file limit on Windows to 8192
This is a temporary solution for now to accommodate for mods containing more than 4096 files.
2022-01-09 21:33:58 -05:00
Morph
b3308830b2 Merge pull request #7683 from liushuyu/fmt-8.1
logging: adapt to changes in fmt 8.1
2022-01-09 18:29:59 -08:00
Morph
18adea343e Merge pull request #7687 from german77/tas_handle
input_common: Handle errors on TAS scripts
2022-01-09 16:43:06 -08:00
liushuyu
09f4f3f23b logging/log.h: move enum class formatter to a separate file ...
... to common/logging/formatter.h
2022-01-09 17:35:33 -07:00
liushuyu
a1054a093c cmake: upgrade Conan package fmt to 8.1.1 ...
... requirements for fmt stays at ^8.0.1
2022-01-08 16:03:18 -07:00
liushuyu
099dd0c0d2 logging/log: use underlying_type instead of hardcoding types 2022-01-08 16:02:49 -07:00
bunnei
acbfb0083a Merge pull request #7682 from german77/udp_fix
input_common: Fix UDP controller mappings
2022-01-08 13:41:39 -08:00
german77
ea089c012e input_common: Handle errors on TAS scripts 2022-01-08 12:27:16 -06:00
liushuyu
42f653ab6f logging: adapt to changes in fmt 8.1 2022-01-08 01:49:26 -07:00
german77
873d26b335 yuzu: Use pad parameter to choose the correct controller 2022-01-07 16:56:36 -06:00
german77
d05675242a input_common: Fix udp motion not automapping to both sides 2022-01-07 16:56:36 -06:00
german77
72c8a94a6c yuzu: Add controller hotkeys 2022-01-06 21:26:05 -06:00
german77
b94e947793 core/hid: Add home and screenshot button support 2022-01-06 21:11:27 -06:00
35 changed files with 881 additions and 211 deletions

View File

@@ -35,6 +35,8 @@ option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON)
option(YUZU_TESTS "Compile tests" ON)
# Default to a Release build
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
@@ -168,8 +170,7 @@ macro(yuzu_find_packages)
# Capitalization matters here. We need the naming to match the generated paths from Conan
set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg
"Catch2 2.13.7 catch2/2.13.7"
"fmt 8.0.1 fmt/8.0.1"
"fmt 8.0.1 fmt/8.1.1"
"lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11"
@@ -177,6 +178,11 @@ macro(yuzu_find_packages)
# can't use opus until AVX check is fixed: https://github.com/yuzu-emu/yuzu/pull/4068
#"opus 1.3 opus/1.3.1"
)
if (YUZU_TESTS)
list(APPEND REQUIRED_LIBS
"Catch2 2.13.7 catch2/2.13.7"
)
endif()
foreach(PACKAGE ${REQUIRED_LIBS})
string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE})

View File

@@ -13,10 +13,6 @@ if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
endif()
# Catch
add_library(catch-single-include INTERFACE)
target_include_directories(catch-single-include INTERFACE catch/single_include)
# Dynarmic
if (ARCHITECTURE_x86_64)
set(DYNARMIC_TESTS OFF)

View File

@@ -151,7 +151,10 @@ add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(input_common)
add_subdirectory(shader_recompiler)
add_subdirectory(tests)
if (YUZU_TESTS)
add_subdirectory(tests)
endif()
if (ENABLE_SDL2)
add_subdirectory(yuzu_cmd)

View File

@@ -85,6 +85,7 @@ add_library(common STATIC
logging/backend.h
logging/filter.cpp
logging/filter.h
logging/formatter.h
logging/log.h
logging/log_entry.h
logging/text_formatter.cpp

View File

@@ -0,0 +1,23 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <type_traits>
#include <fmt/format.h>
// adapted from https://github.com/fmtlib/fmt/issues/2704
// a generic formatter for enum classes
#if FMT_VERSION >= 80100
template <typename T>
struct fmt::formatter<T, std::enable_if_t<std::is_enum_v<T>, char>>
: formatter<std::underlying_type_t<T>> {
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
return fmt::formatter<std::underlying_type_t<T>>::format(
static_cast<std::underlying_type_t<T>>(value), ctx);
}
};
#endif

View File

@@ -7,8 +7,9 @@
#include <algorithm>
#include <string_view>
#include <fmt/core.h>
#include <fmt/format.h>
#include "common/logging/formatter.h"
#include "common/logging/types.h"
namespace Common::Log {

View File

@@ -351,6 +351,19 @@ void EmulatedController::DisableConfiguration() {
}
}
void EmulatedController::EnableSystemButtons() {
system_buttons_enabled = true;
}
void EmulatedController::DisableSystemButtons() {
system_buttons_enabled = false;
}
void EmulatedController::ResetSystemButtons() {
controller.home_button_state.home.Assign(false);
controller.capture_button_state.capture.Assign(false);
}
bool EmulatedController::IsConfiguring() const {
return is_configuring;
}
@@ -389,7 +402,8 @@ std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
return param.Get("engine", "") == param_.Get("engine", "") &&
param.Get("guid", "") == param_.Get("guid", "") &&
param.Get("port", 0) == param_.Get("port", 0);
param.Get("port", 0) == param_.Get("port", 0) &&
param.Get("pad", 0) == param_.Get("pad", 0);
});
if (devices_it != devices.end()) {
continue;
@@ -398,6 +412,7 @@ std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
device.Set("engine", param.Get("engine", ""));
device.Set("guid", param.Get("guid", ""));
device.Set("port", param.Get("port", 0));
device.Set("pad", param.Get("pad", 0));
devices.push_back(device);
}
@@ -412,7 +427,8 @@ std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
devices.begin(), devices.end(), [param](const Common::ParamPackage param_) {
return param.Get("engine", "") == param_.Get("engine", "") &&
param.Get("guid", "") == param_.Get("guid", "") &&
param.Get("port", 0) == param_.Get("port", 0);
param.Get("port", 0) == param_.Get("port", 0) &&
param.Get("pad", 0) == param_.Get("pad", 0);
});
if (devices_it != devices.end()) {
continue;
@@ -421,6 +437,7 @@ std::vector<Common::ParamPackage> EmulatedController::GetMappedDevices(
device.Set("engine", param.Get("engine", ""));
device.Set("guid", param.Get("guid", ""));
device.Set("port", param.Get("port", 0));
device.Set("pad", param.Get("pad", 0));
devices.push_back(device);
}
return devices;
@@ -596,7 +613,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
controller.npad_button_state.right_sr.Assign(current_status.value);
break;
case Settings::NativeButton::Home:
if (!system_buttons_enabled) {
break;
}
controller.home_button_state.home.Assign(current_status.value);
break;
case Settings::NativeButton::Screenshot:
if (!system_buttons_enabled) {
break;
}
controller.capture_button_state.capture.Assign(current_status.value);
break;
}
}
@@ -1077,6 +1103,20 @@ BatteryValues EmulatedController::GetBatteryValues() const {
return controller.battery_values;
}
HomeButtonState EmulatedController::GetHomeButtons() const {
if (is_configuring) {
return {};
}
return controller.home_button_state;
}
CaptureButtonState EmulatedController::GetCaptureButtons() const {
if (is_configuring) {
return {};
}
return controller.capture_button_state;
}
NpadButtonState EmulatedController::GetNpadButtons() const {
if (is_configuring) {
return {};

View File

@@ -101,6 +101,8 @@ struct ControllerStatus {
VibrationValues vibration_values{};
// Data for HID serices
HomeButtonState home_button_state{};
CaptureButtonState capture_button_state{};
NpadButtonState npad_button_state{};
DebugPadButton debug_pad_button_state{};
AnalogSticks analog_stick_state{};
@@ -198,6 +200,15 @@ public:
/// Returns the emulated controller into normal mode, allowing the modification of the HID state
void DisableConfiguration();
/// Enables Home and Screenshot buttons
void EnableSystemButtons();
/// Disables Home and Screenshot buttons
void DisableSystemButtons();
/// Sets Home and Screenshot buttons to false
void ResetSystemButtons();
/// Returns true if the emulated controller is in configuring mode
bool IsConfiguring() const;
@@ -261,7 +272,13 @@ public:
/// Returns the latest battery status from the controller with parameters
BatteryValues GetBatteryValues() const;
/// Returns the latest status of button input for the npad service
/// Returns the latest status of button input for the hid::HomeButton service
HomeButtonState GetHomeButtons() const;
/// Returns the latest status of button input for the hid::CaptureButton service
CaptureButtonState GetCaptureButtons() const;
/// Returns the latest status of button input for the hid::Npad service
NpadButtonState GetNpadButtons() const;
/// Returns the latest status of button input for the debug pad service
@@ -383,6 +400,7 @@ private:
NpadStyleTag supported_style_tag{NpadStyleSet::All};
bool is_connected{false};
bool is_configuring{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{0.01f};
bool force_update_motion{false};

View File

@@ -378,6 +378,26 @@ struct LedPattern {
};
};
struct HomeButtonState {
union {
u64 raw{};
// Buttons
BitField<0, 1, u64> home;
};
};
static_assert(sizeof(HomeButtonState) == 0x8, "HomeButtonState has incorrect size.");
struct CaptureButtonState {
union {
u64 raw{};
// Buttons
BitField<0, 1, u64> capture;
};
};
static_assert(sizeof(CaptureButtonState) == 0x8, "CaptureButtonState has incorrect size.");
struct NpadButtonState {
union {
NpadButton raw{};

View File

@@ -10,7 +10,7 @@ namespace Core::HID {
MotionInput::MotionInput() {
// Initialize PID constants with default values
SetPID(0.3f, 0.005f, 0.0f);
SetGyroThreshold(0.001f);
SetGyroThreshold(0.00005f);
}
void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) {

View File

@@ -298,7 +298,7 @@ ResultCode KPageTable::MapProcessCode(VAddr addr, std::size_t num_pages, KMemory
return ResultSuccess;
}
ResultCode KPageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
ResultCode KPageTable::MapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
std::lock_guard lock{page_table_lock};
const std::size_t num_pages{size / PageSize};
@@ -307,7 +307,7 @@ ResultCode KPageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std:
KMemoryPermission perm{};
CASCADE_CODE(CheckMemoryState(&state, &perm, nullptr, nullptr, src_addr, size,
KMemoryState::All, KMemoryState::Normal, KMemoryPermission::All,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask,
KMemoryPermission::UserReadWrite, KMemoryAttribute::Mask,
KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
if (IsRegionMapped(dst_addr, size)) {
@@ -335,7 +335,7 @@ ResultCode KPageTable::MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std:
return ResultSuccess;
}
ResultCode KPageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
ResultCode KPageTable::UnmapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size) {
std::lock_guard lock{page_table_lock};
if (!size) {
@@ -361,7 +361,7 @@ ResultCode KPageTable::UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, st
block_manager->Update(dst_addr, num_pages, KMemoryState::Free);
block_manager->Update(src_addr, num_pages, KMemoryState::Normal,
KMemoryPermission::ReadAndWrite);
KMemoryPermission::UserReadWrite);
system.InvalidateCpuInstructionCacheRange(dst_addr, size);
@@ -416,7 +416,7 @@ void KPageTable::MapPhysicalMemory(KPageLinkedList& page_linked_list, VAddr star
}
const std::size_t num_pages{std::min(src_num_pages, dst_num_pages)};
Operate(dst_addr, num_pages, KMemoryPermission::ReadAndWrite, OperationType::Map,
Operate(dst_addr, num_pages, KMemoryPermission::UserReadWrite, OperationType::Map,
map_addr);
dst_addr += num_pages * PageSize;
@@ -470,7 +470,7 @@ ResultCode KPageTable::MapPhysicalMemory(VAddr addr, std::size_t size) {
const std::size_t num_pages{size / PageSize};
block_manager->Update(addr, num_pages, KMemoryState::Free, KMemoryPermission::None,
KMemoryAttribute::None, KMemoryState::Normal,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::None);
KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
return ResultSuccess;
}
@@ -554,7 +554,7 @@ ResultCode KPageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
KMemoryState src_state{};
CASCADE_CODE(CheckMemoryState(
&src_state, nullptr, nullptr, nullptr, src_addr, size, KMemoryState::FlagCanAlias,
KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::ReadAndWrite,
KMemoryState::FlagCanAlias, KMemoryPermission::All, KMemoryPermission::UserReadWrite,
KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
if (IsRegionMapped(dst_addr, size)) {
@@ -568,13 +568,13 @@ ResultCode KPageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
{
auto block_guard = detail::ScopeExit([&] {
Operate(src_addr, num_pages, KMemoryPermission::ReadAndWrite,
Operate(src_addr, num_pages, KMemoryPermission::UserReadWrite,
OperationType::ChangePermissions);
});
CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::None,
OperationType::ChangePermissions));
CASCADE_CODE(MapPages(dst_addr, page_linked_list, KMemoryPermission::ReadAndWrite));
CASCADE_CODE(MapPages(dst_addr, page_linked_list, KMemoryPermission::UserReadWrite));
block_guard.Cancel();
}
@@ -582,7 +582,7 @@ ResultCode KPageTable::Map(VAddr dst_addr, VAddr src_addr, std::size_t size) {
block_manager->Update(src_addr, num_pages, src_state, KMemoryPermission::None,
KMemoryAttribute::Locked);
block_manager->Update(dst_addr, num_pages, KMemoryState::Stack,
KMemoryPermission::ReadAndWrite);
KMemoryPermission::UserReadWrite);
return ResultSuccess;
}
@@ -617,13 +617,13 @@ ResultCode KPageTable::Unmap(VAddr dst_addr, VAddr src_addr, std::size_t size) {
auto block_guard = detail::ScopeExit([&] { MapPages(dst_addr, dst_pages, dst_perm); });
CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap));
CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::ReadAndWrite,
CASCADE_CODE(Operate(src_addr, num_pages, KMemoryPermission::UserReadWrite,
OperationType::ChangePermissions));
block_guard.Cancel();
}
block_manager->Update(src_addr, num_pages, src_state, KMemoryPermission::ReadAndWrite);
block_manager->Update(src_addr, num_pages, src_state, KMemoryPermission::UserReadWrite);
block_manager->Update(dst_addr, num_pages, KMemoryState::Free);
return ResultSuccess;
@@ -713,50 +713,61 @@ ResultCode KPageTable::UnmapPages(VAddr addr, KPageLinkedList& page_linked_list,
}
ResultCode KPageTable::SetProcessMemoryPermission(VAddr addr, std::size_t size,
KMemoryPermission perm) {
Svc::MemoryPermission svc_perm) {
const size_t num_pages = size / PageSize;
// Lock the table.
std::lock_guard lock{page_table_lock};
KMemoryState prev_state{};
KMemoryPermission prev_perm{};
// Verify we can change the memory permission.
KMemoryState old_state;
KMemoryPermission old_perm;
size_t num_allocator_blocks;
R_TRY(this->CheckMemoryState(std::addressof(old_state), std::addressof(old_perm), nullptr,
std::addressof(num_allocator_blocks), addr, size,
KMemoryState::FlagCode, KMemoryState::FlagCode,
KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::All, KMemoryAttribute::None));
CASCADE_CODE(CheckMemoryState(
&prev_state, &prev_perm, nullptr, nullptr, addr, size, KMemoryState::FlagCode,
KMemoryState::FlagCode, KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::Mask, KMemoryAttribute::None, KMemoryAttribute::IpcAndDeviceMapped));
// Determine new perm/state.
const KMemoryPermission new_perm = ConvertToKMemoryPermission(svc_perm);
KMemoryState new_state = old_state;
const bool is_w = (new_perm & KMemoryPermission::UserWrite) == KMemoryPermission::UserWrite;
const bool is_x = (new_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
const bool was_x =
(old_perm & KMemoryPermission::UserExecute) == KMemoryPermission::UserExecute;
ASSERT(!(is_w && is_x));
KMemoryState state{prev_state};
// Ensure state is mutable if permission allows write
if ((perm & KMemoryPermission::Write) != KMemoryPermission::None) {
if (prev_state == KMemoryState::Code) {
state = KMemoryState::CodeData;
} else if (prev_state == KMemoryState::AliasCode) {
state = KMemoryState::AliasCodeData;
} else {
if (is_w) {
switch (old_state) {
case KMemoryState::Code:
new_state = KMemoryState::CodeData;
break;
case KMemoryState::AliasCode:
new_state = KMemoryState::AliasCodeData;
break;
default:
UNREACHABLE();
}
}
// Return early if there is nothing to change
if (state == prev_state && perm == prev_perm) {
return ResultSuccess;
}
// Succeed if there's nothing to do.
R_SUCCEED_IF(old_perm == new_perm && old_state == new_state);
if ((prev_perm & KMemoryPermission::Execute) != (perm & KMemoryPermission::Execute)) {
// Perform mapping operation.
const auto operation =
was_x ? OperationType::ChangePermissionsAndRefresh : OperationType::ChangePermissions;
R_TRY(Operate(addr, num_pages, new_perm, operation));
// Update the blocks.
block_manager->Update(addr, num_pages, new_state, new_perm, KMemoryAttribute::None);
// Ensure cache coherency, if we're setting pages as executable.
if (is_x) {
// Memory execution state is changing, invalidate CPU cache range
system.InvalidateCpuInstructionCacheRange(addr, size);
}
const std::size_t num_pages{size / PageSize};
const OperationType operation{(perm & KMemoryPermission::Execute) != KMemoryPermission::None
? OperationType::ChangePermissionsAndRefresh
: OperationType::ChangePermissions};
CASCADE_CODE(Operate(addr, num_pages, perm, operation));
block_manager->Update(addr, num_pages, state, perm);
return ResultSuccess;
}
@@ -785,7 +796,7 @@ ResultCode KPageTable::ReserveTransferMemory(VAddr addr, std::size_t size, KMemo
&state, nullptr, &attribute, nullptr, addr, size,
KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted,
KMemoryState::FlagCanTransfer | KMemoryState::FlagReferenceCounted, KMemoryPermission::All,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::Mask, KMemoryAttribute::None,
KMemoryPermission::UserReadWrite, KMemoryAttribute::Mask, KMemoryAttribute::None,
KMemoryAttribute::IpcAndDeviceMapped));
block_manager->Update(addr, size / PageSize, state, perm, attribute | KMemoryAttribute::Locked);
@@ -805,7 +816,7 @@ ResultCode KPageTable::ResetTransferMemory(VAddr addr, std::size_t size) {
KMemoryPermission::None, KMemoryPermission::None, KMemoryAttribute::Mask,
KMemoryAttribute::Locked, KMemoryAttribute::IpcAndDeviceMapped));
block_manager->Update(addr, size / PageSize, state, KMemoryPermission::ReadAndWrite);
block_manager->Update(addr, size / PageSize, state, KMemoryPermission::UserReadWrite);
return ResultSuccess;
}
@@ -906,7 +917,7 @@ ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks),
heap_region_start + size, GetHeapSize() - size,
KMemoryState::All, KMemoryState::Normal,
KMemoryPermission::All, KMemoryPermission::ReadAndWrite,
KMemoryPermission::All, KMemoryPermission::UserReadWrite,
KMemoryAttribute::All, KMemoryAttribute::None));
// Unmap the end of the heap.
@@ -981,7 +992,7 @@ ResultCode KPageTable::SetHeapSize(VAddr* out, std::size_t size) {
// Apply the memory block update.
block_manager->Update(current_heap_end, num_pages, KMemoryState::Normal,
KMemoryPermission::ReadAndWrite, KMemoryAttribute::None);
KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
// Update the current heap end.
current_heap_end = heap_region_start + size;

View File

@@ -31,8 +31,8 @@ public:
KMemoryManager::Pool pool);
ResultCode MapProcessCode(VAddr addr, std::size_t pages_count, KMemoryState state,
KMemoryPermission perm);
ResultCode MapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
ResultCode UnmapProcessCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
ResultCode MapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
ResultCode UnmapCodeMemory(VAddr dst_addr, VAddr src_addr, std::size_t size);
ResultCode UnmapProcessMemory(VAddr dst_addr, std::size_t size, KPageTable& src_page_table,
VAddr src_addr);
ResultCode MapPhysicalMemory(VAddr addr, std::size_t size);
@@ -43,7 +43,8 @@ public:
ResultCode MapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state,
KMemoryPermission perm);
ResultCode UnmapPages(VAddr addr, KPageLinkedList& page_linked_list, KMemoryState state);
ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size, KMemoryPermission perm);
ResultCode SetProcessMemoryPermission(VAddr addr, std::size_t size,
Svc::MemoryPermission svc_perm);
KMemoryInfo QueryInfo(VAddr addr);
ResultCode ReserveTransferMemory(VAddr addr, std::size_t size, KMemoryPermission perm);
ResultCode ResetTransferMemory(VAddr addr, std::size_t size);

View File

@@ -509,7 +509,7 @@ VAddr KProcess::CreateTLSRegion() {
const VAddr tls_page_addr{page_table
->AllocateAndMapMemory(1, PageSize, true, start, size / PageSize,
KMemoryState::ThreadLocal,
KMemoryPermission::ReadAndWrite,
KMemoryPermission::UserReadWrite,
tls_map_addr)
.ValueOr(0)};
@@ -541,16 +541,16 @@ void KProcess::FreeTLSRegion(VAddr tls_address) {
void KProcess::LoadModule(CodeSet code_set, VAddr base_addr) {
const auto ReprotectSegment = [&](const CodeSet::Segment& segment,
KMemoryPermission permission) {
Svc::MemoryPermission permission) {
page_table->SetProcessMemoryPermission(segment.addr + base_addr, segment.size, permission);
};
kernel.System().Memory().WriteBlock(*this, base_addr, code_set.memory.data(),
code_set.memory.size());
ReprotectSegment(code_set.CodeSegment(), KMemoryPermission::ReadAndExecute);
ReprotectSegment(code_set.RODataSegment(), KMemoryPermission::Read);
ReprotectSegment(code_set.DataSegment(), KMemoryPermission::ReadAndWrite);
ReprotectSegment(code_set.CodeSegment(), Svc::MemoryPermission::ReadExecute);
ReprotectSegment(code_set.RODataSegment(), Svc::MemoryPermission::Read);
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
}
bool KProcess::IsSignaled() const {
@@ -587,7 +587,7 @@ ResultCode KProcess::AllocateMainThreadStack(std::size_t stack_size) {
CASCADE_RESULT(main_thread_stack_top,
page_table->AllocateAndMapMemory(
main_thread_stack_size / PageSize, PageSize, false, start, size / PageSize,
KMemoryState::Stack, KMemoryPermission::ReadAndWrite));
KMemoryState::Stack, KMemoryPermission::UserReadWrite));
main_thread_stack_top += main_thread_stack_size;

View File

@@ -49,8 +49,6 @@ void KScheduler::RescheduleCores(KernelCore& kernel, u64 cores_pending_reschedul
if (!must_context_switch || core != current_core) {
auto& phys_core = kernel.PhysicalCore(core);
phys_core.Interrupt();
} else {
must_context_switch = true;
}
cores_pending_reschedule &= ~(1ULL << core);
}

View File

@@ -51,7 +51,8 @@ namespace Kernel {
struct KernelCore::Impl {
explicit Impl(Core::System& system_, KernelCore& kernel_)
: time_manager{system_}, object_list_container{kernel_}, system{system_} {}
: time_manager{system_}, object_list_container{kernel_},
service_threads_manager{1, "yuzu:ServiceThreadsManager"}, system{system_} {}
void SetMulticore(bool is_multi) {
is_multicore = is_multi;
@@ -121,7 +122,7 @@ struct KernelCore::Impl {
object_list_container.Finalize();
// Ensures all service threads gracefully shutdown.
service_threads.clear();
ClearServiceThreads();
next_object_id = 0;
next_kernel_process_id = KProcess::InitialKIPIDMin;
@@ -704,6 +705,27 @@ struct KernelCore::Impl {
return port;
}
std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(KernelCore& kernel,
const std::string& name) {
auto service_thread = std::make_shared<Kernel::ServiceThread>(kernel, 1, name);
service_threads_manager.QueueWork(
[this, service_thread]() { service_threads.emplace(service_thread); });
return service_thread;
}
void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
if (auto strong_ptr = service_thread.lock()) {
service_threads_manager.QueueWork(
[this, strong_ptr{std::move(strong_ptr)}]() { service_threads.erase(strong_ptr); });
}
}
void ClearServiceThreads() {
service_threads_manager.QueueWork([this]() { service_threads.clear(); });
}
std::mutex server_ports_lock;
std::mutex server_sessions_lock;
std::mutex registered_objects_lock;
@@ -759,6 +781,7 @@ struct KernelCore::Impl {
// Threads used for services
std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads;
Common::ThreadWorker service_threads_manager;
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
@@ -1099,15 +1122,11 @@ void KernelCore::ExitSVCProfile() {
}
std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name);
impl->service_threads.emplace(service_thread);
return service_thread;
return impl->CreateServiceThread(*this, name);
}
void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
if (auto strong_ptr = service_thread.lock()) {
impl->service_threads.erase(strong_ptr);
}
impl->ReleaseServiceThread(service_thread);
}
Init::KSlabResourceCounts& KernelCore::SlabResourceCounts() {

View File

@@ -16,17 +16,25 @@ namespace Kernel {
PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_,
Core::CPUInterrupts& interrupts_)
: core_index{core_index_}, system{system_}, scheduler{scheduler_},
interrupts{interrupts_}, guard{std::make_unique<Common::SpinLock>()} {}
interrupts{interrupts_}, guard{std::make_unique<Common::SpinLock>()} {
#ifdef ARCHITECTURE_x86_64
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with
// a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
auto& kernel = system.Kernel();
arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
#else
#error Platform not supported yet.
#endif
}
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
#ifdef ARCHITECTURE_x86_64
auto& kernel = system.Kernel();
if (is_64_bit) {
arm_interface = std::make_unique<Core::ARM_Dynarmic_64>(
system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
} else {
if (!is_64_bit) {
// We already initialized a 64-bit core, replace with a 32-bit one.
arm_interface = std::make_unique<Core::ARM_Dynarmic_32>(
system, interrupts, kernel.IsMulticore(), kernel.GetExclusiveMonitor(), core_index);
}

View File

@@ -1309,6 +1309,8 @@ static ResultCode SetProcessMemoryPermission(Core::System& system, Handle proces
R_UNLESS(Common::IsAligned(size, PageSize), ResultInvalidSize);
R_UNLESS(size > 0, ResultInvalidSize);
R_UNLESS((address < address + size), ResultInvalidCurrentMemory);
R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory);
R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory);
// Validate the memory permission.
R_UNLESS(IsValidProcessMemoryPermission(perm), ResultInvalidNewMemoryPermission);
@@ -1323,7 +1325,7 @@ static ResultCode SetProcessMemoryPermission(Core::System& system, Handle proces
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
// Set the memory permission.
return page_table.SetProcessMemoryPermission(address, size, ConvertToKMemoryPermission(perm));
return page_table.SetProcessMemoryPermission(address, size, perm);
}
static ResultCode MapProcessMemory(Core::System& system, VAddr dst_address, Handle process_handle,
@@ -1626,7 +1628,7 @@ static ResultCode MapProcessCodeMemory(Core::System& system, Handle process_hand
return ResultInvalidMemoryRegion;
}
return page_table.MapProcessCodeMemory(dst_address, src_address, size);
return page_table.MapCodeMemory(dst_address, src_address, size);
}
static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_handle,
@@ -1694,7 +1696,7 @@ static ResultCode UnmapProcessCodeMemory(Core::System& system, Handle process_ha
return ResultInvalidMemoryRegion;
}
return page_table.UnmapProcessCodeMemory(dst_address, src_address, size);
return page_table.UnmapCodeMemory(dst_address, src_address, size);
}
/// Exits the current process

View File

@@ -37,7 +37,8 @@ namespace Service::HID {
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
// TODO: Correct update rate for motion is 5ms. Check why some games don't behave at that speed
constexpr auto motion_update_ns = std::chrono::nanoseconds{10 * 1000 * 1000}; // (10ms, 100Hz)
constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
IAppletResource::IAppletResource(Core::System& system_,

View File

@@ -14,6 +14,7 @@
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_system_control.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
#include "core/loader/nro.h"
@@ -325,7 +326,7 @@ public:
for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) {
auto& page_table{process->PageTable()};
const VAddr addr{GetRandomMapRegion(page_table, size)};
const ResultCode result{page_table.MapProcessCodeMemory(addr, baseAddress, size)};
const ResultCode result{page_table.MapCodeMemory(addr, baseAddress, size)};
if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
@@ -351,12 +352,12 @@ public:
if (bss_size) {
auto block_guard = detail::ScopeExit([&] {
page_table.UnmapProcessCodeMemory(addr + nro_size, bss_addr, bss_size);
page_table.UnmapProcessCodeMemory(addr, nro_addr, nro_size);
page_table.UnmapCodeMemory(addr + nro_size, bss_addr, bss_size);
page_table.UnmapCodeMemory(addr, nro_addr, nro_size);
});
const ResultCode result{
page_table.MapProcessCodeMemory(addr + nro_size, bss_addr, bss_size)};
page_table.MapCodeMemory(addr + nro_size, bss_addr, bss_size)};
if (result == Kernel::ResultInvalidCurrentMemory) {
continue;
@@ -397,12 +398,12 @@ public:
nro_header.segment_headers[DATA_INDEX].memory_size);
CASCADE_CODE(process->PageTable().SetProcessMemoryPermission(
text_start, ro_start - text_start, Kernel::KMemoryPermission::ReadAndExecute));
text_start, ro_start - text_start, Kernel::Svc::MemoryPermission::ReadExecute));
CASCADE_CODE(process->PageTable().SetProcessMemoryPermission(
ro_start, data_start - ro_start, Kernel::KMemoryPermission::Read));
ro_start, data_start - ro_start, Kernel::Svc::MemoryPermission::Read));
return process->PageTable().SetProcessMemoryPermission(
data_start, bss_end_addr - data_start, Kernel::KMemoryPermission::ReadAndWrite);
data_start, bss_end_addr - data_start, Kernel::Svc::MemoryPermission::ReadWrite);
}
void LoadModule(Kernel::HLERequestContext& ctx) {
@@ -530,16 +531,19 @@ public:
ResultCode UnmapNro(const NROInfo& info) {
// Each region must be unmapped separately to validate memory state
auto& page_table{system.CurrentProcess()->PageTable()};
CASCADE_CODE(page_table.UnmapProcessCodeMemory(info.nro_address + info.text_size +
info.ro_size + info.data_size,
info.bss_address, info.bss_size));
CASCADE_CODE(page_table.UnmapProcessCodeMemory(
info.nro_address + info.text_size + info.ro_size,
info.src_addr + info.text_size + info.ro_size, info.data_size));
CASCADE_CODE(page_table.UnmapProcessCodeMemory(
info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size));
CASCADE_CODE(
page_table.UnmapProcessCodeMemory(info.nro_address, info.src_addr, info.text_size));
if (info.bss_size != 0) {
CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size +
info.ro_size + info.data_size,
info.bss_address, info.bss_size));
}
CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size + info.ro_size,
info.src_addr + info.text_size + info.ro_size,
info.data_size));
CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address + info.text_size,
info.src_addr + info.text_size, info.ro_size));
CASCADE_CODE(page_table.UnmapCodeMemory(info.nro_address, info.src_addr, info.text_size));
return ResultSuccess;
}

View File

@@ -201,6 +201,12 @@ public:
return "XBox 360 Controller";
case SDL_CONTROLLER_TYPE_XBOXONE:
return "XBox One Controller";
case SDL_CONTROLLER_TYPE_PS3:
return "DualShock 3 Controller";
case SDL_CONTROLLER_TYPE_PS4:
return "DualShock 4 Controller";
case SDL_CONTROLLER_TYPE_PS5:
return "DualSense Controller";
default:
break;
}
@@ -663,6 +669,7 @@ ButtonBindings SDLDriver::GetDefaultButtonBinding() const {
{Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER},
{Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
};
}
@@ -699,6 +706,7 @@ ButtonBindings SDLDriver::GetNintendoButtonBinding(
{Settings::NativeButton::SL, sl_button},
{Settings::NativeButton::SR, sr_button},
{Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE},
{Settings::NativeButton::Screenshot, SDL_CONTROLLER_BUTTON_MISC1},
};
}

View File

@@ -24,7 +24,7 @@ namespace InputCommon {
class SDLJoystick;
using ButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>;
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 18>;
using ZButtonBindings =
std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>;

View File

@@ -23,7 +23,7 @@ enum class Tas::TasAxis : u8 {
};
// Supported keywords and buttons from a TAS file
constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
constexpr std::array<std::pair<std::string_view, TasButton>, 18> text_to_tas_button = {
std::pair{"KEY_A", TasButton::BUTTON_A},
{"KEY_B", TasButton::BUTTON_B},
{"KEY_X", TasButton::BUTTON_X},
@@ -40,8 +40,9 @@ constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_but
{"KEY_DDOWN", TasButton::BUTTON_DOWN},
{"KEY_SL", TasButton::BUTTON_SL},
{"KEY_SR", TasButton::BUTTON_SR},
{"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
{"KEY_HOME", TasButton::BUTTON_HOME},
// These buttons are disabled to avoid TAS input from activating hotkeys
// {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
// {"KEY_HOME", TasButton::BUTTON_HOME},
{"KEY_ZL", TasButton::TRIGGER_ZL},
{"KEY_ZR", TasButton::TRIGGER_ZR},
};
@@ -105,10 +106,16 @@ void Tas::LoadTasFile(size_t player_index, size_t file_index) {
continue;
}
const auto num_frames = std::stoi(seg_list[0]);
while (frame_no < num_frames) {
commands[player_index].emplace_back();
frame_no++;
try {
const auto num_frames = std::stoi(seg_list[0]);
while (frame_no < num_frames) {
commands[player_index].emplace_back();
frame_no++;
}
} catch (const std::invalid_argument&) {
LOG_ERROR(Input, "Invalid argument: '{}' at command {}", seg_list[0], frame_no);
} catch (const std::out_of_range&) {
LOG_ERROR(Input, "Out of range: '{}' at command {}", seg_list[0], frame_no);
}
TASCommand command = {
@@ -233,10 +240,21 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
}
}
const float x = std::stof(seg_list.at(0)) / 32767.0f;
const float y = std::stof(seg_list.at(1)) / 32767.0f;
if (seg_list.size() < 2) {
LOG_ERROR(Input, "Invalid axis data: '{}'", line);
return {};
}
return {x, y};
try {
const float x = std::stof(seg_list.at(0)) / 32767.0f;
const float y = std::stof(seg_list.at(1)) / 32767.0f;
return {x, y};
} catch (const std::invalid_argument&) {
LOG_ERROR(Input, "Invalid argument: '{}'", line);
} catch (const std::out_of_range&) {
LOG_ERROR(Input, "Out of range: '{}'", line);
}
return {};
}
u64 Tas::ReadCommandButtons(const std::string& line) const {

View File

@@ -442,14 +442,22 @@ MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& p
}
MotionMapping mapping = {};
Common::ParamPackage motion_params;
motion_params.Set("engine", GetEngineName());
motion_params.Set("guid", params.Get("guid", ""));
motion_params.Set("port", params.Get("port", 0));
motion_params.Set("pad", params.Get("pad", 0));
motion_params.Set("motion", 0);
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params));
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(motion_params));
Common::ParamPackage left_motion_params;
left_motion_params.Set("engine", GetEngineName());
left_motion_params.Set("guid", params.Get("guid", ""));
left_motion_params.Set("port", params.Get("port", 0));
left_motion_params.Set("pad", params.Get("pad", 0));
left_motion_params.Set("motion", 0);
Common::ParamPackage right_motion_params;
right_motion_params.Set("engine", GetEngineName());
right_motion_params.Set("guid", params.Get("guid", ""));
right_motion_params.Set("port", params.Get("port", 0));
right_motion_params.Set("pad", params.Get("pad", 0));
right_motion_params.Set("motion", 0);
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_motion_params));
return mapping;
}

View File

@@ -9,7 +9,7 @@
#include <string_view>
#include <utility>
#include <fmt/format.h>
#include "common/logging/formatter.h"
namespace Shader {

View File

@@ -16,6 +16,6 @@ add_executable(tests
create_target_directory_groups(tests)
target_link_libraries(tests PRIVATE common core input_common)
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} Catch2::Catch2 Threads::Threads)
add_test(NAME tests COMMAND tests)

View File

@@ -66,27 +66,27 @@ const std::array<int, 2> Config::default_stick_mod = {
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
{QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), Qt::WindowShortcut}},
{QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), QStringLiteral("Screenshot"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), QStringLiteral("Home+X"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), QStringLiteral("Home+Plus"), Qt::WindowShortcut}},
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Exit Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("Esc"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), QStringLiteral("Home+Minus"), Qt::WindowShortcut}},
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut}},
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), QStringLiteral("Home+Y"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), QStringLiteral(""), Qt::WindowShortcut}},
}};
// clang-format on
@@ -679,7 +679,6 @@ void Config::ReadShortcutValues() {
qt_config->beginGroup(QStringLiteral("Shortcuts"));
for (const auto& [name, group, shortcut] : default_hotkeys) {
const auto& [keyseq, context] = shortcut;
qt_config->beginGroup(group);
qt_config->beginGroup(name);
// No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
@@ -688,7 +687,10 @@ void Config::ReadShortcutValues() {
UISettings::values.shortcuts.push_back(
{name,
group,
{ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
{ReadSetting(QStringLiteral("KeySeq"), shortcut.keyseq).toString(),
ReadSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq)
.toString(),
shortcut.context}});
qt_config->endGroup();
qt_config->endGroup();
}
@@ -741,7 +743,10 @@ void Config::ReadUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
UISettings::values.theme =
ReadSetting(QStringLiteral("theme"), QString::fromUtf8(UISettings::themes[0].second))
ReadSetting(
QStringLiteral("theme"),
QString::fromUtf8(
UISettings::themes[static_cast<size_t>(UISettings::Theme::DarkColorful)].second))
.toString();
ReadBasicSetting(UISettings::values.enable_discord_presence);
ReadBasicSetting(UISettings::values.select_user_on_boot);
@@ -1227,8 +1232,10 @@ void Config::SaveShortcutValues() {
qt_config->beginGroup(group);
qt_config->beginGroup(name);
WriteSetting(QStringLiteral("KeySeq"), shortcut.first, default_hotkey.first);
WriteSetting(QStringLiteral("Context"), shortcut.second, default_hotkey.second);
WriteSetting(QStringLiteral("KeySeq"), shortcut.keyseq, default_hotkey.keyseq);
WriteSetting(QStringLiteral("Controller_KeySeq"), shortcut.controller_keyseq,
default_hotkey.controller_keyseq);
WriteSetting(QStringLiteral("Context"), shortcut.context, default_hotkey.context);
qt_config->endGroup();
qt_config->endGroup();
}
@@ -1266,8 +1273,10 @@ void Config::SaveSystemValues() {
void Config::SaveUIValues() {
qt_config->beginGroup(QStringLiteral("UI"));
WriteSetting(QStringLiteral("theme"), UISettings::values.theme,
QString::fromUtf8(UISettings::themes[0].second));
WriteSetting(
QStringLiteral("theme"), UISettings::values.theme,
QString::fromUtf8(
UISettings::themes[static_cast<size_t>(UISettings::Theme::DarkColorful)].second));
WriteBasicSetting(UISettings::values.enable_discord_presence);
WriteBasicSetting(UISettings::values.select_user_on_boot);

View File

@@ -45,7 +45,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)},
graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
input_tab{std::make_unique<ConfigureInput>(system_, this)},
network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
profile_tab{std::make_unique<ConfigureProfileManager>(system_, this)},

View File

@@ -5,15 +5,24 @@
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include "common/settings.h"
#include <QTimer>
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
#include "ui_configure_hotkeys.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_hotkeys.h"
#include "yuzu/hotkeys.h"
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
constexpr int name_column = 0;
constexpr int hotkey_column = 1;
constexpr int controller_column = 2;
ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@@ -26,16 +35,24 @@ ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->hotkey_list->setModel(model);
// TODO(Kloen): Make context configurable as well (hiding the column for now)
ui->hotkey_list->hideColumn(2);
ui->hotkey_list->setColumnWidth(0, 200);
ui->hotkey_list->resizeColumnToContents(1);
ui->hotkey_list->setColumnWidth(name_column, 200);
ui->hotkey_list->resizeColumnToContents(hotkey_column);
connect(ui->button_restore_defaults, &QPushButton::clicked, this,
&ConfigureHotkeys::RestoreDefaults);
connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll);
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this] {
const auto buttons = controller->GetNpadButtons();
if (buttons.raw != Core::HID::NpadButton::None) {
SetPollingResult(buttons.raw, false);
return;
}
});
RetranslateUI();
}
@@ -49,15 +66,18 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
auto* action = new QStandardItem(hotkey.first);
auto* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
auto* controller_keyseq = new QStandardItem(hotkey.second.controller_keyseq);
action->setEditable(false);
keyseq->setEditable(false);
parent_item->appendRow({action, keyseq});
controller_keyseq->setEditable(false);
parent_item->appendRow({action, keyseq, controller_keyseq});
}
model->appendRow(parent_item);
}
ui->hotkey_list->expandAll();
ui->hotkey_list->resizeColumnToContents(0);
ui->hotkey_list->resizeColumnToContents(name_column);
ui->hotkey_list->resizeColumnToContents(hotkey_column);
}
void ConfigureHotkeys::changeEvent(QEvent* event) {
@@ -71,7 +91,7 @@ void ConfigureHotkeys::changeEvent(QEvent* event) {
void ConfigureHotkeys::RetranslateUI() {
ui->retranslateUi(this);
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Controller Hotkey")});
}
void ConfigureHotkeys::Configure(QModelIndex index) {
@@ -79,7 +99,15 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
return;
}
index = index.sibling(index.row(), 1);
// Controller configuration is selected
if (index.column() == controller_column) {
ConfigureController(index);
return;
}
// Swap to the hotkey column
index = index.sibling(index.row(), hotkey_column);
const auto previous_key = model->data(index);
SequenceDialog hotkey_dialog{this};
@@ -99,13 +127,113 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
}
}
void ConfigureHotkeys::ConfigureController(QModelIndex index) {
if (timeout_timer->isActive()) {
return;
}
const auto previous_key = model->data(index);
input_setter = [this, index, previous_key](const Core::HID::NpadButton button,
const bool cancel) {
if (cancel) {
model->setData(index, previous_key);
return;
}
const QString button_string = tr("Home+%1").arg(GetButtonName(button));
const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string);
if (key_sequence_used) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The entered key sequence is already assigned to: %1").arg(used_action));
model->setData(index, previous_key);
} else {
model->setData(index, button_string);
}
};
model->setData(index, tr("[waiting]"));
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
// We need to disable configuration to be able to read npad buttons
controller->DisableConfiguration();
controller->DisableSystemButtons();
}
void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) {
timeout_timer->stop();
poll_timer->stop();
// Re-Enable configuration
controller->EnableConfiguration();
controller->EnableSystemButtons();
(*input_setter)(button, cancel);
input_setter = std::nullopt;
}
QString ConfigureHotkeys::GetButtonName(Core::HID::NpadButton button) const {
Core::HID::NpadButtonState state{button};
if (state.a) {
return tr("A");
}
if (state.b) {
return tr("B");
}
if (state.x) {
return tr("X");
}
if (state.y) {
return tr("Y");
}
if (state.l || state.right_sl || state.left_sl) {
return tr("L");
}
if (state.r || state.right_sr || state.left_sr) {
return tr("R");
}
if (state.zl) {
return tr("ZL");
}
if (state.zr) {
return tr("ZR");
}
if (state.left) {
return tr("Dpad_Left");
}
if (state.right) {
return tr("Dpad_Right");
}
if (state.up) {
return tr("Dpad_Up");
}
if (state.down) {
return tr("Dpad_Down");
}
if (state.stick_l) {
return tr("Left_Stick");
}
if (state.stick_r) {
return tr("Right_Stick");
}
if (state.minus) {
return tr("Minus");
}
if (state.plus) {
return tr("Plus");
}
return tr("Invalid");
}
std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* const parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
const QStandardItem* const key_seq_item = parent->child(r2, 1);
const QStandardItem* const key_seq_item = parent->child(r2, hotkey_column);
const auto key_seq_str = key_seq_item->text();
const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
@@ -118,12 +246,31 @@ std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence)
return std::make_pair(false, QString());
}
std::pair<bool, QString> ConfigureHotkeys::IsUsedControllerKey(const QString& key_sequence) const {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* const parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
const QStandardItem* const key_seq_item = parent->child(r2, controller_column);
const auto key_seq_str = key_seq_item->text();
if (key_sequence == key_seq_str) {
return std::make_pair(true, parent->child(r2, 0)->text());
}
}
}
return std::make_pair(false, QString());
}
void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
const QStandardItem* parent = model->item(key_id, 0);
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
const QStandardItem* action = parent->child(key_column_id, 0);
const QStandardItem* keyseq = parent->child(key_column_id, 1);
const QStandardItem* action = parent->child(key_column_id, name_column);
const QStandardItem* keyseq = parent->child(key_column_id, hotkey_column);
const QStandardItem* controller_keyseq =
parent->child(key_column_id, controller_column);
for (auto& [group, sub_actions] : registry.hotkey_groups) {
if (group != parent->text())
continue;
@@ -131,6 +278,7 @@ void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
if (action_name != action->text())
continue;
hotkey.keyseq = QKeySequence(keyseq->text());
hotkey.controller_keyseq = controller_keyseq->text();
}
}
}
@@ -144,7 +292,12 @@ void ConfigureHotkeys::RestoreDefaults() {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first);
model->item(r, 0)
->child(r2, hotkey_column)
->setText(Config::default_hotkeys[r2].shortcut.keyseq);
model->item(r, 0)
->child(r2, controller_column)
->setText(Config::default_hotkeys[r2].shortcut.controller_keyseq);
}
}
}
@@ -154,7 +307,8 @@ void ConfigureHotkeys::ClearAll() {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)->child(r2, 1)->setText(QString{});
model->item(r, 0)->child(r2, hotkey_column)->setText(QString{});
model->item(r, 0)->child(r2, controller_column)->setText(QString{});
}
}
}
@@ -165,28 +319,52 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
return;
}
const auto selected = index.sibling(index.row(), 1);
// Swap to the hotkey column if the controller hotkey column is not selected
if (index.column() != controller_column) {
index = index.sibling(index.row(), hotkey_column);
}
QMenu context_menu;
QAction* restore_default = context_menu.addAction(tr("Restore Default"));
QAction* clear = context_menu.addAction(tr("Clear"));
connect(restore_default, &QAction::triggered, [this, selected] {
const QKeySequence& default_key_sequence = QKeySequence::fromString(
Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText);
const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
if (key_sequence_used &&
default_key_sequence != QKeySequence(model->data(selected).toString())) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The default key sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
connect(restore_default, &QAction::triggered, [this, index] {
if (index.column() == controller_column) {
RestoreControllerHotkey(index);
return;
}
RestoreHotkey(index);
});
connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
connect(clear, &QAction::triggered, [this, index] { model->setData(index, QString{}); });
context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
}
void ConfigureHotkeys::RestoreControllerHotkey(QModelIndex index) {
const QString& default_key_sequence =
Config::default_hotkeys[index.row()].shortcut.controller_keyseq;
const auto [key_sequence_used, used_action] = IsUsedControllerKey(default_key_sequence);
if (key_sequence_used && default_key_sequence != model->data(index).toString()) {
QMessageBox::warning(
this, tr("Conflicting Button Sequence"),
tr("The default button sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(index, default_key_sequence);
}
}
void ConfigureHotkeys::RestoreHotkey(QModelIndex index) {
const QKeySequence& default_key_sequence = QKeySequence::fromString(
Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText);
const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The default key sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(index, default_key_sequence.toString(QKeySequence::NativeText));
}
}

View File

@@ -7,6 +7,16 @@
#include <memory>
#include <QWidget>
namespace Common {
class ParamPackage;
}
namespace Core::HID {
class HIDCore;
class EmulatedController;
enum class NpadButton : u64;
} // namespace Core::HID
namespace Ui {
class ConfigureHotkeys;
}
@@ -18,7 +28,7 @@ class ConfigureHotkeys : public QWidget {
Q_OBJECT
public:
explicit ConfigureHotkeys(QWidget* parent = nullptr);
explicit ConfigureHotkeys(Core::HID::HIDCore& hid_core_, QWidget* parent = nullptr);
~ConfigureHotkeys() override;
void ApplyConfiguration(HotkeyRegistry& registry);
@@ -35,13 +45,24 @@ private:
void RetranslateUI();
void Configure(QModelIndex index);
void ConfigureController(QModelIndex index);
std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const;
std::pair<bool, QString> IsUsedControllerKey(const QString& key_sequence) const;
void RestoreDefaults();
void ClearAll();
void PopupContextMenu(const QPoint& menu_location);
void RestoreControllerHotkey(QModelIndex index);
void RestoreHotkey(QModelIndex index);
std::unique_ptr<Ui::ConfigureHotkeys> ui;
QStandardItemModel* model;
void SetPollingResult(Core::HID::NpadButton button, bool cancel);
QString GetButtonName(Core::HID::NpadButton button) const;
Core::HID::EmulatedController* controller;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
std::optional<std::function<void(Core::HID::NpadButton, bool)>> input_setter;
};

View File

@@ -747,15 +747,16 @@ void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
const auto first_engine = devices[0].Get("engine", "");
const auto first_guid = devices[0].Get("guid", "");
const auto first_port = devices[0].Get("port", 0);
const auto first_pad = devices[0].Get("pad", 0);
if (devices.size() == 1) {
const auto devices_it =
std::find_if(input_devices.begin(), input_devices.end(),
[first_engine, first_guid, first_port](const Common::ParamPackage param) {
return param.Get("engine", "") == first_engine &&
param.Get("guid", "") == first_guid &&
param.Get("port", 0) == first_port;
});
const auto devices_it = std::find_if(
input_devices.begin(), input_devices.end(),
[first_engine, first_guid, first_port, first_pad](const Common::ParamPackage param) {
return param.Get("engine", "") == first_engine &&
param.Get("guid", "") == first_guid && param.Get("port", 0) == first_port &&
param.Get("pad", 0) == first_pad;
});
const int device_index =
devices_it != input_devices.end()
? static_cast<int>(std::distance(input_devices.begin(), devices_it))

View File

@@ -2,10 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <sstream>
#include <QKeySequence>
#include <QShortcut>
#include <QTreeWidgetItem>
#include <QtGlobal>
#include "core/hid/emulated_controller.h"
#include "yuzu/hotkeys.h"
#include "yuzu/uisettings.h"
@@ -18,8 +21,9 @@ void HotkeyRegistry::SaveHotkeys() {
for (const auto& hotkey : group.second) {
UISettings::values.shortcuts.push_back(
{hotkey.first, group.first,
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
hotkey.second.context)});
UISettings::ContextualShortcut({hotkey.second.keyseq.toString(),
hotkey.second.controller_keyseq,
hotkey.second.context})});
}
}
}
@@ -29,28 +33,49 @@ void HotkeyRegistry::LoadHotkeys() {
// beginGroup()
for (auto shortcut : UISettings::values.shortcuts) {
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
if (!shortcut.shortcut.first.isEmpty()) {
hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
if (!shortcut.shortcut.keyseq.isEmpty()) {
hk.keyseq =
QKeySequence::fromString(shortcut.shortcut.keyseq, QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.context);
}
if (!shortcut.shortcut.controller_keyseq.isEmpty()) {
hk.controller_keyseq = shortcut.shortcut.controller_keyseq;
}
if (hk.shortcut) {
hk.shortcut->disconnect();
hk.shortcut->setKey(hk.keyseq);
}
if (hk.controller_shortcut) {
hk.controller_shortcut->disconnect();
hk.controller_shortcut->SetKey(hk.controller_keyseq);
}
}
}
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
Hotkey& hk = hotkey_groups[group][action];
if (!hk.shortcut)
if (!hk.shortcut) {
hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context);
}
hk.shortcut->setAutoRepeat(false);
return hk.shortcut;
}
ControllerShortcut* HotkeyRegistry::GetControllerHotkey(const QString& group, const QString& action,
Core::HID::EmulatedController* controller) {
Hotkey& hk = hotkey_groups[group][action];
if (!hk.controller_shortcut) {
hk.controller_shortcut = new ControllerShortcut(controller);
hk.controller_shortcut->SetKey(hk.controller_keyseq);
}
return hk.controller_shortcut;
}
QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
return hotkey_groups[group][action].keyseq;
}
@@ -59,3 +84,131 @@ Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
const QString& action) {
return hotkey_groups[group][action].context;
}
ControllerShortcut::ControllerShortcut(Core::HID::EmulatedController* controller) {
emulated_controller = controller;
Core::HID::ControllerUpdateCallback engine_callback{
.on_change = [this](Core::HID::ControllerTriggerType type) { ControllerUpdateEvent(type); },
.is_npad_service = false,
};
callback_key = emulated_controller->SetCallback(engine_callback);
is_enabled = true;
}
ControllerShortcut::~ControllerShortcut() {
emulated_controller->DeleteCallback(callback_key);
}
void ControllerShortcut::SetKey(const ControllerButtonSequence& buttons) {
button_sequence = buttons;
}
void ControllerShortcut::SetKey(const QString& buttons_shortcut) {
ControllerButtonSequence sequence{};
name = buttons_shortcut.toStdString();
std::istringstream command_line(buttons_shortcut.toStdString());
std::string line;
while (std::getline(command_line, line, '+')) {
if (line.empty()) {
continue;
}
if (line == "A") {
sequence.npad.a.Assign(1);
}
if (line == "B") {
sequence.npad.b.Assign(1);
}
if (line == "X") {
sequence.npad.x.Assign(1);
}
if (line == "Y") {
sequence.npad.y.Assign(1);
}
if (line == "L") {
sequence.npad.l.Assign(1);
}
if (line == "R") {
sequence.npad.r.Assign(1);
}
if (line == "ZL") {
sequence.npad.zl.Assign(1);
}
if (line == "ZR") {
sequence.npad.zr.Assign(1);
}
if (line == "Dpad_Left") {
sequence.npad.left.Assign(1);
}
if (line == "Dpad_Right") {
sequence.npad.right.Assign(1);
}
if (line == "Dpad_Up") {
sequence.npad.up.Assign(1);
}
if (line == "Dpad_Down") {
sequence.npad.down.Assign(1);
}
if (line == "Left_Stick") {
sequence.npad.stick_l.Assign(1);
}
if (line == "Right_Stick") {
sequence.npad.stick_r.Assign(1);
}
if (line == "Minus") {
sequence.npad.minus.Assign(1);
}
if (line == "Plus") {
sequence.npad.plus.Assign(1);
}
if (line == "Home") {
sequence.home.home.Assign(1);
}
if (line == "Screenshot") {
sequence.capture.capture.Assign(1);
}
}
button_sequence = sequence;
}
ControllerButtonSequence ControllerShortcut::ButtonSequence() const {
return button_sequence;
}
void ControllerShortcut::SetEnabled(bool enable) {
is_enabled = enable;
}
bool ControllerShortcut::IsEnabled() const {
return is_enabled;
}
void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType type) {
if (!is_enabled) {
return;
}
if (type != Core::HID::ControllerTriggerType::Button) {
return;
}
if (button_sequence.npad.raw == Core::HID::NpadButton::None &&
button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) {
return;
}
const auto player_npad_buttons =
emulated_controller->GetNpadButtons().raw & button_sequence.npad.raw;
const u64 player_capture_buttons =
emulated_controller->GetCaptureButtons().raw & button_sequence.capture.raw;
const u64 player_home_buttons =
emulated_controller->GetHomeButtons().raw & button_sequence.home.raw;
if (player_npad_buttons == button_sequence.npad.raw &&
player_capture_buttons == button_sequence.capture.raw &&
player_home_buttons == button_sequence.home.raw && !active) {
// Force user to press the home or capture button again
active = true;
emit Activated();
return;
}
active = false;
}

View File

@@ -5,11 +5,53 @@
#pragma once
#include <map>
#include "core/hid/hid_types.h"
class QDialog;
class QKeySequence;
class QSettings;
class QShortcut;
class ControllerShortcut;
namespace Core::HID {
enum class ControllerTriggerType;
class EmulatedController;
} // namespace Core::HID
struct ControllerButtonSequence {
Core::HID::CaptureButtonState capture{};
Core::HID::HomeButtonState home{};
Core::HID::NpadButtonState npad{};
};
class ControllerShortcut : public QObject {
Q_OBJECT
public:
explicit ControllerShortcut(Core::HID::EmulatedController* controller);
~ControllerShortcut();
void SetKey(const ControllerButtonSequence& buttons);
void SetKey(const QString& buttons_shortcut);
ControllerButtonSequence ButtonSequence() const;
void SetEnabled(bool enable);
bool IsEnabled() const;
Q_SIGNALS:
void Activated();
private:
void ControllerUpdateEvent(Core::HID::ControllerTriggerType type);
bool is_enabled{};
bool active{};
int callback_key{};
ControllerButtonSequence button_sequence{};
std::string name{};
Core::HID::EmulatedController* emulated_controller = nullptr;
};
class HotkeyRegistry final {
public:
@@ -46,6 +88,8 @@ public:
* QShortcut's parent.
*/
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
ControllerShortcut* GetControllerHotkey(const QString& group, const QString& action,
Core::HID::EmulatedController* controller);
/**
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
@@ -68,7 +112,9 @@ public:
private:
struct Hotkey {
QKeySequence keyseq;
QString controller_keyseq;
QShortcut* shortcut = nullptr;
ControllerShortcut* controller_shortcut = nullptr;
Qt::ShortcutContext context = Qt::WindowShortcut;
};

View File

@@ -32,6 +32,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
#include "yuzu/util/controller_navigation.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
// defines.
@@ -966,6 +967,12 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name
action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name));
this->addAction(action);
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
connect(controller_hotkey, &ControllerShortcut::Activated, this,
[action] { action->trigger(); });
}
void GMainWindow::InitializeHotkeys() {
@@ -987,8 +994,12 @@ void GMainWindow::InitializeHotkeys() {
static const QString main_window = QStringLiteral("Main Window");
const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) {
const QShortcut* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
const auto* hotkey = hotkey_registry.GetHotkey(main_window, action_name, this);
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
connect(hotkey, &QShortcut::activated, this, function);
connect(controller_hotkey, &ControllerShortcut::Activated, this, function);
};
connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] {
@@ -1165,8 +1176,7 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Single_Window_Mode, &GMainWindow::ToggleWindowMode);
connect_menu(ui->action_Display_Dock_Widget_Headers, &GMainWindow::OnDisplayTitleBars);
connect_menu(ui->action_Show_Filter_Bar, &GMainWindow::OnToggleFilterBar);
connect(ui->action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
connect_menu(ui->action_Show_Status_Bar, &GMainWindow::OnToggleStatusBar);
connect_menu(ui->action_Reset_Window_Size_720, &GMainWindow::ResetWindowSize720);
connect_menu(ui->action_Reset_Window_Size_900, &GMainWindow::ResetWindowSize900);
@@ -2168,6 +2178,11 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
}
void GMainWindow::OnMenuLoadFile() {
if (is_load_file_select_active) {
return;
}
is_load_file_select_active = true;
const QString extensions =
QStringLiteral("*.")
.append(GameList::supported_file_extensions.join(QStringLiteral(" *.")))
@@ -2177,6 +2192,7 @@ void GMainWindow::OnMenuLoadFile() {
.arg(extensions);
const QString filename = QFileDialog::getOpenFileName(
this, tr("Load File"), UISettings::values.roms_path, file_filter);
is_load_file_select_active = false;
if (filename.isEmpty()) {
return;
@@ -2809,6 +2825,11 @@ void GMainWindow::OnTasStartStop() {
if (!emulation_running) {
return;
}
// Disable system buttons to prevent TAS from executing a hotkey
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
controller->ResetSystemButtons();
input_subsystem->GetTas()->StartStop();
OnTasStateChanged();
}
@@ -2817,12 +2838,34 @@ void GMainWindow::OnTasRecord() {
if (!emulation_running) {
return;
}
if (is_tas_recording_dialog_active) {
return;
}
// Disable system buttons to prevent TAS from recording a hotkey
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
controller->ResetSystemButtons();
const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) {
const auto res =
QMessageBox::question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
QMessageBox::Yes | QMessageBox::No);
is_tas_recording_dialog_active = true;
ControllerNavigation* controller_navigation =
new ControllerNavigation(system->HIDCore(), this);
// Use QMessageBox instead of question so we can link controller navigation
QMessageBox* box_dialog = new QMessageBox();
box_dialog->setWindowTitle(tr("TAS Recording"));
box_dialog->setText(tr("Overwrite file of player 1?"));
box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
box_dialog->setDefaultButton(QMessageBox::Yes);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[box_dialog](Qt::Key key) {
QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
QCoreApplication::postEvent(box_dialog, event);
});
int res = box_dialog->exec();
controller_navigation->UnloadController();
input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
is_tas_recording_dialog_active = false;
}
OnTasStateChanged();
}
@@ -2871,10 +2914,15 @@ void GMainWindow::OnLoadAmiibo() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
}
if (is_amiibo_file_select_active) {
return;
}
is_amiibo_file_select_active = true;
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
is_amiibo_file_select_active = false;
if (filename.isEmpty()) {
return;
@@ -2934,6 +2982,10 @@ void GMainWindow::OnToggleFilterBar() {
}
}
void GMainWindow::OnToggleStatusBar() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
}
void GMainWindow::OnCaptureScreenshot() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
@@ -3626,8 +3678,8 @@ int main(int argc, char* argv[]) {
QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
#ifdef _WIN32
// Increases the maximum open file limit to 4096
_setmaxstdio(4096);
// Increases the maximum open file limit to 8192
_setmaxstdio(8192);
#endif
#ifdef __APPLE__

View File

@@ -186,6 +186,9 @@ public slots:
void OnTasStateChanged();
private:
/// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry.
void LinkActionShortcut(QAction* action, const QString& action_name);
void RegisterMetaTypes();
void InitializeWidgets();
@@ -286,6 +289,7 @@ private slots:
void OnOpenYuzuFolder();
void OnAbout();
void OnToggleFilterBar();
void OnToggleStatusBar();
void OnDisplayTitleBars(bool);
void InitializeHotkeys();
void ToggleFullscreen();
@@ -303,9 +307,6 @@ private slots:
void OnMouseActivity();
private:
/// Updates an action's shortcut and text to reflect an updated hotkey from the hotkey registry.
void LinkActionShortcut(QAction* action, const QString& action_name);
void RemoveBaseContent(u64 program_id, const QString& entry_type);
void RemoveUpdateContent(u64 program_id, const QString& entry_type);
void RemoveAddOnContent(u64 program_id, const QString& entry_type);
@@ -400,6 +401,16 @@ private:
// Applets
QtSoftwareKeyboardDialog* software_keyboard = nullptr;
// True if amiibo file select is visible
bool is_amiibo_file_select_active{};
// True if load file select is visible
bool is_load_file_select_active{};
// True if TAS recording dialog is visible
bool is_tas_recording_dialog_active{};
#ifdef __linux__
QDBusObjectPath wake_lock{};
#endif

View File

@@ -17,7 +17,11 @@
namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
struct ContextualShortcut {
QString keyseq;
QString controller_keyseq;
int context;
};
struct Shortcut {
QString name;
@@ -25,6 +29,15 @@ struct Shortcut {
ContextualShortcut shortcut;
};
enum class Theme {
Default,
DefaultColorful,
Dark,
DarkColorful,
MidnightBlue,
MidnightBlueColorful,
};
using Themes = std::array<std::pair<const char*, const char*>, 6>;
extern const Themes themes;