Merge pull request #4866 from Morph1984/mjolnir-p3-prod

Project Mjölnir: Part 3 - Controller Profiles and Vibration Rework
This commit is contained in:
bunnei 2020-11-17 20:02:27 -08:00 committed by GitHub
commit abda366362
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
69 changed files with 3401 additions and 1588 deletions

View file

@ -1,3 +1,7 @@
QAbstractSpinBox {
min-height: 19px;
}
QPushButton#TogglableStatusBarButton {
color: #959595;
border: 1px solid transparent;
@ -35,10 +39,10 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
min-width: 21px;
min-height: 21px;
max-width: 21px;
max-height: 21px;
}
QWidget#bottomPerGameInput,
@ -71,7 +75,7 @@ QWidget#middleControllerApplet {
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
width: 123px;
width: 120px;
}
QWidget#connectedControllers {

View file

@ -99,12 +99,19 @@ QGroupBox::indicator:unchecked:disabled {
}
QRadioButton {
spacing: 5px;
outline: none;
color: #eff0f1;
spacing: 3px;
padding: 0px;
border: none;
outline: none;
margin-bottom: 2px;
}
QGroupBox QRadioButton {
padding-left: 0px;
padding-right: 7px;
}
QRadioButton:disabled {
color: #76797C;
}
@ -522,13 +529,12 @@ QToolButton#qt_toolbar_ext_button {
QPushButton {
color: #eff0f1;
border-width: 1px;
border-color: #54575B;
border-style: solid;
padding: 6px 4px;
border: 1px solid #54575B;
border-radius: 2px;
padding: 5px 0px 5px 0px;
outline: none;
min-width: 100px;
min-height: 13px;
background-color: #232629;
}
@ -553,8 +559,9 @@ QComboBox {
selection-background-color: #3daee9;
border: 1px solid #54575B;
border-radius: 2px;
padding: 4px 6px;
min-width: 75px;
padding: 0px 4px 0px 4px;
min-width: 60px;
min-height: 23px;
background-color: #232629;
}
@ -608,26 +615,26 @@ QComboBox::down-arrow:focus {
}
QAbstractSpinBox {
padding: 4px 6px;
border: 1px solid #54575B;
background-color: #232629;
color: #eff0f1;
border-radius: 2px;
min-width: 75px;
min-width: 52px;
min-height: 23px;
}
QAbstractSpinBox:up-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center right;
left: -6px;
left: -2px;
}
QAbstractSpinBox:down-button {
background-color: transparent;
subcontrol-origin: border;
subcontrol-position: center left;
right: -6px;
right: -2px;
}
QAbstractSpinBox::up-arrow,
@ -1277,41 +1284,33 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 24px;
min-height: 24px;
max-width: 24px;
max-height: 24px;
min-width: 23px;
min-height: 23px;
max-width: 23px;
max-height: 23px;
padding: 0px 0px;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange {
padding: 4px 0px 5px 0px;
min-width: 63px;
}
QSpinBox#vibrationSpin {
padding: 4px 0px 5px 0px;
min-width: 63px;
}
QSpinBox#spinboxLStickRange:up-button,
QSpinBox#spinboxRStickRange:up-button,
QSpinBox#vibrationSpin:up-button {
left: -2px;
}
QSpinBox#spinboxLStickRange:down-button,
QSpinBox#spinboxRStickRange:down-button,
QSpinBox#vibrationSpin:down-button {
right: -1px;
QSpinBox#spinboxRStickRange,
QSpinBox#vibrationSpinPlayer1,
QSpinBox#vibrationSpinPlayer2,
QSpinBox#vibrationSpinPlayer3,
QSpinBox#vibrationSpinPlayer4,
QSpinBox#vibrationSpinPlayer5,
QSpinBox#vibrationSpinPlayer6,
QSpinBox#vibrationSpinPlayer7,
QSpinBox#vibrationSpinPlayer8 {
min-width: 68px;
}
QDialog#ConfigureVibration QGroupBox::indicator,
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QDialog#ConfigureVibration QGroupBox::title,
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
@ -1340,16 +1339,7 @@ QWidget#middleControllerApplet {
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
width: 119px;
}
QRadioButton#radioDocked {
margin-left: -3px;
}
QRadioButton#radioUndocked {
margin-right: 5px;
width: 120px;
}
QWidget#connectedControllers {

View file

@ -172,8 +172,8 @@ QCheckBox {
color: #F0F0F0;
spacing: 4px;
outline: none;
padding-top: 4px;
padding-bottom: 4px;
padding-top: 2px;
padding-bottom: 2px;
}
QCheckBox:focus {
@ -239,7 +239,7 @@ QGroupBox {
border: 1px solid #32414B;
border-radius: 4px;
margin-top: 12px;
padding: 4px;
padding: 2px;
}
QGroupBox::title {
@ -247,7 +247,7 @@ QGroupBox::title {
subcontrol-position: top left;
padding-left: 3px;
padding-right: 5px;
padding-top: 4px;
padding-top: 2px;
}
QGroupBox::indicator {
@ -298,6 +298,11 @@ QRadioButton {
outline: none;
}
QGroupBox QRadioButton {
padding-left: 0px;
padding-right: 7px;
}
QRadioButton:focus {
border: none;
}
@ -321,7 +326,6 @@ QRadioButton QWidget {
QRadioButton::indicator {
border: none;
outline: none;
margin-left: 4px;
height: 16px;
width: 16px;
}
@ -785,14 +789,8 @@ QAbstractSpinBox {
background-color: #19232D;
border: 1px solid #32414B;
color: #F0F0F0;
/* This fixes 103, 111 */
padding-top: 2px;
/* This fixes 103, 111 */
padding-bottom: 2px;
padding-left: 4px;
padding-right: 4px;
border-radius: 4px;
/* min-width: 5px; removed to fix 109 */
min-height: 19px;
}
QAbstractSpinBox:up-button {
@ -997,10 +995,11 @@ QPushButton {
border: 1px solid #32414B;
color: #F0F0F0;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
/* Issue #194 - Special case of QPushButton inside dialogs, for better UI */
min-width: 80px;
min-height: 13px;
}
QPushButton:disabled {
@ -1008,14 +1007,14 @@ QPushButton:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
}
QPushButton:checked {
background-color: #32414B;
border: 1px solid #32414B;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
}
@ -1024,7 +1023,7 @@ QPushButton:checked:disabled {
border: 1px solid #32414B;
color: #787878;
border-radius: 4px;
padding: 3px;
padding: 3px 0px 3px 0px;
outline: none;
}
@ -1197,15 +1196,9 @@ QComboBox {
border: 1px solid #32414B;
border-radius: 4px;
selection-background-color: #1464A0;
padding-left: 4px;
padding-right: 36px;
/* 4 + 16*2 See scrollbar size */
/* Fixes #103, #111 */
min-height: 1.5em;
/* padding-top: 2px; removed to fix #132 */
/* padding-bottom: 2px; removed to fix #132 */
/* min-width: 75px; removed to fix #109 */
/* Needed to remove indicator - fix #132 */
padding: 0px 4px 0px 4px;
min-width: 60px;
min-height: 19px;
}
QComboBox QAbstractItemView {
@ -2198,29 +2191,40 @@ QPushButton#RendererStatusBarButton:!checked {
}
QPushButton#buttonRefreshDevices {
min-width: 20px;
min-height: 20px;
max-width: 20px;
max-height: 20px;
min-width: 19px;
min-height: 19px;
max-width: 19px;
max-height: 19px;
padding: 0px 0px;
}
QSpinBox#spinboxLStickRange,
QSpinBox#spinboxRStickRange {
min-width: 38px;
QSpinBox#spinboxRStickRange,
QSpinBox#vibrationSpinPlayer1,
QSpinBox#vibrationSpinPlayer2,
QSpinBox#vibrationSpinPlayer3,
QSpinBox#vibrationSpinPlayer4,
QSpinBox#vibrationSpinPlayer5,
QSpinBox#vibrationSpinPlayer6,
QSpinBox#vibrationSpinPlayer7,
QSpinBox#vibrationSpinPlayer8 {
min-width: 68px;
}
QDialog#ConfigureVibration QGroupBox::indicator,
QGroupBox#motionGroup::indicator,
QGroupBox#vibrationGroup::indicator {
margin-left: 0px;
}
QDialog#ConfigureVibration QGroupBox,
QWidget#bottomPerGameInput QGroupBox#motionGroup,
QWidget#bottomPerGameInput QGroupBox#vibrationGroup,
QWidget#bottomPerGameInput QGroupBox#inputConfigGroup {
padding: 0px;
}
QDialog#ConfigureVibration QGroupBox::title,
QGroupBox#motionGroup::title,
QGroupBox#vibrationGroup::title {
spacing: 2px;
@ -2260,26 +2264,7 @@ QWidget#middleControllerApplet {
QWidget#topPerGameInput QComboBox,
QWidget#middleControllerApplet QComboBox {
padding-right: 2px;
width: 127px;
}
QGroupBox#handheldGroup {
padding-left: 0px;
}
QRadioButton#radioDocked {
margin-left: -1px;
padding-left: 0px;
}
QRadioButton#radioDocked::indicator {
margin-left: 0px;
}
QRadioButton#radioUndocked {
margin-right: 2px;
width: 120px;
}
QWidget#connectedControllers {
@ -2352,7 +2337,7 @@ QCheckBox#checkboxPlayer5Connected,
QCheckBox#checkboxPlayer6Connected,
QCheckBox#checkboxPlayer7Connected,
QCheckBox#checkboxPlayer8Connected {
spacing: 0px;
spacing: 0px;
}
QWidget#connectedControllers QLabel {
@ -2427,7 +2412,7 @@ QCheckBox#checkboxPlayer7Connected::indicator,
QCheckBox#checkboxPlayer8Connected::indicator {
width: 14px;
height: 14px;
margin-left: 2px;
margin-left: 0px;
}
QWidget#Player1LEDs QCheckBox::indicator:checked,

View file

@ -27,19 +27,19 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
auto& players = Settings::values.players;
auto& players = Settings::values.players.GetValue();
const std::size_t min_supported_players =
parameters.enable_single_mode ? 1 : parameters.min_players;
// Disconnect Handheld first.
npad.DisconnectNPadAtIndex(8);
npad.DisconnectNpadAtIndex(8);
// Deduce the best configuration based on the input parameters.
for (std::size_t index = 0; index < players.size() - 2; ++index) {
// First, disconnect all controllers regardless of the value of keep_controllers_connected.
// This makes it easy to connect the desired controllers.
npad.DisconnectNPadAtIndex(index);
npad.DisconnectNpadAtIndex(index);
// Only connect the minimum number of required players.
if (index >= min_supported_players) {
@ -66,7 +66,7 @@ void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callb
npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
}
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
!Settings::values.use_docked_mode) {
!Settings::values.use_docked_mode.GetValue()) {
// We should *never* reach here under any normal circumstances.
npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
index);

View file

@ -47,7 +47,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
u32 width, height;
if (Settings::values.use_docked_mode) {
if (Settings::values.use_docked_mode.GetValue()) {
width = ScreenDocked::Width * res_scale;
height = ScreenDocked::Height * res_scale;
} else {

View file

@ -33,7 +33,7 @@ public:
virtual bool GetAnalogDirectionStatus(AnalogDirection direction) const {
return {};
}
virtual bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const {
virtual bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const {
return {};
}
};
@ -121,6 +121,13 @@ using ButtonDevice = InputDevice<bool>;
*/
using AnalogDevice = InputDevice<std::tuple<float, float>>;
/**
* A vibration device is an input device that returns an unsigned byte as status.
* It represents whether the vibration device supports vibration or not.
* If the status returns 1, it supports vibration. Otherwise, it does not support vibration.
*/
using VibrationDevice = InputDevice<u8>;
/**
* A motion status is an object that returns a tuple of accelerometer state vector,
* gyroscope state vector, rotation state vector and orientation state matrix.

View file

@ -751,7 +751,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext&
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
if (Settings::values.use_docked_mode) {
if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *
@ -824,7 +824,7 @@ void IStorage::Open(Kernel::HLERequestContext& ctx) {
}
void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) {
const bool use_docked_mode{Settings::values.use_docked_mode};
const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()};
LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode);
IPC::ResponseBuilder rb{ctx, 3};

View file

@ -25,7 +25,7 @@ namespace Service::AM::Applets {
static Core::Frontend::ControllerParameters ConvertToFrontendParameters(
ControllerSupportArgPrivate private_arg, ControllerSupportArgHeader header, bool enable_text,
std::vector<IdentificationColor> identification_colors, std::vector<ExplainText> text) {
HID::Controller_NPad::NPadType npad_style_set;
HID::Controller_NPad::NpadStyleSet npad_style_set;
npad_style_set.raw = private_arg.style_set;
return {
@ -222,7 +222,7 @@ void Controller::Execute() {
void Controller::ConfigurationComplete() {
ControllerSupportResultInfo result_info{};
const auto& players = Settings::values.players;
const auto& players = Settings::values.players.GetValue();
// If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
// Otherwise, only count connected players from P1-P8.

View file

@ -69,7 +69,8 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) {
}
PerformanceMode Controller::GetCurrentPerformanceMode() const {
return Settings::values.use_docked_mode ? PerformanceMode::Docked : PerformanceMode::Handheld;
return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Docked
: PerformanceMode::Handheld;
}
PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) {

View file

@ -117,7 +117,10 @@ u32 Controller_NPad::IndexToNPad(std::size_t index) {
}
Controller_NPad::Controller_NPad(Core::System& system) : ControllerBase(system), system(system) {}
Controller_NPad::~Controller_NPad() = default;
Controller_NPad::~Controller_NPad() {
OnRelease();
}
void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
const auto controller_type = connected_controllers[controller_idx].type;
@ -139,7 +142,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
controller.pad_assignment = NpadAssignments::Single;
break;
case NPadControllerType::Handheld:
controller.joy_styles.handheld.Assign(1);
@ -147,7 +150,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
controller.pad_assignment = NpadAssignments::Dual;
break;
case NPadControllerType::JoyDual:
controller.joy_styles.joycon_dual.Assign(1);
@ -156,26 +159,26 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
controller.properties.is_vertical.Assign(1);
controller.properties.use_plus.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Dual;
controller.pad_assignment = NpadAssignments::Dual;
break;
case NPadControllerType::JoyLeft:
controller.joy_styles.joycon_left.Assign(1);
controller.device_type.joycon_left.Assign(1);
controller.properties.is_horizontal.Assign(1);
controller.properties.use_minus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
controller.pad_assignment = NpadAssignments::Single;
break;
case NPadControllerType::JoyRight:
controller.joy_styles.joycon_right.Assign(1);
controller.device_type.joycon_right.Assign(1);
controller.properties.is_horizontal.Assign(1);
controller.properties.use_plus.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
controller.pad_assignment = NpadAssignments::Single;
break;
case NPadControllerType::Pokeball:
controller.joy_styles.pokeball.Assign(1);
controller.device_type.pokeball.Assign(1);
controller.pad_assignment = NPadAssignments::Single;
controller.pad_assignment = NpadAssignments::Single;
break;
}
@ -184,11 +187,14 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
controller.single_color.button_color = 0;
controller.dual_color_error = ColorReadError::ReadOk;
controller.left_color.body_color = Settings::values.players[controller_idx].body_color_left;
controller.left_color.button_color = Settings::values.players[controller_idx].button_color_left;
controller.right_color.body_color = Settings::values.players[controller_idx].body_color_right;
controller.left_color.body_color =
Settings::values.players.GetValue()[controller_idx].body_color_left;
controller.left_color.button_color =
Settings::values.players.GetValue()[controller_idx].button_color_left;
controller.right_color.body_color =
Settings::values.players.GetValue()[controller_idx].body_color_right;
controller.right_color.button_color =
Settings::values.players[controller_idx].button_color_right;
Settings::values.players.GetValue()[controller_idx].button_color_right;
controller.battery_level[0] = BATTERY_FULL;
controller.battery_level[1] = BATTERY_FULL;
@ -199,7 +205,7 @@ void Controller_NPad::InitNewlyAddedController(std::size_t controller_idx) {
void Controller_NPad::OnInit() {
auto& kernel = system.Kernel();
for (std::size_t i = 0; i < styleset_changed_events.size(); i++) {
for (std::size_t i = 0; i < styleset_changed_events.size(); ++i) {
styleset_changed_events[i] = Kernel::WritableEvent::CreateEventPair(
kernel, fmt::format("npad:NpadStyleSetChanged_{}", i));
}
@ -208,6 +214,8 @@ void Controller_NPad::OnInit() {
return;
}
OnLoadInputDevices();
if (style.raw == 0) {
// We want to support all controllers
style.handheld.Assign(1);
@ -218,12 +226,27 @@ void Controller_NPad::OnInit() {
style.pokeball.Assign(1);
}
std::transform(Settings::values.players.begin(), Settings::values.players.end(),
connected_controllers.begin(), [](const Settings::PlayerInput& player) {
std::transform(Settings::values.players.GetValue().begin(),
Settings::values.players.GetValue().end(), connected_controllers.begin(),
[](const Settings::PlayerInput& player) {
return ControllerHolder{MapSettingsTypeToNPad(player.controller_type),
player.connected};
});
// Connect the Player 1 or Handheld controller if none are connected.
if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
[](const ControllerHolder& controller) { return controller.is_connected; })) {
const auto controller =
MapSettingsTypeToNPad(Settings::values.players.GetValue()[0].controller_type);
if (controller == NPadControllerType::Handheld) {
Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
connected_controllers[HANDHELD_INDEX] = {controller, true};
} else {
Settings::values.players.GetValue()[0].connected = true;
connected_controllers[0] = {controller, true};
}
}
// Account for handheld
if (connected_controllers[HANDHELD_INDEX].is_connected) {
connected_controllers[HANDHELD_INDEX].type = NPadControllerType::Handheld;
@ -242,7 +265,7 @@ void Controller_NPad::OnInit() {
}
void Controller_NPad::OnLoadInputDevices() {
const auto& players = Settings::values.players;
const auto& players = Settings::values.players.GetValue();
for (std::size_t i = 0; i < players.size(); ++i) {
std::transform(players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
players[i].buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
@ -250,13 +273,26 @@ void Controller_NPad::OnLoadInputDevices() {
std::transform(players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
players[i].analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
sticks[i].begin(), Input::CreateDevice<Input::AnalogDevice>);
std::transform(players[i].vibrations.begin() +
Settings::NativeVibration::VIBRATION_HID_BEGIN,
players[i].vibrations.begin() + Settings::NativeVibration::VIBRATION_HID_END,
vibrations[i].begin(), Input::CreateDevice<Input::VibrationDevice>);
std::transform(players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_BEGIN,
players[i].motions.begin() + Settings::NativeMotion::MOTION_HID_END,
motions[i].begin(), Input::CreateDevice<Input::MotionDevice>);
for (std::size_t device_idx = 0; device_idx < vibrations[i].size(); ++device_idx) {
InitializeVibrationDeviceAtIndex(i, device_idx);
}
}
}
void Controller_NPad::OnRelease() {}
void Controller_NPad::OnRelease() {
for (std::size_t npad_idx = 0; npad_idx < vibrations.size(); ++npad_idx) {
for (std::size_t device_idx = 0; device_idx < vibrations[npad_idx].size(); ++device_idx) {
VibrateControllerAtIndex(npad_idx, device_idx, {});
}
}
}
void Controller_NPad::RequestPadStateUpdate(u32 npad_id) {
const auto controller_idx = NPadIdToIndex(npad_id);
@ -339,7 +375,7 @@ void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing, u8*
if (!IsControllerActivated()) {
return;
}
for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
auto& npad = shared_memory_entries[i];
const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
&npad.handheld_states,
@ -481,7 +517,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
if (!IsControllerActivated()) {
return;
}
for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
for (std::size_t i = 0; i < shared_memory_entries.size(); ++i) {
auto& npad = shared_memory_entries[i];
const auto& controller_type = connected_controllers[i].type;
@ -515,7 +551,7 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
// Try to read sixaxis sensor states
std::array<MotionDevice, 2> motion_devices;
if (sixaxis_sensors_enabled && Settings::values.motion_enabled) {
if (sixaxis_sensors_enabled && Settings::values.motion_enabled.GetValue()) {
sixaxis_at_rest = true;
for (std::size_t e = 0; e < motion_devices.size(); ++e) {
const auto& device = motions[i][e];
@ -601,15 +637,15 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
shared_memory_entries.size() * sizeof(NPadEntry));
}
void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
void Controller_NPad::SetSupportedStyleSet(NpadStyleSet style_set) {
style.raw = style_set.raw;
}
Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
Controller_NPad::NpadStyleSet Controller_NPad::GetSupportedStyleSet() const {
return style;
}
void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
void Controller_NPad::SetSupportedNpadIdTypes(u8* data, std::size_t length) {
ASSERT(length > 0 && (length % sizeof(u32)) == 0);
supported_npad_id_types.clear();
supported_npad_id_types.resize(length / sizeof(u32));
@ -621,7 +657,7 @@ void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length)
std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
}
std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
std::size_t Controller_NPad::GetSupportedNpadIdTypesSize() const {
return supported_npad_id_types.size();
}
@ -641,7 +677,7 @@ Controller_NPad::NpadHandheldActivationMode Controller_NPad::GetNpadHandheldActi
return handheld_activation_mode;
}
void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
void Controller_NPad::SetNpadMode(u32 npad_id, NpadAssignments assignment_mode) {
const std::size_t npad_index = NPadIdToIndex(npad_id);
ASSERT(npad_index < shared_memory_entries.size());
if (shared_memory_entries[npad_index].pad_assignment != assignment_mode) {
@ -649,35 +685,140 @@ void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode)
}
}
void Controller_NPad::VibrateController(const std::vector<u32>& controllers,
const std::vector<Vibration>& vibrations) {
LOG_TRACE(Service_HID, "called");
bool Controller_NPad::VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
const VibrationValue& vibration_value) {
if (!connected_controllers[npad_index].is_connected || !vibrations[npad_index][device_index]) {
return false;
}
if (!Settings::values.vibration_enabled || !can_controllers_vibrate) {
const auto& player = Settings::values.players.GetValue()[npad_index];
if (!player.vibration_enabled) {
if (latest_vibration_values[npad_index][device_index].amp_low != 0.0f ||
latest_vibration_values[npad_index][device_index].amp_high != 0.0f) {
// Send an empty vibration to stop any vibrations.
vibrations[npad_index][device_index]->SetRumblePlay(0.0f, 160.0f, 0.0f, 320.0f);
// Then reset the vibration value to its default value.
latest_vibration_values[npad_index][device_index] = {};
}
return false;
}
if (!Settings::values.enable_accurate_vibrations.GetValue()) {
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::chrono::steady_clock;
const auto now = steady_clock::now();
// Filter out non-zero vibrations that are within 10ms of each other.
if ((vibration_value.amp_low != 0.0f || vibration_value.amp_high != 0.0f) &&
duration_cast<milliseconds>(now - last_vibration_timepoints[npad_index][device_index]) <
milliseconds(10)) {
return false;
}
last_vibration_timepoints[npad_index][device_index] = now;
}
auto& vibration = vibrations[npad_index][device_index];
const auto player_vibration_strength = static_cast<f32>(player.vibration_strength);
const auto amp_low =
std::min(vibration_value.amp_low * player_vibration_strength / 100.0f, 1.0f);
const auto amp_high =
std::min(vibration_value.amp_high * player_vibration_strength / 100.0f, 1.0f);
return vibration->SetRumblePlay(amp_low, vibration_value.freq_low, amp_high,
vibration_value.freq_high);
}
void Controller_NPad::VibrateController(const DeviceHandle& vibration_device_handle,
const VibrationValue& vibration_value) {
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
return;
}
bool success = true;
for (std::size_t i = 0; i < controllers.size(); ++i) {
if (!connected_controllers[i].is_connected) {
continue;
}
using namespace Settings::NativeButton;
const auto& button_state = buttons[i];
if (button_state[A - BUTTON_HID_BEGIN]) {
if (button_state[A - BUTTON_HID_BEGIN]->SetRumblePlay(
vibrations[0].amp_high, vibrations[0].amp_low, vibrations[0].freq_high,
vibrations[0].freq_low)) {
success = false;
}
}
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
if (!vibration_devices_mounted[npad_index][device_index] ||
!connected_controllers[npad_index].is_connected) {
return;
}
if (success) {
last_processed_vibration = vibrations.back();
if (vibration_device_handle.device_index == DeviceIndex::None) {
UNREACHABLE_MSG("DeviceIndex should never be None!");
return;
}
// Some games try to send mismatched parameters in the device handle, block these.
if ((connected_controllers[npad_index].type == NPadControllerType::JoyLeft &&
(vibration_device_handle.npad_type == NpadType::JoyconRight ||
vibration_device_handle.device_index == DeviceIndex::Right)) ||
(connected_controllers[npad_index].type == NPadControllerType::JoyRight &&
(vibration_device_handle.npad_type == NpadType::JoyconLeft ||
vibration_device_handle.device_index == DeviceIndex::Left))) {
return;
}
// Filter out vibrations with equivalent values to reduce unnecessary state changes.
if (vibration_value.amp_low == latest_vibration_values[npad_index][device_index].amp_low &&
vibration_value.amp_high == latest_vibration_values[npad_index][device_index].amp_high) {
return;
}
if (VibrateControllerAtIndex(npad_index, device_index, vibration_value)) {
latest_vibration_values[npad_index][device_index] = vibration_value;
}
}
Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
return last_processed_vibration;
void Controller_NPad::VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
const std::vector<VibrationValue>& vibration_values) {
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
return;
}
ASSERT_OR_EXECUTE_MSG(
vibration_device_handles.size() == vibration_values.size(), { return; },
"The amount of device handles does not match with the amount of vibration values,"
"this is undefined behavior!");
for (std::size_t i = 0; i < vibration_device_handles.size(); ++i) {
VibrateController(vibration_device_handles[i], vibration_values[i]);
}
}
Controller_NPad::VibrationValue Controller_NPad::GetLastVibration(
const DeviceHandle& vibration_device_handle) const {
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
return latest_vibration_values[npad_index][device_index];
}
void Controller_NPad::InitializeVibrationDevice(const DeviceHandle& vibration_device_handle) {
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
InitializeVibrationDeviceAtIndex(npad_index, device_index);
}
void Controller_NPad::InitializeVibrationDeviceAtIndex(std::size_t npad_index,
std::size_t device_index) {
if (vibrations[npad_index][device_index]) {
vibration_devices_mounted[npad_index][device_index] =
vibrations[npad_index][device_index]->GetStatus() == 1;
} else {
vibration_devices_mounted[npad_index][device_index] = false;
}
}
void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
permit_vibration_session_enabled = permit_vibration_session;
}
bool Controller_NPad::IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const {
const auto npad_index = NPadIdToIndex(vibration_device_handle.npad_id);
const auto device_index = static_cast<std::size_t>(vibration_device_handle.device_index);
return vibration_devices_mounted[npad_index][device_index];
}
std::shared_ptr<Kernel::ReadableEvent> Controller_NPad::GetStyleSetChangedEvent(u32 npad_id) const {
@ -696,31 +837,38 @@ void Controller_NPad::AddNewControllerAt(NPadControllerType controller, std::siz
void Controller_NPad::UpdateControllerAt(NPadControllerType controller, std::size_t npad_index,
bool connected) {
if (!connected) {
DisconnectNPadAtIndex(npad_index);
DisconnectNpadAtIndex(npad_index);
return;
}
if (controller == NPadControllerType::Handheld) {
Settings::values.players[HANDHELD_INDEX].controller_type =
Settings::values.players.GetValue()[HANDHELD_INDEX].controller_type =
MapNPadToSettingsType(controller);
Settings::values.players[HANDHELD_INDEX].connected = true;
Settings::values.players.GetValue()[HANDHELD_INDEX].connected = true;
connected_controllers[HANDHELD_INDEX] = {controller, true};
InitNewlyAddedController(HANDHELD_INDEX);
return;
}
Settings::values.players[npad_index].controller_type = MapNPadToSettingsType(controller);
Settings::values.players[npad_index].connected = true;
Settings::values.players.GetValue()[npad_index].controller_type =
MapNPadToSettingsType(controller);
Settings::values.players.GetValue()[npad_index].connected = true;
connected_controllers[npad_index] = {controller, true};
InitNewlyAddedController(npad_index);
}
void Controller_NPad::DisconnectNPad(u32 npad_id) {
DisconnectNPadAtIndex(NPadIdToIndex(npad_id));
void Controller_NPad::DisconnectNpad(u32 npad_id) {
DisconnectNpadAtIndex(NPadIdToIndex(npad_id));
}
void Controller_NPad::DisconnectNPadAtIndex(std::size_t npad_index) {
Settings::values.players[npad_index].connected = false;
void Controller_NPad::DisconnectNpadAtIndex(std::size_t npad_index) {
for (std::size_t device_idx = 0; device_idx < vibrations[npad_index].size(); ++device_idx) {
// Send an empty vibration to stop any vibrations.
VibrateControllerAtIndex(npad_index, device_idx, {});
vibration_devices_mounted[npad_index][device_idx] = false;
}
Settings::values.players.GetValue()[npad_index].connected = false;
connected_controllers[npad_index].is_connected = false;
auto& controller = shared_memory_entries[npad_index];
@ -758,7 +906,7 @@ void Controller_NPad::MergeSingleJoyAsDualJoy(u32 npad_id_1, u32 npad_id_2) {
(connected_controllers[npad_index_2].type == NPadControllerType::JoyLeft &&
connected_controllers[npad_index_1].type == NPadControllerType::JoyRight)) {
// Disconnect the joycon at the second id and connect the dual joycon at the first index.
DisconnectNPad(npad_id_2);
DisconnectNpad(npad_id_2);
AddNewControllerAt(NPadControllerType::JoyDual, npad_index_1);
}
}
@ -830,14 +978,6 @@ void Controller_NPad::SetUnintendedHomeButtonInputProtectionEnabled(bool is_prot
unintended_home_button_input_protection[NPadIdToIndex(npad_id)] = is_protection_enabled;
}
void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
can_controllers_vibrate = can_vibrate;
}
bool Controller_NPad::IsVibrationEnabled() const {
return can_controllers_vibrate;
}
void Controller_NPad::ClearAllConnectedControllers() {
for (auto& controller : connected_controllers) {
if (controller.is_connected && controller.type != NPadControllerType::None) {
@ -882,7 +1022,7 @@ bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const
return false;
}
// Handheld should not be supported in docked mode
if (Settings::values.use_docked_mode) {
if (Settings::values.use_docked_mode.GetValue()) {
return false;
}

View file

@ -39,28 +39,30 @@ public:
// Called when input devices should be loaded
void OnLoadInputDevices() override;
struct NPadType {
union {
u32_le raw{};
BitField<0, 1, u32> pro_controller;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
};
enum class NPadControllerType {
None,
ProController,
Handheld,
JoyDual,
JoyLeft,
JoyRight,
Pokeball,
};
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
struct Vibration {
f32 amp_low;
f32 freq_low;
f32 amp_high;
f32 freq_high;
enum class NpadType : u8 {
ProController = 3,
Handheld = 4,
JoyconDual = 5,
JoyconLeft = 6,
JoyconRight = 7,
Pokeball = 9,
};
enum class DeviceIndex : u8 {
Left = 0,
Right = 1,
None = 2,
};
static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
enum class GyroscopeZeroDriftMode : u32 {
Loose = 0,
@ -73,7 +75,7 @@ public:
Horizontal = 1,
};
enum class NPadAssignments : u32_le {
enum class NpadAssignments : u32 {
Dual = 0,
Single = 1,
};
@ -84,15 +86,36 @@ public:
None = 2,
};
enum class NPadControllerType {
None,
ProController,
Handheld,
JoyDual,
JoyLeft,
JoyRight,
Pokeball,
struct DeviceHandle {
NpadType npad_type{};
u8 npad_id{};
DeviceIndex device_index{};
INSERT_PADDING_BYTES(1);
};
static_assert(sizeof(DeviceHandle) == 4, "DeviceHandle is an invalid size");
struct NpadStyleSet {
union {
u32_le raw{};
BitField<0, 1, u32> pro_controller;
BitField<1, 1, u32> handheld;
BitField<2, 1, u32> joycon_dual;
BitField<3, 1, u32> joycon_left;
BitField<4, 1, u32> joycon_right;
BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
};
};
static_assert(sizeof(NpadStyleSet) == 4, "NpadStyleSet is an invalid size");
struct VibrationValue {
f32 amp_low{0.0f};
f32 freq_low{160.0f};
f32 amp_high{0.0f};
f32 freq_high{320.0f};
};
static_assert(sizeof(VibrationValue) == 0x10, "Vibration is an invalid size");
struct LedPattern {
explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
@ -110,12 +133,12 @@ public:
};
};
void SetSupportedStyleSet(NPadType style_set);
NPadType GetSupportedStyleSet() const;
void SetSupportedStyleSet(NpadStyleSet style_set);
NpadStyleSet GetSupportedStyleSet() const;
void SetSupportedNPadIdTypes(u8* data, std::size_t length);
void SetSupportedNpadIdTypes(u8* data, std::size_t length);
void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
std::size_t GetSupportedNPadIdTypesSize() const;
std::size_t GetSupportedNpadIdTypesSize() const;
void SetHoldType(NpadHoldType joy_hold_type);
NpadHoldType GetHoldType() const;
@ -123,12 +146,26 @@ public:
void SetNpadHandheldActivationMode(NpadHandheldActivationMode activation_mode);
NpadHandheldActivationMode GetNpadHandheldActivationMode() const;
void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
void SetNpadMode(u32 npad_id, NpadAssignments assignment_mode);
void VibrateController(const std::vector<u32>& controllers,
const std::vector<Vibration>& vibrations);
bool VibrateControllerAtIndex(std::size_t npad_index, std::size_t device_index,
const VibrationValue& vibration_value);
Vibration GetLastVibration() const;
void VibrateController(const DeviceHandle& vibration_device_handle,
const VibrationValue& vibration_value);
void VibrateControllers(const std::vector<DeviceHandle>& vibration_device_handles,
const std::vector<VibrationValue>& vibration_values);
VibrationValue GetLastVibration(const DeviceHandle& vibration_device_handle) const;
void InitializeVibrationDevice(const DeviceHandle& vibration_device_handle);
void InitializeVibrationDeviceAtIndex(std::size_t npad_index, std::size_t device_index);
void SetPermitVibrationSession(bool permit_vibration_session);
bool IsVibrationDeviceMounted(const DeviceHandle& vibration_device_handle) const;
std::shared_ptr<Kernel::ReadableEvent> GetStyleSetChangedEvent(u32 npad_id) const;
void SignalStyleSetChangedEvent(u32 npad_id) const;
@ -138,8 +175,8 @@ public:
// Adds a new controller at an index with connection status.
void UpdateControllerAt(NPadControllerType controller, std::size_t npad_index, bool connected);
void DisconnectNPad(u32 npad_id);
void DisconnectNPadAtIndex(std::size_t index);
void DisconnectNpad(u32 npad_id);
void DisconnectNpadAtIndex(std::size_t index);
void SetGyroscopeZeroDriftMode(GyroscopeZeroDriftMode drift_mode);
GyroscopeZeroDriftMode GetGyroscopeZeroDriftMode() const;
@ -148,8 +185,6 @@ public:
LedPattern GetLedPattern(u32 npad_id);
bool IsUnintendedHomeButtonInputProtectionEnabled(u32 npad_id) const;
void SetUnintendedHomeButtonInputProtectionEnabled(bool is_protection_enabled, u32 npad_id);
void SetVibrationEnabled(bool can_vibrate);
bool IsVibrationEnabled() const;
void ClearAllConnectedControllers();
void DisconnectAllConnectedControllers();
void ConnectAllDisconnectedControllers();
@ -324,8 +359,8 @@ private:
};
struct NPadEntry {
NPadType joy_styles;
NPadAssignments pad_assignment;
NpadStyleSet joy_styles;
NpadAssignments pad_assignment;
ColorReadError single_color_error;
ControllerColor single_color;
@ -368,7 +403,7 @@ private:
u32 press_state{};
NPadType style{};
NpadStyleSet style{};
std::array<NPadEntry, 10> shared_memory_entries{};
using ButtonArray = std::array<
std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>,
@ -376,22 +411,28 @@ private:
using StickArray = std::array<
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>,
10>;
using VibrationArray = std::array<std::array<std::unique_ptr<Input::VibrationDevice>,
Settings::NativeVibration::NUM_VIBRATIONS_HID>,
10>;
using MotionArray = std::array<
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTION_HID>,
std::array<std::unique_ptr<Input::MotionDevice>, Settings::NativeMotion::NUM_MOTIONS_HID>,
10>;
ButtonArray buttons;
StickArray sticks;
VibrationArray vibrations;
MotionArray motions;
std::vector<u32> supported_npad_id_types{};
NpadHoldType hold_type{NpadHoldType::Vertical};
NpadHandheldActivationMode handheld_activation_mode{NpadHandheldActivationMode::Dual};
// Each controller should have their own styleset changed event
std::array<Kernel::EventPair, 10> styleset_changed_events;
Vibration last_processed_vibration{};
std::array<std::array<std::chrono::steady_clock::time_point, 2>, 10> last_vibration_timepoints;
std::array<std::array<VibrationValue, 2>, 10> latest_vibration_values{};
bool permit_vibration_session_enabled{false};
std::array<std::array<bool, 2>, 10> vibration_devices_mounted{};
std::array<ControllerHolder, 10> connected_controllers{};
std::array<bool, 10> unintended_home_button_input_protection{};
GyroscopeZeroDriftMode gyroscope_zero_drift_mode{GyroscopeZeroDriftMode::Standard};
bool can_controllers_vibrate{true};
bool sixaxis_sensors_enabled{true};
bool sixaxis_at_rest{true};
std::array<ControllerPad, 10> npad_pad_states{};

File diff suppressed because it is too large Load diff

View file

@ -86,17 +86,15 @@ public:
private:
void CreateAppletResource(Kernel::HLERequestContext& ctx);
void ActivateXpad(Kernel::HLERequestContext& ctx);
void GetXpadIDs(Kernel::HLERequestContext& ctx);
void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void ActivateDebugPad(Kernel::HLERequestContext& ctx);
void ActivateTouchScreen(Kernel::HLERequestContext& ctx);
void ActivateMouse(Kernel::HLERequestContext& ctx);
void ActivateKeyboard(Kernel::HLERequestContext& ctx);
void SendKeyboardLockKeyEvent(Kernel::HLERequestContext& ctx);
void ActivateGesture(Kernel::HLERequestContext& ctx);
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
void ActivateXpad(Kernel::HLERequestContext& ctx);
void GetXpadIDs(Kernel::HLERequestContext& ctx);
void ActivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void DeactivateSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
void EnableSixAxisSensorFusion(Kernel::HLERequestContext& ctx);
@ -104,6 +102,7 @@ private:
void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
void ActivateGesture(Kernel::HLERequestContext& ctx);
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx);
@ -112,6 +111,7 @@ private:
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx);
void DisconnectNpad(Kernel::HLERequestContext& ctx);
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx);
void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx);
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx);
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx);
@ -125,15 +125,16 @@ private:
void SwapNpadAssignment(Kernel::HLERequestContext& ctx);
void IsUnintendedHomeButtonInputProtectionEnabled(Kernel::HLERequestContext& ctx);
void EnableUnintendedHomeButtonInputProtection(Kernel::HLERequestContext& ctx);
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
void SendVibrationValue(Kernel::HLERequestContext& ctx);
void SendVibrationValues(Kernel::HLERequestContext& ctx);
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
void SendVibrationValue(Kernel::HLERequestContext& ctx);
void GetActualVibrationValue(Kernel::HLERequestContext& ctx);
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
void PermitVibration(Kernel::HLERequestContext& ctx);
void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
void SendVibrationValues(Kernel::HLERequestContext& ctx);
void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx);
void EndPermitVibrationSession(Kernel::HLERequestContext& ctx);
void IsVibrationDeviceMounted(Kernel::HLERequestContext& ctx);
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
void StopConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
@ -146,6 +147,22 @@ private:
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
enum class VibrationDeviceType : u32 {
LinearResonantActuator = 1,
};
enum class VibrationDevicePosition : u32 {
None = 0,
Left = 1,
Right = 2,
};
struct VibrationDeviceInfo {
VibrationDeviceType type{};
VibrationDevicePosition position{};
};
static_assert(sizeof(VibrationDeviceInfo) == 0x8, "VibrationDeviceInfo has incorrect size.");
std::shared_ptr<IAppletResource> applet_resource;
Core::System& system;
};

View file

@ -771,7 +771,7 @@ private:
IPC::ResponseBuilder rb{ctx, 6};
rb.Push(RESULT_SUCCESS);
if (Settings::values.use_docked_mode) {
if (Settings::values.use_docked_mode.GetValue()) {
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth) *
static_cast<u32>(Settings::values.resolution_factor.GetValue()));
rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight) *

View file

@ -49,7 +49,7 @@ void LogSettings() {
};
LOG_INFO(Config, "yuzu Configuration:");
log_setting("Controls_UseDockedMode", values.use_docked_mode);
log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
log_setting("System_CurrentUser", values.current_user);
log_setting("System_LanguageIndex", values.language_index.GetValue());
@ -145,6 +145,12 @@ void RestoreGlobalState() {
values.rng_seed.SetGlobal(true);
values.custom_rtc.SetGlobal(true);
values.sound_index.SetGlobal(true);
// Controls
values.players.SetGlobal(true);
values.use_docked_mode.SetGlobal(true);
values.vibration_enabled.SetGlobal(true);
values.motion_enabled.SetGlobal(true);
}
void Sanitize() {

View file

@ -65,6 +65,38 @@ private:
Type local{};
};
/**
* The InputSetting class allows for getting a reference to either the global or local members.
* This is required as we cannot easily modify the values of user-defined types within containers
* using the SetValue() member function found in the Setting class. The primary purpose of this
* class is to store an array of 10 PlayerInput structs for both the global and local (per-game)
* setting and allows for easily accessing and modifying both settings.
*/
template <typename Type>
class InputSetting final {
public:
InputSetting() = default;
explicit InputSetting(Type val) : global{val} {}
~InputSetting() = default;
void SetGlobal(bool to_global) {
use_global = to_global;
}
bool UsingGlobal() const {
return use_global;
}
Type& GetValue(bool need_global = false) {
if (use_global || need_global) {
return global;
}
return local;
}
private:
bool use_global = true;
Type global{};
Type local{};
};
struct TouchFromButtonMap {
std::string name;
std::vector<std::string> buttons;
@ -133,9 +165,18 @@ struct Values {
Setting<s32> sound_index;
// Controls
std::array<PlayerInput, 10> players;
InputSetting<std::array<PlayerInput, 10>> players;
bool use_docked_mode;
Setting<bool> use_docked_mode;
Setting<bool> vibration_enabled;
Setting<bool> enable_accurate_vibrations;
Setting<bool> motion_enabled;
std::string motion_device;
std::string udp_input_address;
u16 udp_input_port;
u8 udp_pad_index;
bool mouse_enabled;
std::string mouse_device;
@ -149,20 +190,15 @@ struct Values {
ButtonsRaw debug_pad_buttons;
AnalogsRaw debug_pad_analogs;
bool vibration_enabled;
bool motion_enabled;
std::string motion_device;
std::string touch_device;
TouchscreenInput touchscreen;
std::atomic_bool is_device_reload_pending{true};
bool use_touch_from_button;
std::string touch_device;
int touch_from_button_map_index;
std::string udp_input_address;
u16 udp_input_port;
u8 udp_pad_index;
std::vector<TouchFromButtonMap> touch_from_button_maps;
std::atomic_bool is_device_reload_pending{true};
// Data Storage
bool use_virtual_sd;
bool gamecard_inserted;

View file

@ -213,7 +213,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
Settings::values.use_assembly_shaders.GetValue());
AddField(field_type, "Renderer_UseAsynchronousShaders",
Settings::values.use_asynchronous_shaders.GetValue());
AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue());
}
bool TelemetrySession::SubmitTestcase() {

View file

@ -230,10 +230,8 @@ void Adapter::SendVibrations() {
vibration_changed = false;
}
bool Adapter::RumblePlay(std::size_t port, f32 amplitude) {
amplitude = std::clamp(amplitude, 0.0f, 1.0f);
const auto raw_amp = static_cast<u8>(amplitude * 0x8);
pads[port].rumble_amplitude = raw_amp;
bool Adapter::RumblePlay(std::size_t port, u8 amplitude) {
pads[port].rumble_amplitude = amplitude;
return rumble_enabled;
}

View file

@ -77,8 +77,8 @@ public:
Adapter();
~Adapter();
/// Request a vibration for a controlelr
bool RumblePlay(std::size_t port, f32 amplitude);
/// Request a vibration for a controller
bool RumblePlay(std::size_t port, u8 amplitude);
/// Used for polling
void BeginConfiguration();

View file

@ -15,7 +15,7 @@ namespace InputCommon {
class GCButton final : public Input::ButtonDevice {
public:
explicit GCButton(u32 port_, s32 button_, GCAdapter::Adapter* adapter)
explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter)
: port(port_), button(button_), gcadapter(adapter) {}
~GCButton() override;
@ -27,18 +27,10 @@ public:
return false;
}
bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
const float amplitude = amp_high + amp_low > 2.0f ? 1.0f : (amp_high + amp_low) * 0.5f;
const auto new_amp =
static_cast<f32>(pow(amplitude, 0.5f) * (3.0f - 2.0f * pow(amplitude, 0.15f)));
return gcadapter->RumblePlay(port, new_amp);
}
private:
const u32 port;
const s32 button;
GCAdapter::Adapter* gcadapter;
const GCAdapter::Adapter* gcadapter;
};
class GCAxisButton final : public Input::ButtonDevice {
@ -299,4 +291,42 @@ Common::ParamPackage GCAnalogFactory::GetNextInput() {
return params;
}
class GCVibration final : public Input::VibrationDevice {
public:
explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter)
: port(port_), gcadapter(adapter) {}
u8 GetStatus() const override {
return gcadapter->RumblePlay(port, 0);
}
bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override {
const auto mean_amplitude = (amp_low + amp_high) * 0.5f;
const auto processed_amplitude = static_cast<u8>(
pow(mean_amplitude, 0.5f) * (3.0f - 2.0f * pow(mean_amplitude, 0.15f)) * 0x8);
return gcadapter->RumblePlay(port, processed_amplitude);
}
private:
const u32 port;
GCAdapter::Adapter* gcadapter;
};
/// An vibration device factory that creates vibration devices from GC Adapter
GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_)
: adapter(std::move(adapter_)) {}
/**
* Creates a vibration device from a joystick
* @param params contains parameters for creating the device:
* - "port": the nth gcpad on the adapter
*/
std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create(
const Common::ParamPackage& params) {
const auto port = static_cast<u32>(params.Get("port", 0));
return std::make_unique<GCVibration>(port, adapter.get());
}
} // namespace InputCommon

View file

@ -64,4 +64,15 @@ private:
bool polling = false;
};
/// A vibration device factory creates vibration devices from GC Adapter
class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
public:
explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_);
std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override;
private:
std::shared_ptr<GCAdapter::Adapter> adapter;
};
} // namespace InputCommon

View file

@ -28,6 +28,8 @@ struct InputSubsystem::Impl {
Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons);
gcanalog = std::make_shared<GCAnalogFactory>(gcadapter);
Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog);
gcvibration = std::make_shared<GCVibrationFactory>(gcadapter);
Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration);
keyboard = std::make_shared<Keyboard>();
Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard);
@ -64,9 +66,11 @@ struct InputSubsystem::Impl {
#endif
Input::UnregisterFactory<Input::ButtonDevice>("gcpad");
Input::UnregisterFactory<Input::AnalogDevice>("gcpad");
Input::UnregisterFactory<Input::VibrationDevice>("gcpad");
gcbuttons.reset();
gcanalog.reset();
gcvibration.reset();
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
@ -78,7 +82,7 @@ struct InputSubsystem::Impl {
[[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
std::vector<Common::ParamPackage> devices = {
Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}},
Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
};
#ifdef HAVE_SDL2
auto sdl_devices = sdl->GetInputDevices();
@ -96,10 +100,6 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") {
return {};
}
if (params.Get("class", "") == "key") {
// TODO consider returning the SDL key codes for the default keybindings
return {};
}
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetAnalogMappingForDevice(params);
}
@ -116,10 +116,6 @@ struct InputSubsystem::Impl {
if (!params.Has("class") || params.Get("class", "") == "any") {
return {};
}
if (params.Get("class", "") == "key") {
// TODO consider returning the SDL key codes for the default keybindings
return {};
}
if (params.Get("class", "") == "gcpad") {
return gcadapter->GetButtonMappingForDevice(params);
}
@ -150,6 +146,7 @@ struct InputSubsystem::Impl {
#endif
std::shared_ptr<GCButtonFactory> gcbuttons;
std::shared_ptr<GCAnalogFactory> gcanalog;
std::shared_ptr<GCVibrationFactory> gcvibration;
std::shared_ptr<UDPMotionFactory> udpmotion;
std::shared_ptr<UDPTouchFactory> udptouch;
std::shared_ptr<CemuhookUDP::Client> udp;

View file

@ -80,30 +80,13 @@ public:
return static_cast<float>(state.axes.at(axis)) / (32767.0f * range);
}
bool RumblePlay(f32 amp_low, f32 amp_high, u32 time) {
const u16 raw_amp_low = static_cast<u16>(amp_low * 0xFFFF);
const u16 raw_amp_high = static_cast<u16>(amp_high * 0xFFFF);
// Lower drastically the number of state changes
if (raw_amp_low >> 11 == last_state_rumble_low >> 11 &&
raw_amp_high >> 11 == last_state_rumble_high >> 11) {
if (raw_amp_low + raw_amp_high != 0 ||
last_state_rumble_low + last_state_rumble_high == 0) {
return false;
}
}
// Don't change state if last vibration was < 20ms
const auto now = std::chrono::system_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_vibration) <
std::chrono::milliseconds(20)) {
return raw_amp_low + raw_amp_high == 0;
bool RumblePlay(u16 amp_low, u16 amp_high) {
if (sdl_controller) {
return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, 0) == 0;
} else if (sdl_joystick) {
return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, 0) == 0;
}
last_vibration = now;
last_state_rumble_low = raw_amp_low;
last_state_rumble_high = raw_amp_high;
if (sdl_joystick) {
SDL_JoystickRumble(sdl_joystick.get(), raw_amp_low, raw_amp_high, time);
}
return false;
}
@ -172,9 +155,6 @@ private:
} state;
std::string guid;
int port;
u16 last_state_rumble_high = 0;
u16 last_state_rumble_low = 0;
std::chrono::time_point<std::chrono::system_clock> last_vibration;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
mutable std::mutex mutex;
@ -327,12 +307,6 @@ public:
return joystick->GetButton(button);
}
bool SetRumblePlay(f32 amp_high, f32 amp_low, f32 freq_high, f32 freq_low) const override {
const f32 new_amp_low = pow(amp_low, 0.5f) * (3.0f - 2.0f * pow(amp_low, 0.15f));
const f32 new_amp_high = pow(amp_high, 0.5f) * (3.0f - 2.0f * pow(amp_high, 0.15f));
return joystick->RumblePlay(new_amp_low, new_amp_high, 250);
}
private:
std::shared_ptr<SDLJoystick> joystick;
int button;
@ -416,6 +390,32 @@ private:
const float range;
};
class SDLVibration final : public Input::VibrationDevice {
public:
explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_)
: joystick(std::move(joystick_)) {}
u8 GetStatus() const override {
joystick->RumblePlay(1, 1);
return joystick->RumblePlay(0, 0);
}
bool SetRumblePlay(f32 amp_low, f32 freq_low, f32 amp_high, f32 freq_high) const override {
const auto process_amplitude = [](f32 amplitude) {
return static_cast<u16>(std::pow(amplitude, 0.5f) *
(3.0f - 2.0f * std::pow(amplitude, 0.15f)) * 0xFFFF);
};
const auto processed_amp_low = process_amplitude(amp_low);
const auto processed_amp_high = process_amplitude(amp_high);
return joystick->RumblePlay(processed_amp_low, processed_amp_high);
}
private:
std::shared_ptr<SDLJoystick> joystick;
};
class SDLDirectionMotion final : public Input::MotionDevice {
public:
explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
@ -558,7 +558,7 @@ class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
public:
explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
/**
* Creates analog device from joystick axes
* Creates an analog device from joystick axes
* @param params contains parameters for creating the device:
* - "guid": the guid of the joystick to bind
* - "port": the nth joystick of the same type
@ -584,6 +584,26 @@ private:
SDLState& state;
};
/// An vibration device factory that creates vibration devices from SDL joystick
class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> {
public:
explicit SDLVibrationFactory(SDLState& state_) : state(state_) {}
/**
* Creates a vibration device from a joystick
* @param params contains parameters for creating the device:
* - "guid": the guid of the joystick to bind
* - "port": the nth joystick of the same type
*/
std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override {
const std::string guid = params.Get("guid", "0");
const int port = params.Get("port", 0);
return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port));
}
private:
SDLState& state;
};
/// A motion device factory that creates motion devices from SDL joystick
class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
@ -650,11 +670,13 @@ private:
SDLState::SDLState() {
using namespace Input;
analog_factory = std::make_shared<SDLAnalogFactory>(*this);
button_factory = std::make_shared<SDLButtonFactory>(*this);
analog_factory = std::make_shared<SDLAnalogFactory>(*this);
vibration_factory = std::make_shared<SDLVibrationFactory>(*this);
motion_factory = std::make_shared<SDLMotionFactory>(*this);
RegisterFactory<AnalogDevice>("sdl", analog_factory);
RegisterFactory<ButtonDevice>("sdl", button_factory);
RegisterFactory<AnalogDevice>("sdl", analog_factory);
RegisterFactory<VibrationDevice>("sdl", vibration_factory);
RegisterFactory<MotionDevice>("sdl", motion_factory);
// If the frontend is going to manage the event loop, then we don't start one here
@ -676,7 +698,7 @@ SDLState::SDLState() {
using namespace std::chrono_literals;
while (initialized) {
SDL_PumpEvents();
std::this_thread::sleep_for(5ms);
std::this_thread::sleep_for(1ms);
}
});
}
@ -691,6 +713,7 @@ SDLState::~SDLState() {
using namespace Input;
UnregisterFactory<ButtonDevice>("sdl");
UnregisterFactory<AnalogDevice>("sdl");
UnregisterFactory<VibrationDevice>("sdl");
UnregisterFactory<MotionDevice>("sdl");
CloseJoysticks();
@ -1045,7 +1068,6 @@ public:
void Start(const std::string& device_id) override {
SDLPoller::Start(device_id);
// Load the game controller
// Reset stored axes
analog_x_axis = -1;
analog_y_axis = -1;
@ -1058,40 +1080,21 @@ public:
if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) {
continue;
}
// Simplify controller config by testing if game controller support is enabled.
if (event.type == SDL_JOYAXISMOTION) {
const auto axis = event.jaxis.axis;
if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
auto* const controller = joystick->GetSDLGameController()) {
const auto axis_left_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX)
.value.axis;
const auto axis_left_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY)
.value.axis;
const auto axis_right_x =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX)
.value.axis;
const auto axis_right_y =
SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY)
.value.axis;
if (axis == axis_left_x || axis == axis_left_y) {
analog_x_axis = axis_left_x;
analog_y_axis = axis_left_y;
break;
} else if (axis == axis_right_x || axis == axis_right_y) {
analog_x_axis = axis_right_x;
analog_y_axis = axis_right_y;
break;
}
// In order to return a complete analog param, we need inputs for both axes.
// First we take the x-axis (horizontal) input, then the y-axis (vertical) input.
if (analog_x_axis == -1) {
analog_x_axis = axis;
} else if (analog_y_axis == -1 && analog_x_axis != axis) {
analog_y_axis = axis;
}
} else {
// If the press wasn't accepted as a joy axis, check for a button press
auto button_press = button_poller.FromEvent(event);
if (button_press) {
return *button_press;
}
}
// If the press wasn't accepted as a joy axis, check for a button press
auto button_press = button_poller.FromEvent(event);
if (button_press) {
return *button_press;
}
}
@ -1104,6 +1107,7 @@ public:
return params;
}
}
return {};
}

View file

@ -22,6 +22,7 @@ namespace InputCommon::SDL {
class SDLAnalogFactory;
class SDLButtonFactory;
class SDLMotionFactory;
class SDLVibrationFactory;
class SDLJoystick;
class SDLState : public State {
@ -72,6 +73,7 @@ private:
std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory;
std::shared_ptr<SDLVibrationFactory> vibration_factory;
std::shared_ptr<SDLMotionFactory> motion_factory;
bool start_thread = false;

View file

@ -14,13 +14,6 @@ const std::array<const char*, NumButtons> mapping = {{
}};
}
namespace NativeMotion {
const std::array<const char*, NumMotions> mapping = {{
"motionleft",
"motionright",
}};
}
namespace NativeAnalog {
const std::array<const char*, NumAnalogs> mapping = {{
"lstick",
@ -28,6 +21,20 @@ const std::array<const char*, NumAnalogs> mapping = {{
}};
}
namespace NativeVibration {
const std::array<const char*, NumVibrations> mapping = {{
"left_vibration_device",
"right_vibration_device",
}};
}
namespace NativeMotion {
const std::array<const char*, NumMotions> mapping = {{
"motionleft",
"motionright",
}};
}
namespace NativeMouseButton {
const std::array<const char*, NumMouseButtons> mapping = {{
"left",

View file

@ -66,17 +66,32 @@ constexpr int NUM_STICKS_HID = NumAnalogs;
extern const std::array<const char*, NumAnalogs> mapping;
} // namespace NativeAnalog
namespace NativeVibration {
enum Values : int {
LeftVibrationDevice,
RightVibrationDevice,
NumVibrations,
};
constexpr int VIBRATION_HID_BEGIN = LeftVibrationDevice;
constexpr int VIBRATION_HID_END = NumVibrations;
constexpr int NUM_VIBRATIONS_HID = NumVibrations;
extern const std::array<const char*, NumVibrations> mapping;
}; // namespace NativeVibration
namespace NativeMotion {
enum Values : int {
MOTIONLEFT,
MOTIONRIGHT,
MotionLeft,
MotionRight,
NumMotions,
};
constexpr int MOTION_HID_BEGIN = MOTIONLEFT;
constexpr int MOTION_HID_BEGIN = MotionLeft;
constexpr int MOTION_HID_END = NumMotions;
constexpr int NUM_MOTION_HID = NumMotions;
constexpr int NUM_MOTIONS_HID = NumMotions;
extern const std::array<const char*, NumMotions> mapping;
} // namespace NativeMotion
@ -305,9 +320,11 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods;
} // namespace NativeKeyboard
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>;
using MotionRaw = std::array<std::string, NativeMotion::NumMotions>;
using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>;
using MotionsRaw = std::array<std::string, NativeMotion::NumMotions>;
using VibrationsRaw = std::array<std::string, NativeVibration::NumVibrations>;
using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>;
using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>;
using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>;
@ -330,7 +347,11 @@ struct PlayerInput {
ControllerType controller_type;
ButtonsRaw buttons;
AnalogsRaw analogs;
MotionRaw motions;
VibrationsRaw vibrations;
MotionsRaw motions;
bool vibration_enabled;
int vibration_strength;
u32 body_color_left;
u32 body_color_right;

View file

@ -344,7 +344,7 @@ void TestCommunication(const std::string& host, u16 port, std::size_t pad_index,
};
Socket socket{host, port, pad_index, client_id, std::move(callback)};
std::thread worker_thread{SocketLoop, &socket};
const bool result = success_event.WaitFor(std::chrono::seconds(8));
const bool result = success_event.WaitFor(std::chrono::seconds(5));
socket.Stop();
worker_thread.join();
if (result) {

View file

@ -68,12 +68,12 @@ add_executable(yuzu
configuration/configure_input_advanced.cpp
configuration/configure_input_advanced.h
configuration/configure_input_advanced.ui
configuration/configure_input_dialog.cpp
configuration/configure_input_dialog.h
configuration/configure_input_dialog.ui
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_input_player.ui
configuration/configure_input_profile_dialog.cpp
configuration/configure_input_profile_dialog.h
configuration/configure_input_profile_dialog.ui
configuration/configure_motion_touch.cpp
configuration/configure_motion_touch.h
configuration/configure_motion_touch.ui
@ -105,9 +105,14 @@ add_executable(yuzu
configuration/configure_ui.cpp
configuration/configure_ui.h
configuration/configure_ui.ui
configuration/configure_vibration.cpp
configuration/configure_vibration.h
configuration/configure_vibration.ui
configuration/configure_web.cpp
configuration/configure_web.h
configuration/configure_web.ui
configuration/input_profiles.cpp
configuration/input_profiles.h
debugger/console.cpp
debugger/console.h
debugger/profiler.cpp

View file

@ -160,32 +160,12 @@ p, li { white-space: pre-wrap; }
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <thread>
#include "common/assert.h"
#include "common/string_util.h"
@ -13,11 +14,16 @@
#include "core/hle/service/sm/sm.h"
#include "ui_controller.h"
#include "yuzu/applets/controller.h"
#include "yuzu/configuration/configure_input_dialog.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_profile_dialog.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h"
namespace {
constexpr std::size_t HANDHELD_INDEX = 8;
constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
{true, false, false, false},
{true, true, false, false},
@ -106,7 +112,8 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
QWidget* parent, Core::Frontend::ControllerParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_)
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
parameters(std::move(parameters_)), input_subsystem(input_subsystem_) {
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
input_profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
player_widgets = {
@ -223,12 +230,22 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
}
}
connect(ui->vibrationButton, &QPushButton::clicked, this,
&QtControllerSelectorDialog::CallConfigureVibrationDialog);
connect(ui->inputConfigButton, &QPushButton::clicked, this,
&QtControllerSelectorDialog::CallConfigureInputDialog);
&QtControllerSelectorDialog::CallConfigureInputProfileDialog);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&QtControllerSelectorDialog::ApplyConfiguration);
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
// If all the parameters are met AND only allows a single player,
// stop the constructor here as we do not need to continue.
if (CheckIfParametersMet() && parameters.enable_single_mode) {
return;
}
// If keep_controllers_connected is false, forcefully disconnect all controllers
if (!parameters.keep_controllers_connected) {
for (auto player : player_groupboxes) {
@ -236,58 +253,66 @@ QtControllerSelectorDialog::QtControllerSelectorDialog(
}
}
CheckIfParametersMet();
resize(0, 0);
}
QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
void QtControllerSelectorDialog::ApplyConfiguration() {
// Update the controller state once more, just to be sure they are properly applied.
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
UpdateControllerState(index);
int QtControllerSelectorDialog::exec() {
if (parameters_met && parameters.enable_single_mode) {
return QDialog::Accepted;
}
return QDialog::exec();
}
const bool pre_docked_mode = Settings::values.use_docked_mode;
Settings::values.use_docked_mode = ui->radioDocked->isChecked();
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
void QtControllerSelectorDialog::ApplyConfiguration() {
const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void QtControllerSelectorDialog::LoadConfiguration() {
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
const auto connected = Settings::values.players[index].connected ||
(index == 0 && Settings::values.players[8].connected);
const auto connected =
Settings::values.players.GetValue()[index].connected ||
(index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
player_groupboxes[index]->setChecked(connected);
connected_controller_checkboxes[index]->setChecked(connected);
emulated_controllers[index]->setCurrentIndex(
GetIndexFromControllerType(Settings::values.players[index].controller_type));
GetIndexFromControllerType(Settings::values.players.GetValue()[index].controller_type));
}
UpdateDockedState(Settings::values.players[8].connected);
UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
void QtControllerSelectorDialog::CallConfigureInputDialog() {
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
ConfigureVibration dialog(this);
ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Accepted) {
dialog.ApplyConfiguration();
}
}
void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get());
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
dialog.ApplyConfiguration();
LoadConfiguration();
CheckIfParametersMet();
}
void QtControllerSelectorDialog::CheckIfParametersMet() {
bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
@ -301,7 +326,7 @@ void QtControllerSelectorDialog::CheckIfParametersMet() {
num_connected_players > max_supported_players) {
parameters_met = false;
ui->buttonBox->setEnabled(parameters_met);
return;
return parameters_met;
}
// Next, check against all connected controllers.
@ -326,18 +351,13 @@ void QtControllerSelectorDialog::CheckIfParametersMet() {
return true;
}();
if (!all_controllers_compatible) {
parameters_met = false;
ui->buttonBox->setEnabled(parameters_met);
return;
}
parameters_met = true;
parameters_met = all_controllers_compatible;
ui->buttonBox->setEnabled(parameters_met);
return parameters_met;
}
void QtControllerSelectorDialog::SetSupportedControllers() {
const QString theme = [this] {
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@ -426,7 +446,7 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
}
}();
const QString theme = [this] {
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@ -441,32 +461,48 @@ void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index)
}
void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
auto& player = Settings::values.players[player_index];
auto& player = Settings::values.players.GetValue()[player_index];
player.controller_type =
const auto controller_type =
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
player.connected = player_groupboxes[player_index]->isChecked();
const auto player_connected = player_groupboxes[player_index]->isChecked() &&
controller_type != Settings::ControllerType::Handheld;
// Player 2-8
if (player_index != 0) {
UpdateController(player.controller_type, player_index, player.connected);
if (player.controller_type == controller_type && player.connected == player_connected) {
// Set vibration devices in the event that the input device has changed.
ConfigureVibration::SetVibrationDevices(player_index);
return;
}
// Player 1 and Handheld
auto& handheld = Settings::values.players[8];
// If Handheld is selected, copy all the settings from Player 1 to Handheld.
if (player.controller_type == Settings::ControllerType::Handheld) {
handheld = player;
handheld.connected = player_groupboxes[player_index]->isChecked();
player.connected = false; // Disconnect Player 1
} else {
player.connected = player_groupboxes[player_index]->isChecked();
handheld.connected = false; // Disconnect Handheld
// Disconnect the controller first.
UpdateController(controller_type, player_index, false);
player.controller_type = controller_type;
player.connected = player_connected;
ConfigureVibration::SetVibrationDevices(player_index);
// Handheld
if (player_index == 0) {
auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
if (controller_type == Settings::ControllerType::Handheld) {
handheld = player;
}
handheld.connected = player_groupboxes[player_index]->isChecked() &&
controller_type == Settings::ControllerType::Handheld;
UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
}
UpdateController(player.controller_type, player_index, player.connected);
UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
if (!player.connected) {
return;
}
// This emulates a delay between disconnecting and reconnecting controllers as some games
// do not respond to a change in controller type if it was instantaneous.
using namespace std::chrono_literals;
std::this_thread::sleep_for(20ms);
UpdateController(controller_type, player_index, player_connected);
}
void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
@ -520,8 +556,8 @@ void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
ui->radioDocked->setChecked(Settings::values.use_docked_mode);
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
@ -564,8 +600,8 @@ void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
// Disconnect any unsupported players here and disable or hide them if applicable.
Settings::values.players[index].connected = false;
UpdateController(Settings::values.players[index].controller_type, index, false);
Settings::values.players.GetValue()[index].connected = false;
UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false);
// Hide the player widgets when max_supported_controllers is less than or equal to 4.
if (max_supported_players <= 4) {
player_widgets[index]->hide();

View file

@ -16,6 +16,8 @@ class QDialogButtonBox;
class QGroupBox;
class QLabel;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
@ -33,6 +35,8 @@ public:
InputCommon::InputSubsystem* input_subsystem_);
~QtControllerSelectorDialog() override;
int exec() override;
private:
// Applies the current configuration.
void ApplyConfiguration();
@ -40,12 +44,15 @@ private:
// Loads the current input configuration into the frontend applet.
void LoadConfiguration();
// Initializes the "Configure Input" Dialog.
void CallConfigureInputDialog();
// Initializes the "Configure Vibration" Dialog.
void CallConfigureVibrationDialog();
// Checks the current configuration against the given parameters and
// sets the value of parameters_met.
void CheckIfParametersMet();
// Initializes the "Create Input Profile" Dialog.
void CallConfigureInputProfileDialog();
// Checks the current configuration against the given parameters.
// This sets and returns the value of parameters_met.
bool CheckIfParametersMet();
// Sets the controller icons for "Supported Controller Types".
void SetSupportedControllers();
@ -78,6 +85,8 @@ private:
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<InputProfiles> input_profiles;
// This is true if and only if all parameters are met. Otherwise, this is false.
// This determines whether the "OK" button can be clicked to exit the applet.
bool parameters_met{false};

View file

@ -1217,9 +1217,6 @@
</item>
<item>
<widget class="QComboBox" name="comboPlayer3Emulated">
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>Pro Controller</string>
@ -2279,7 +2276,7 @@
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
<number>8</number>
</property>
<property name="topMargin">
<number>6</number>
@ -2332,30 +2329,24 @@
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpin">
<widget class="QPushButton" name="vibrationButton">
<property name="minimumSize">
<size>
<width>65</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>65</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
@ -2387,18 +2378,18 @@
<widget class="QPushButton" name="motionButton">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
@ -2411,7 +2402,7 @@
<item>
<widget class="QGroupBox" name="inputConfigGroup">
<property name="title">
<string>Input Config</string>
<string>Profiles</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="leftMargin">
@ -2430,15 +2421,15 @@
<widget class="QPushButton" name="inputConfigButton">
<property name="maximumSize">
<size>
<width>65</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Open</string>
<string>Create</string>
</property>
</widget>
</item>
@ -2657,16 +2648,6 @@
<signal>accepted()</signal>
<receiver>QtControllerSelectorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -382,7 +382,12 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
// touch input is handled in TouchBeginEvent
if (!Settings::values.touchscreen.enabled) {
input_subsystem->GetKeyboard()->PressKey(event->button());
return;
}
// Touch input is handled in TouchBeginEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
@ -398,7 +403,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
// touch input is handled in TouchUpdateEvent
// Touch input is handled in TouchUpdateEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
@ -411,7 +416,12 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
// touch input is handled in TouchEndEvent
if (!Settings::values.touchscreen.enabled) {
input_subsystem->GetKeyboard()->ReleaseKey(event->button());
return;
}
// Touch input is handled in TouchEndEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}

View file

@ -5,6 +5,7 @@
#include <array>
#include <QKeySequence>
#include <QSettings>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
@ -14,14 +15,10 @@
namespace FS = Common::FS;
Config::Config(const std::string& config_file, bool is_global) {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
qt_config_loc = FS::GetUserPath(FS::UserPath::ConfigDir) + config_file;
FS::CreateFullPath(qt_config_loc);
qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
global = is_global;
Reload();
Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) {
global = config_type == ConfigType::GlobalConfig;
Initialize(config_name);
}
Config::~Config() {
@ -242,84 +239,152 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
}};
// clang-format on
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p];
void Config::Initialize(const std::string& config_name) {
switch (type) {
case ConfigType::GlobalConfig:
qt_config_loc = fmt::format("{}" DIR_SEP "{}.ini", FS::GetUserPath(FS::UserPath::ConfigDir),
config_name);
FS::CreateFullPath(qt_config_loc);
qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
QSettings::IniFormat);
Reload();
break;
case ConfigType::PerGameConfig:
qt_config_loc = fmt::format("{}custom" DIR_SEP "{}.ini",
FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
FS::CreateFullPath(qt_config_loc);
qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
QSettings::IniFormat);
Reload();
break;
case ConfigType::InputProfile:
qt_config_loc = fmt::format("{}input" DIR_SEP "{}.ini",
FS::GetUserPath(FS::UserPath::ConfigDir), config_name);
FS::CreateFullPath(qt_config_loc);
qt_config = std::make_unique<QSettings>(QString::fromStdString(qt_config_loc),
QSettings::IniFormat);
break;
}
}
player.connected =
ReadSetting(QStringLiteral("player_%1_connected").arg(p), false).toBool();
void Config::ReadPlayerValue(std::size_t player_index) {
const QString player_prefix = [this, player_index] {
if (type == ConfigType::InputProfile) {
return QString{};
} else {
return QStringLiteral("player_%1_").arg(player_index);
}
}();
player.controller_type = static_cast<Settings::ControllerType>(
auto& player = Settings::values.players.GetValue()[player_index];
if (player_prefix.isEmpty()) {
const auto controller = static_cast<Settings::ControllerType>(
qt_config
->value(QStringLiteral("player_%1_type").arg(p),
->value(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
if (controller == Settings::ControllerType::LeftJoycon ||
controller == Settings::ControllerType::RightJoycon) {
player.controller_type = controller;
}
} else {
player.connected =
ReadSetting(QStringLiteral("%1connected").arg(player_prefix), player_index == 0)
.toBool();
player.controller_type = static_cast<Settings::ControllerType>(
qt_config
->value(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(Settings::ControllerType::ProController))
.toUInt());
player.vibration_enabled =
qt_config->value(QStringLiteral("%1vibration_enabled").arg(player_prefix), true)
.toBool();
player.vibration_strength =
qt_config->value(QStringLiteral("%1vibration_strength").arg(player_prefix), 100)
.toInt();
player.body_color_left = qt_config
->value(QStringLiteral("player_%1_body_color_left").arg(p),
->value(QStringLiteral("%1body_color_left").arg(player_prefix),
Settings::JOYCON_BODY_NEON_BLUE)
.toUInt();
player.body_color_right = qt_config
->value(QStringLiteral("player_%1_body_color_right").arg(p),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
player.button_color_left = qt_config
->value(QStringLiteral("player_%1_button_color_left").arg(p),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
player.body_color_right =
qt_config
->value(QStringLiteral("%1body_color_right").arg(player_prefix),
Settings::JOYCON_BODY_NEON_RED)
.toUInt();
player.button_color_left =
qt_config
->value(QStringLiteral("%1button_color_left").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_BLUE)
.toUInt();
player.button_color_right =
qt_config
->value(QStringLiteral("player_%1_button_color_right").arg(p),
->value(QStringLiteral("%1button_color_right").arg(player_prefix),
Settings::JOYCON_BUTTONS_NEON_RED)
.toUInt();
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param =
InputCommon::GenerateKeyboardParam(default_buttons[i]);
auto& player_buttons = player.buttons[i];
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
auto& player_buttons = player.buttons[i];
player_buttons = qt_config
->value(QStringLiteral("player_%1_").arg(p) +
QString::fromUtf8(Settings::NativeButton::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_buttons.empty()) {
player_buttons = default_param;
}
player_buttons = qt_config
->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeButton::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_buttons.empty()) {
player_buttons = default_param;
}
}
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param =
InputCommon::GenerateKeyboardParam(default_motions[i]);
auto& player_motions = player.motions[i];
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f);
auto& player_analogs = player.analogs[i];
player_motions = qt_config
->value(QStringLiteral("player_%1_").arg(p) +
QString::fromUtf8(Settings::NativeMotion::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_motions.empty()) {
player_motions = default_param;
}
player_analogs = qt_config
->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_analogs.empty()) {
player_analogs = default_param;
}
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f);
auto& player_analogs = player.analogs[i];
for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
auto& player_vibrations = player.vibrations[i];
player_analogs = qt_config
->value(QStringLiteral("player_%1_").arg(p) +
QString::fromUtf8(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_analogs.empty()) {
player_analogs = default_param;
}
player_vibrations =
qt_config
->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeVibration::mapping[i]),
QString{})
.toString()
.toStdString();
}
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
auto& player_motions = player.motions[i];
player_motions = qt_config
->value(QStringLiteral("%1").arg(player_prefix) +
QString::fromUtf8(Settings::NativeMotion::mapping[i]),
QString::fromStdString(default_param))
.toString()
.toStdString();
if (player_motions.empty()) {
player_motions = default_param;
}
}
}
@ -436,18 +501,21 @@ void Config::ReadAudioValues() {
void Config::ReadControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
ReadPlayerValues();
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
ReadPlayerValue(p);
}
ReadDebugValues();
ReadKeyboardValues();
ReadMouseValues();
ReadTouchscreenValues();
ReadMotionTouchValues();
Settings::values.vibration_enabled =
ReadSetting(QStringLiteral("vibration_enabled"), true).toBool();
Settings::values.motion_enabled = ReadSetting(QStringLiteral("motion_enabled"), true).toBool();
Settings::values.use_docked_mode =
ReadSetting(QStringLiteral("use_docked_mode"), false).toBool();
ReadSettingGlobal(Settings::values.use_docked_mode, QStringLiteral("use_docked_mode"), false);
ReadSettingGlobal(Settings::values.vibration_enabled, QStringLiteral("vibration_enabled"),
true);
ReadSettingGlobal(Settings::values.enable_accurate_vibrations,
QStringLiteral("enable_accurate_vibrations"), false);
ReadSettingGlobal(Settings::values.motion_enabled, QStringLiteral("motion_enabled"), true);
qt_config->endGroup();
}
@ -920,49 +988,64 @@ void Config::ReadValues() {
ReadSystemValues();
}
void Config::SavePlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
const auto& player = Settings::values.players[p];
void Config::SavePlayerValue(std::size_t player_index) {
const QString player_prefix = [this, player_index] {
if (type == ConfigType::InputProfile) {
return QString{};
} else {
return QStringLiteral("player_%1_").arg(player_index);
}
}();
WriteSetting(QStringLiteral("player_%1_connected").arg(p), player.connected, false);
WriteSetting(QStringLiteral("player_%1_type").arg(p),
static_cast<u8>(player.controller_type),
static_cast<u8>(Settings::ControllerType::ProController));
const auto& player = Settings::values.players.GetValue()[player_index];
WriteSetting(QStringLiteral("player_%1_body_color_left").arg(p), player.body_color_left,
WriteSetting(QStringLiteral("%1type").arg(player_prefix),
static_cast<u8>(player.controller_type),
static_cast<u8>(Settings::ControllerType::ProController));
if (!player_prefix.isEmpty()) {
WriteSetting(QStringLiteral("%1connected").arg(player_prefix), player.connected, false);
WriteSetting(QStringLiteral("%1vibration_enabled").arg(player_prefix),
player.vibration_enabled, true);
WriteSetting(QStringLiteral("%1vibration_strength").arg(player_prefix),
player.vibration_strength, 100);
WriteSetting(QStringLiteral("%1body_color_left").arg(player_prefix), player.body_color_left,
Settings::JOYCON_BODY_NEON_BLUE);
WriteSetting(QStringLiteral("player_%1_body_color_right").arg(p), player.body_color_right,
Settings::JOYCON_BODY_NEON_RED);
WriteSetting(QStringLiteral("player_%1_button_color_left").arg(p), player.button_color_left,
Settings::JOYCON_BUTTONS_NEON_BLUE);
WriteSetting(QStringLiteral("player_%1_button_color_right").arg(p),
WriteSetting(QStringLiteral("%1body_color_right").arg(player_prefix),
player.body_color_right, Settings::JOYCON_BODY_NEON_RED);
WriteSetting(QStringLiteral("%1button_color_left").arg(player_prefix),
player.button_color_left, Settings::JOYCON_BUTTONS_NEON_BLUE);
WriteSetting(QStringLiteral("%1button_color_right").arg(player_prefix),
player.button_color_right, Settings::JOYCON_BUTTONS_NEON_RED);
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param =
InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(player.buttons[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param =
InputCommon::GenerateKeyboardParam(default_motions[i]);
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeMotion::mapping[i]),
QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(QStringLiteral("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(player.analogs[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
WriteSetting(QStringLiteral("%1").arg(player_prefix) +
QString::fromStdString(Settings::NativeButton::mapping[i]),
QString::fromStdString(player.buttons[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_stick_mod[i], 0.5f);
WriteSetting(QStringLiteral("%1").arg(player_prefix) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
QString::fromStdString(player.analogs[i]),
QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeVibration::NumVibrations; ++i) {
WriteSetting(QStringLiteral("%1").arg(player_prefix) +
QString::fromStdString(Settings::NativeVibration::mapping[i]),
QString::fromStdString(player.vibrations[i]), QString{});
}
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
WriteSetting(QStringLiteral("%1").arg(player_prefix) +
QString::fromStdString(Settings::NativeMotion::mapping[i]),
QString::fromStdString(player.motions[i]),
QString::fromStdString(default_param));
}
}
@ -1087,14 +1170,20 @@ void Config::SaveAudioValues() {
void Config::SaveControlValues() {
qt_config->beginGroup(QStringLiteral("Controls"));
SavePlayerValues();
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
SavePlayerValue(p);
}
SaveDebugValues();
SaveMouseValues();
SaveTouchscreenValues();
SaveMotionTouchValues();
WriteSetting(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled, true);
WriteSetting(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
WriteSettingGlobal(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
WriteSettingGlobal(QStringLiteral("vibration_enabled"), Settings::values.vibration_enabled,
true);
WriteSettingGlobal(QStringLiteral("enable_accurate_vibrations"),
Settings::values.enable_accurate_vibrations, false);
WriteSettingGlobal(QStringLiteral("motion_enabled"), Settings::values.motion_enabled, true);
WriteSetting(QStringLiteral("motion_device"),
QString::fromStdString(Settings::values.motion_device),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
@ -1102,7 +1191,6 @@ void Config::SaveControlValues() {
QString::fromStdString(Settings::values.touch_device),
QStringLiteral("engine:emu_window"));
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
WriteSetting(QStringLiteral("use_docked_mode"), Settings::values.use_docked_mode, false);
qt_config->endGroup();
}
@ -1515,3 +1603,19 @@ void Config::Save() {
Settings::Sanitize();
SaveValues();
}
void Config::ReadControlPlayerValue(std::size_t player_index) {
qt_config->beginGroup(QStringLiteral("Controls"));
ReadPlayerValue(player_index);
qt_config->endGroup();
}
void Config::SaveControlPlayerValue(std::size_t player_index) {
qt_config->beginGroup(QStringLiteral("Controls"));
SavePlayerValue(player_index);
qt_config->endGroup();
}
const std::string& Config::GetConfigFilePath() const {
return qt_config_loc;
}

View file

@ -16,12 +16,24 @@ class QSettings;
class Config {
public:
explicit Config(const std::string& config_loc = "qt-config.ini", bool is_global = true);
enum class ConfigType {
GlobalConfig,
PerGameConfig,
InputProfile,
};
explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
void Save();
void ReadControlPlayerValue(std::size_t player_index);
void SaveControlPlayerValue(std::size_t player_index);
const std::string& GetConfigFilePath() const;
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
@ -33,8 +45,10 @@ public:
static const std::array<UISettings::Shortcut, 16> default_hotkeys;
private:
void Initialize(const std::string& config_name);
void ReadValues();
void ReadPlayerValues();
void ReadPlayerValue(std::size_t player_index);
void ReadDebugValues();
void ReadKeyboardValues();
void ReadMouseValues();
@ -62,7 +76,7 @@ private:
void ReadWebServiceValues();
void SaveValues();
void SavePlayerValues();
void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
@ -111,9 +125,9 @@ private:
void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
const QVariant& default_value);
ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
};

View file

@ -275,32 +275,12 @@
<signal>accepted()</signal>
<receiver>ConfigureDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>220</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>220</x>
<y>200</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>220</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>220</x>
<y>200</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -4,11 +4,14 @@
#include "ui_configure_debug_controller.h"
#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input_player.h"
ConfigureDebugController::ConfigureDebugController(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem)
InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
debug_controller(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, true)) {
debug_controller(
new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) {
ui->setupUi(this);
ui->controllerLayout->addWidget(debug_controller);

View file

@ -6,10 +6,13 @@
#include <memory>
#include <QDialog>
#include "yuzu/configuration/configure_input_player.h"
class QPushButton;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
@ -22,8 +25,8 @@ class ConfigureDebugController : public QDialog {
Q_OBJECT
public:
explicit ConfigureDebugController(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem);
explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles);
~ConfigureDebugController() override;
void ApplyConfiguration();

View file

@ -66,32 +66,12 @@
<signal>accepted()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -23,6 +23,8 @@
#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
namespace {
template <typename Dialog, typename... Args>
@ -64,7 +66,8 @@ void OnDockedModeChanged(bool last_state, bool new_state) {
}
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
}
@ -73,14 +76,22 @@ ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
std::size_t max_players) {
player_controllers = {
new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem),
new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
profiles.get()),
};
player_tabs = {
@ -113,8 +124,10 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
}
}
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices,
[this] { UpdateAllInputDevices(); });
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
&ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
});
@ -134,7 +147,7 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
ui->tabAdvanced->layout()->addWidget(advanced);
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem);
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
});
connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
@ -146,6 +159,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
connect(ui->vibrationButton, &QPushButton::clicked,
[this] { CallConfigureDialog<ConfigureVibration>(*this); });
connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
@ -171,12 +187,12 @@ void ConfigureInput::ApplyConfiguration() {
advanced->ApplyConfiguration();
const bool pre_docked_mode = Settings::values.use_docked_mode;
Settings::values.use_docked_mode = ui->radioDocked->isChecked();
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode);
const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
Settings::values.vibration_enabled = ui->vibrationGroup->isChecked();
Settings::values.motion_enabled = ui->motionGroup->isChecked();
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void ConfigureInput::changeEvent(QEvent* event) {
@ -193,16 +209,16 @@ void ConfigureInput::RetranslateUI() {
void ConfigureInput::LoadConfiguration() {
LoadPlayerControllerIndices();
UpdateDockedState(Settings::values.players[8].connected);
UpdateDockedState(Settings::values.players.GetValue()[8].connected);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
ui->motionGroup->setChecked(Settings::values.motion_enabled);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
void ConfigureInput::LoadPlayerControllerIndices() {
for (std::size_t i = 0; i < player_connected.size(); ++i) {
const auto connected = Settings::values.players[i].connected ||
(i == 0 && Settings::values.players[8].connected);
const auto connected = Settings::values.players.GetValue()[i].connected ||
(i == 0 && Settings::values.players.GetValue()[8].connected);
player_connected[i]->setChecked(connected);
}
}
@ -231,8 +247,8 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
ui->radioDocked->setChecked(Settings::values.use_docked_mode);
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode);
ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
@ -242,6 +258,16 @@ void ConfigureInput::UpdateDockedState(bool is_handheld) {
void ConfigureInput::UpdateAllInputDevices() {
for (const auto& player : player_controllers) {
player->UpdateInputDevices();
player->UpdateInputDeviceCombobox();
}
}
void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
for (std::size_t i = 0; i < player_controllers.size(); ++i) {
if (i == player_index) {
continue;
}
player_controllers[i]->UpdateInputProfiles();
}
}

View file

@ -8,17 +8,18 @@
#include <memory>
#include <QKeyEvent>
#include <QList>
#include <QWidget>
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
#include "ui_configure_input.h"
class QCheckBox;
class QString;
class QTimer;
class ConfigureInputAdvanced;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
@ -51,6 +52,7 @@ private:
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index);
/// Load configuration settings.
void LoadConfiguration();
@ -61,6 +63,8 @@ private:
std::unique_ptr<Ui::ConfigureInput> ui;
std::unique_ptr<InputProfiles> profiles;
std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs;
std::array<QCheckBox*, 8> player_connected;

View file

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<width>680</width>
<height>540</height>
</rect>
</property>
@ -142,7 +142,7 @@
<number>6</number>
</property>
<property name="leftMargin">
<number>3</number>
<number>8</number>
</property>
<property name="topMargin">
<number>6</number>
@ -195,30 +195,24 @@
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpin">
<widget class="QPushButton" name="vibrationButton">
<property name="minimumSize">
<size>
<width>65</width>
<height>21</height>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>65</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>200</number>
</property>
<property name="value">
<number>100</number>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
@ -250,18 +244,18 @@
<widget class="QPushButton" name="motionButton">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
@ -272,7 +266,7 @@
</widget>
</item>
<item alignment="Qt::AlignVCenter">
<widget class="QWidget" name="widget" native="true">
<widget class="QWidget" name="connectedControllers" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>5</number>
@ -468,13 +462,13 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -494,7 +488,7 @@
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Defaults</string>
@ -511,13 +505,13 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -537,7 +531,7 @@
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Clear</string>

View file

@ -68,8 +68,7 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
connect(color_buttons[button_idx], &QPushButton::clicked, this,
[this, player_idx, button_idx] {
OnControllerButtonClick(static_cast<int>(player_idx),
static_cast<int>(button_idx));
OnControllerButtonClick(player_idx, button_idx);
});
}
}
@ -94,20 +93,21 @@ ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
void ConfigureInputAdvanced::OnControllerButtonClick(int player_idx, int button_idx) {
void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx,
std::size_t button_idx) {
const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
if (!new_bg_color.isValid()) {
return;
}
controllers_colors[player_idx][button_idx] = new_bg_color;
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
QStringLiteral("background-color: %1; min-width: 55px;")
QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
void ConfigureInputAdvanced::ApplyConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
auto& player = Settings::values.players[player_idx];
auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors{};
std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
colors.begin(), [](QColor color) { return color.rgb(); });
@ -126,7 +126,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
void ConfigureInputAdvanced::LoadConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
auto& player = Settings::values.players[player_idx];
auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors = {
player.body_color_left,
player.button_color_left,
@ -139,7 +139,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
QStringLiteral("background-color: %1; min-width: 55px;")
QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
}

View file

@ -35,7 +35,7 @@ private:
void RetranslateUI();
void UpdateUIEnabled();
void OnControllerButtonClick(int player_idx, int button_idx);
void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx);
void LoadConfiguration();

View file

@ -192,18 +192,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -247,18 +247,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -323,18 +323,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -378,18 +378,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -478,18 +478,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -533,18 +533,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -609,18 +609,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -664,18 +664,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -782,18 +782,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -837,18 +837,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -913,18 +913,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -968,18 +968,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1068,18 +1068,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1123,18 +1123,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1199,18 +1199,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1254,18 +1254,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1393,18 +1393,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1448,18 +1448,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1524,18 +1524,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1579,18 +1579,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1679,18 +1679,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1734,18 +1734,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1810,18 +1810,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1865,18 +1865,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -1983,18 +1983,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2038,18 +2038,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2114,18 +2114,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2169,18 +2169,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2269,18 +2269,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2324,18 +2324,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2400,18 +2400,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>
@ -2455,18 +2455,18 @@
</property>
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string/>

View file

@ -1,37 +0,0 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "ui_configure_input_dialog.h"
#include "yuzu/configuration/configure_input_dialog.h"
ConfigureInputDialog::ConfigureInputDialog(QWidget* parent, std::size_t max_players,
InputCommon::InputSubsystem* input_subsystem)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInputDialog>()),
input_widget(new ConfigureInput(this)) {
ui->setupUi(this);
input_widget->Initialize(input_subsystem, max_players);
ui->inputLayout->addWidget(input_widget);
RetranslateUI();
}
ConfigureInputDialog::~ConfigureInputDialog() = default;
void ConfigureInputDialog::ApplyConfiguration() {
input_widget->ApplyConfiguration();
}
void ConfigureInputDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureInputDialog::RetranslateUI() {
ui->retranslateUi(this);
}

View file

@ -1,38 +0,0 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include "yuzu/configuration/configure_input.h"
class QPushButton;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureInputDialog;
}
class ConfigureInputDialog : public QDialog {
Q_OBJECT
public:
explicit ConfigureInputDialog(QWidget* parent, std::size_t max_players,
InputCommon::InputSubsystem* input_subsystem);
~ConfigureInputDialog() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
std::unique_ptr<Ui::ConfigureInputDialog> ui;
ConfigureInput* input_widget;
};

View file

@ -4,6 +4,7 @@
#include <algorithm>
#include <memory>
#include <thread>
#include <utility>
#include <QGridLayout>
#include <QInputDialog>
@ -22,8 +23,9 @@
#include "ui_configure_input_player.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input_player.h"
constexpr std::size_t HANDHELD_INDEX = 8;
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/util/limitable_input_dialog.h"
const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
ConfigureInputPlayer::analog_sub_buttons{{
@ -35,6 +37,8 @@ const std::array<std::string, ConfigureInputPlayer::ANALOG_SUB_BUTTONS_NUM>
namespace {
constexpr std::size_t HANDHELD_INDEX = 8;
void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
bool connected) {
Core::System& system{Core::System::GetInstance()};
@ -240,10 +244,11 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_index,
QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_,
bool debug)
InputProfiles* profiles_, bool debug)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputPlayer>()), player_index(player_index),
debug(debug), input_subsystem{input_subsystem_}, timeout_timer(std::make_unique<QTimer>()),
poll_timer(std::make_unique<QTimer>()), bottom_row(bottom_row) {
debug(debug), input_subsystem{input_subsystem_}, profiles(profiles_),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()),
bottom_row(bottom_row) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
@ -366,6 +371,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
connect(analog_button, &QPushButton::clicked, [=, this] {
if (!map_analog_stick_accepted) {
map_analog_stick_accepted =
QMessageBox::information(
this, tr("Map Analog Stick"),
tr("After pressing OK, first move your joystick horizontally, and then "
"vertically.\nTo invert the axes, first move your joystick "
"vertically, and then horizontally."),
QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok;
if (!map_analog_stick_accepted) {
return;
}
}
HandleClick(
analog_map_buttons[analog_id][sub_button_id],
[=, this](const Common::ParamPackage& params) {
@ -455,11 +472,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
});
}
if (debug || player_index == 9) {
ui->groupConnectedController->setCheckable(false);
}
// The Debug Controller can only choose the Pro Controller.
if (debug) {
ui->buttonScreenshot->setEnabled(false);
ui->buttonHome->setEnabled(false);
ui->groupConnectedController->setCheckable(false);
QStringList debug_controller_types = {
tr("Pro Controller"),
};
@ -477,11 +497,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
UpdateMotionButtons();
});
connect(ui->comboDevices, qOverload<int>(&QComboBox::currentIndexChanged), this,
connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults);
ui->comboDevices->setCurrentIndex(-1);
ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
UpdateInputDevices();
connect(ui->buttonRefreshDevices, &QPushButton::clicked,
[this] { emit RefreshInputDevices(); });
@ -492,14 +513,14 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
Common::ParamPackage params;
if (input_subsystem->GetGCButtons()->IsPolling()) {
params = input_subsystem->GetGCButtons()->GetNextInput();
if (params.Has("engine")) {
if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
}
if (input_subsystem->GetGCAnalogs()->IsPolling()) {
params = input_subsystem->GetGCAnalogs()->GetNextInput();
if (params.Has("engine")) {
if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
@ -513,13 +534,24 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
}
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
if (params.Has("engine") && IsInputAcceptable(params)) {
SetPollingResult(params, false);
return;
}
}
});
UpdateInputProfiles();
connect(ui->buttonProfilesNew, &QPushButton::clicked, this,
&ConfigureInputPlayer::CreateProfile);
connect(ui->buttonProfilesDelete, &QPushButton::clicked, this,
&ConfigureInputPlayer::DeleteProfile);
connect(ui->comboProfiles, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::LoadProfile);
connect(ui->buttonProfilesSave, &QPushButton::clicked, this,
&ConfigureInputPlayer::SaveProfile);
LoadConfiguration();
// TODO(wwylele): enable this when we actually emulate it
@ -529,7 +561,7 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
ConfigureInputPlayer::~ConfigureInputPlayer() = default;
void ConfigureInputPlayer::ApplyConfiguration() {
auto& player = Settings::values.players[player_index];
auto& player = Settings::values.players.GetValue()[player_index];
auto& buttons = debug ? Settings::values.debug_pad_buttons : player.buttons;
auto& analogs = debug ? Settings::values.debug_pad_analogs : player.analogs;
@ -543,33 +575,58 @@ void ConfigureInputPlayer::ApplyConfiguration() {
}
auto& motions = player.motions;
std::transform(motions_param.begin(), motions_param.end(), motions.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
player.controller_type =
static_cast<Settings::ControllerType>(ui->comboControllerType->currentIndex());
player.connected = ui->groupConnectedController->isChecked();
const auto controller_type =
GetControllerTypeFromIndex(ui->comboControllerType->currentIndex());
const auto player_connected = ui->groupConnectedController->isChecked() &&
controller_type != Settings::ControllerType::Handheld;
// Player 2-8
if (player_index != 0) {
UpdateController(player.controller_type, player_index, player.connected);
if (player.controller_type == controller_type && player.connected == player_connected) {
// Set vibration devices in the event that the input device has changed.
ConfigureVibration::SetVibrationDevices(player_index);
return;
}
// Player 1 and Handheld
auto& handheld = Settings::values.players[HANDHELD_INDEX];
// If Handheld is selected, copy all the settings from Player 1 to Handheld.
if (player.controller_type == Settings::ControllerType::Handheld) {
handheld = player;
handheld.connected = ui->groupConnectedController->isChecked();
player.connected = false; // Disconnect Player 1
} else {
player.connected = ui->groupConnectedController->isChecked();
handheld.connected = false; // Disconnect Handheld
// Disconnect the controller first.
UpdateController(controller_type, player_index, false);
player.controller_type = controller_type;
player.connected = player_connected;
ConfigureVibration::SetVibrationDevices(player_index);
// Handheld
if (player_index == 0) {
auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
if (controller_type == Settings::ControllerType::Handheld) {
handheld = player;
}
handheld.connected = ui->groupConnectedController->isChecked() &&
controller_type == Settings::ControllerType::Handheld;
UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
}
UpdateController(player.controller_type, player_index, player.connected);
UpdateController(Settings::ControllerType::Handheld, HANDHELD_INDEX, handheld.connected);
if (!player.connected) {
return;
}
// This emulates a delay between disconnecting and reconnecting controllers as some games
// do not respond to a change in controller type if it was instantaneous.
using namespace std::chrono_literals;
std::this_thread::sleep_for(20ms);
UpdateController(controller_type, player_index, player_connected);
}
void ConfigureInputPlayer::showEvent(QShowEvent* event) {
if (bottom_row == nullptr) {
return;
}
QWidget::showEvent(event);
ui->main->addWidget(bottom_row);
}
void ConfigureInputPlayer::changeEvent(QEvent* event) {
@ -586,7 +643,7 @@ void ConfigureInputPlayer::RetranslateUI() {
}
void ConfigureInputPlayer::LoadConfiguration() {
auto& player = Settings::values.players[player_index];
auto& player = Settings::values.players.GetValue()[player_index];
if (debug) {
std::transform(Settings::values.debug_pad_buttons.begin(),
Settings::values.debug_pad_buttons.end(), buttons_param.begin(),
@ -604,6 +661,7 @@ void ConfigureInputPlayer::LoadConfiguration() {
}
UpdateUI();
UpdateInputDeviceCombobox();
if (debug) {
return;
@ -612,44 +670,75 @@ void ConfigureInputPlayer::LoadConfiguration() {
ui->comboControllerType->setCurrentIndex(static_cast<int>(player.controller_type));
ui->groupConnectedController->setChecked(
player.connected ||
(player_index == 0 && Settings::values.players[HANDHELD_INDEX].connected));
(player_index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected));
}
void ConfigureInputPlayer::UpdateInputDevices() {
input_devices = input_subsystem->GetInputDevices();
ui->comboDevices->clear();
for (auto device : input_devices) {
ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
void ConfigureInputPlayer::ConnectPlayer(bool connected) {
ui->groupConnectedController->setChecked(connected);
}
void ConfigureInputPlayer::UpdateInputDeviceCombobox() {
// Skip input device persistence if "Input Devices" is set to "Any".
if (ui->comboDevices->currentIndex() == 0) {
UpdateInputDevices();
return;
}
// Find the first button that isn't empty.
const auto button_param =
std::find_if(buttons_param.begin(), buttons_param.end(),
[](const Common::ParamPackage param) { return param.Has("engine"); });
const bool buttons_empty = button_param == buttons_param.end();
const auto current_engine = button_param->Get("engine", "");
const auto current_guid = button_param->Get("guid", "");
const auto current_port = button_param->Get("port", "");
const bool is_keyboard_mouse = current_engine == "keyboard" || current_engine == "mouse";
UpdateInputDevices();
if (buttons_empty) {
return;
}
const bool all_one_device =
std::all_of(buttons_param.begin(), buttons_param.end(),
[current_engine, current_guid, current_port,
is_keyboard_mouse](const Common::ParamPackage param) {
if (is_keyboard_mouse) {
return !param.Has("engine") || param.Get("engine", "") == "keyboard" ||
param.Get("engine", "") == "mouse";
}
return !param.Has("engine") || (param.Get("engine", "") == current_engine &&
param.Get("guid", "") == current_guid &&
param.Get("port", "") == current_port);
});
if (all_one_device) {
if (is_keyboard_mouse) {
ui->comboDevices->setCurrentIndex(1);
return;
}
const auto devices_it = std::find_if(
input_devices.begin(), input_devices.end(),
[current_engine, current_guid, current_port](const Common::ParamPackage param) {
return param.Get("class", "") == current_engine &&
param.Get("guid", "") == current_guid &&
param.Get("port", "") == current_port;
});
const int device_index =
devices_it != input_devices.end()
? static_cast<int>(std::distance(input_devices.begin(), devices_it))
: 0;
ui->comboDevices->setCurrentIndex(device_index);
} else {
ui->comboDevices->setCurrentIndex(0);
}
}
void ConfigureInputPlayer::RestoreDefaults() {
// Reset Buttons
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
}
// Reset Analogs and Modifier Buttons
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
analogs_param[analog_id].Set(
"modifier", InputCommon::GenerateKeyboardParam(Config::default_stick_mod[analog_id]));
}
for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
motions_param[motion_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
}
UpdateUI();
UpdateInputDevices();
ui->comboControllerType->setCurrentIndex(0);
UpdateMappingWithDefaults();
}
void ConfigureInputPlayer::ClearAll() {
@ -752,117 +841,12 @@ void ConfigureInputPlayer::UpdateUI() {
}
}
void ConfigureInputPlayer::UpdateMappingWithDefaults() {
if (ui->comboDevices->currentIndex() < 2) {
return;
void ConfigureInputPlayer::UpdateInputDevices() {
input_devices = input_subsystem->GetInputDevices();
ui->comboDevices->clear();
for (auto device : input_devices) {
ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
}
const auto& device = input_devices[ui->comboDevices->currentIndex()];
auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
for (std::size_t i = 0; i < buttons_param.size(); ++i) {
buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
}
for (std::size_t i = 0; i < analogs_param.size(); ++i) {
analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
}
UpdateUI();
}
void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
button->setText(tr("Shake!"));
} else {
button->setText(tr("[waiting]"));
}
button->setFocus();
// The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
// controller, then they don't want keyboard/mouse input
want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
QWidget::grabMouse();
QWidget::grabKeyboard();
if (type == InputCommon::Polling::DeviceType::Button) {
input_subsystem->GetGCButtons()->BeginConfiguration();
} else {
input_subsystem->GetGCAnalogs()->BeginConfiguration();
}
if (type == InputCommon::Polling::DeviceType::Motion) {
input_subsystem->GetUDPMotions()->BeginConfiguration();
}
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
QWidget::releaseMouse();
QWidget::releaseKeyboard();
input_subsystem->GetGCButtons()->EndConfiguration();
input_subsystem->GetGCAnalogs()->EndConfiguration();
input_subsystem->GetUDPMotions()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}
UpdateUI();
input_setter = std::nullopt;
}
void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
false);
} else {
// We don't want any mouse buttons, so don't stop polling
return;
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::UpdateControllerIcon() {
@ -885,7 +869,7 @@ void ConfigureInputPlayer::UpdateControllerIcon() {
}
}();
const QString theme = [this] {
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
@ -986,14 +970,260 @@ void ConfigureInputPlayer::UpdateMotionButtons() {
}
}
void ConfigureInputPlayer::showEvent(QShowEvent* event) {
if (bottom_row == nullptr) {
void ConfigureInputPlayer::UpdateMappingWithDefaults() {
if (ui->comboDevices->currentIndex() == 0) {
return;
}
QWidget::showEvent(event);
ui->main->addWidget(bottom_row);
if (ui->comboDevices->currentIndex() == 1) {
// Reset keyboard bindings
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; ++button_id) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
Config::default_analogs[analog_id][sub_button_id])};
SetAnalogParam(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
}
analogs_param[analog_id].Set("modifier", InputCommon::GenerateKeyboardParam(
Config::default_stick_mod[analog_id]));
}
for (int motion_id = 0; motion_id < Settings::NativeMotion::NumMotions; ++motion_id) {
motions_param[motion_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_motions[motion_id])};
}
UpdateUI();
return;
}
// Reset controller bindings
const auto& device = input_devices[ui->comboDevices->currentIndex()];
auto button_mapping = input_subsystem->GetButtonMappingForDevice(device);
auto analog_mapping = input_subsystem->GetAnalogMappingForDevice(device);
for (std::size_t i = 0; i < buttons_param.size(); ++i) {
buttons_param[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)];
}
for (std::size_t i = 0; i < analogs_param.size(); ++i) {
analogs_param[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)];
}
UpdateUI();
}
void ConfigureInputPlayer::ConnectPlayer(bool connected) {
ui->groupConnectedController->setChecked(connected);
void ConfigureInputPlayer::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
if (button == ui->buttonMotionLeft || button == ui->buttonMotionRight) {
button->setText(tr("Shake!"));
} else {
button->setText(tr("[waiting]"));
}
button->setFocus();
// The first two input devices are always Any and Keyboard/Mouse. If the user filtered to a
// controller, then they don't want keyboard/mouse input
want_keyboard_mouse = ui->comboDevices->currentIndex() < 2;
input_setter = new_input_setter;
device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
QWidget::grabMouse();
QWidget::grabKeyboard();
if (type == InputCommon::Polling::DeviceType::Button) {
input_subsystem->GetGCButtons()->BeginConfiguration();
} else {
input_subsystem->GetGCAnalogs()->BeginConfiguration();
}
if (type == InputCommon::Polling::DeviceType::Motion) {
input_subsystem->GetUDPMotions()->BeginConfiguration();
}
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
QWidget::releaseMouse();
QWidget::releaseKeyboard();
input_subsystem->GetGCButtons()->EndConfiguration();
input_subsystem->GetGCAnalogs()->EndConfiguration();
input_subsystem->GetUDPMotions()->EndConfiguration();
if (!abort) {
(*input_setter)(params);
}
UpdateUI();
UpdateInputDeviceCombobox();
input_setter = std::nullopt;
}
bool ConfigureInputPlayer::IsInputAcceptable(const Common::ParamPackage& params) const {
if (ui->comboDevices->currentIndex() == 0) {
return true;
}
// Keyboard/Mouse
if (ui->comboDevices->currentIndex() == 1) {
return params.Get("engine", "") == "keyboard" || params.Get("engine", "") == "mouse";
}
const auto current_input_device = input_devices[ui->comboDevices->currentIndex()];
return params.Get("engine", "") == current_input_device.Get("class", "") &&
params.Get("guid", "") == current_input_device.Get("guid", "") &&
params.Get("port", "") == current_input_device.Get("port", "");
}
void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
false);
} else {
// We don't want any mouse buttons, so don't stop polling
return;
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
SetPollingResult({}, true);
}
void ConfigureInputPlayer::CreateProfile() {
const auto profile_name =
LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
if (profile_name.isEmpty()) {
return;
}
if (!profiles->IsProfileNameValid(profile_name.toStdString())) {
QMessageBox::critical(this, tr("Create Input Profile"),
tr("The given profile name is not valid!"));
return;
}
ApplyConfiguration();
if (!profiles->CreateProfile(profile_name.toStdString(), player_index)) {
QMessageBox::critical(this, tr("Create Input Profile"),
tr("Failed to create the input profile \"%1\"").arg(profile_name));
UpdateInputProfiles();
emit RefreshInputProfiles(player_index);
return;
}
emit RefreshInputProfiles(player_index);
ui->comboProfiles->addItem(profile_name);
ui->comboProfiles->setCurrentIndex(ui->comboProfiles->count() - 1);
}
void ConfigureInputPlayer::DeleteProfile() {
const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
if (profile_name.isEmpty()) {
return;
}
if (!profiles->DeleteProfile(profile_name.toStdString())) {
QMessageBox::critical(this, tr("Delete Input Profile"),
tr("Failed to delete the input profile \"%1\"").arg(profile_name));
UpdateInputProfiles();
emit RefreshInputProfiles(player_index);
return;
}
emit RefreshInputProfiles(player_index);
ui->comboProfiles->removeItem(ui->comboProfiles->currentIndex());
ui->comboProfiles->setCurrentIndex(-1);
}
void ConfigureInputPlayer::LoadProfile() {
const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
if (profile_name.isEmpty()) {
return;
}
ApplyConfiguration();
if (!profiles->LoadProfile(profile_name.toStdString(), player_index)) {
QMessageBox::critical(this, tr("Load Input Profile"),
tr("Failed to load the input profile \"%1\"").arg(profile_name));
UpdateInputProfiles();
emit RefreshInputProfiles(player_index);
return;
}
LoadConfiguration();
}
void ConfigureInputPlayer::SaveProfile() {
const QString profile_name = ui->comboProfiles->itemText(ui->comboProfiles->currentIndex());
if (profile_name.isEmpty()) {
return;
}
ApplyConfiguration();
if (!profiles->SaveProfile(profile_name.toStdString(), player_index)) {
QMessageBox::critical(this, tr("Save Input Profile"),
tr("Failed to save the input profile \"%1\"").arg(profile_name));
UpdateInputProfiles();
emit RefreshInputProfiles(player_index);
return;
}
}
void ConfigureInputPlayer::UpdateInputProfiles() {
ui->comboProfiles->clear();
for (const auto& profile_name : profiles->GetInputProfileNames()) {
ui->comboProfiles->addItem(QString::fromStdString(profile_name));
}
ui->comboProfiles->setCurrentIndex(-1);
}

View file

@ -26,6 +26,8 @@ class QString;
class QTimer;
class QWidget;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
@ -45,14 +47,20 @@ class ConfigureInputPlayer : public QWidget {
public:
explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_,
bool debug = false);
InputProfiles* profiles_, bool debug = false);
~ConfigureInputPlayer() override;
/// Save all button configurations to settings file.
void ApplyConfiguration();
/// Set the connection state checkbox (used to sync state).
void ConnectPlayer(bool connected);
/// Update the input devices combobox.
void UpdateInputDevices();
void UpdateInputDeviceCombobox();
/// Updates the list of controller profiles.
void UpdateInputProfiles();
/// Restore all buttons to their default values.
void RestoreDefaults();
@ -60,9 +68,6 @@ public:
/// Clear all input configuration.
void ClearAll();
/// Set the connection state checkbox (used to sync state).
void ConnectPlayer(bool connected);
signals:
/// Emitted when this controller is connected by the user.
void Connected(bool connected);
@ -70,6 +75,12 @@ signals:
void HandheldStateChanged(bool is_handheld);
/// Emitted when the input devices combobox is being refreshed.
void RefreshInputDevices();
/**
* Emitted when the input profiles combobox is being refreshed.
* The player_index represents the current player's index, and the profile combobox
* will not be updated for this index as they are already updated by other mechanisms.
*/
void RefreshInputProfiles(std::size_t player_index);
protected:
void showEvent(QShowEvent* event) override;
@ -89,6 +100,9 @@ private:
/// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Checks whether a given input can be accepted.
bool IsInputAcceptable(const Common::ParamPackage& params) const;
/// Handle mouse button press events.
void mousePressEvent(QMouseEvent* event) override;
@ -98,8 +112,8 @@ private:
/// Update UI to reflect current configuration.
void UpdateUI();
/// Update the controller selection combobox
void UpdateControllerCombobox();
/// Update the available input devices.
void UpdateInputDevices();
/// Update the current controller icon.
void UpdateControllerIcon();
@ -113,6 +127,18 @@ private:
/// Gets the default controller mapping for this device and auto configures the input to match.
void UpdateMappingWithDefaults();
/// Creates a controller profile.
void CreateProfile();
/// Deletes the selected controller profile.
void DeleteProfile();
/// Loads the selected controller profile.
void LoadProfile();
/// Saves the current controller configuration into a selected controller profile.
void SaveProfile();
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index;
@ -120,6 +146,8 @@ private:
InputCommon::InputSubsystem* input_subsystem;
InputProfiles* profiles;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
@ -159,12 +187,15 @@ private:
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
/// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
bool map_analog_stick_accepted{};
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_mouse = false;
bool want_keyboard_mouse{};
/// List of physical devices users can map with. If a SDL backed device is selected, then you
/// can usue this device to get a default mapping.
/// can use this device to get a default mapping.
std::vector<Common::ParamPackage> input_devices;
/// Bottom row is where console wide settings are held, and its "owned" by the parent

View file

@ -83,6 +83,12 @@
</property>
<item>
<widget class="QComboBox" name="comboControllerType">
<property name="minimumSize">
<size>
<width>0</width>
<height>21</height>
</size>
</property>
<item>
<property name="text">
<string>Pro Controller</string>
@ -136,6 +142,12 @@
</property>
<item>
<widget class="QComboBox" name="comboDevices">
<property name="minimumSize">
<size>
<width>0</width>
<height>21</height>
</size>
</property>
<item>
<property name="text">
<string>Any</string>
@ -152,14 +164,14 @@
<widget class="QPushButton" name="buttonRefreshDevices">
<property name="minimumSize">
<size>
<width>24</width>
<height>22</height>
<width>21</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>24</width>
<height>22</height>
<width>21</width>
<height>21</height>
</size>
</property>
<property name="styleSheet">
@ -198,18 +210,25 @@
<number>5</number>
</property>
<item>
<widget class="QComboBox" name="comboProfiles"/>
<widget class="QComboBox" name="comboProfiles">
<property name="minimumSize">
<size>
<width>0</width>
<height>21</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonProfilesSave">
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Save</string>
@ -220,12 +239,12 @@
<widget class="QPushButton" name="buttonProfilesNew">
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>New</string>
@ -236,12 +255,12 @@
<widget class="QPushButton" name="buttonProfilesDelete">
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Delete</string>
@ -393,18 +412,18 @@
<widget class="QPushButton" name="buttonLStickUp">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@ -463,18 +482,18 @@
<widget class="QPushButton" name="buttonLStickLeft">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@ -512,18 +531,18 @@
<widget class="QPushButton" name="buttonLStickRight">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@ -594,18 +613,18 @@
<widget class="QPushButton" name="buttonLStickDown">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@ -664,18 +683,18 @@
<widget class="QPushButton" name="buttonLStick">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Pressed</string>
@ -713,18 +732,18 @@
<widget class="QPushButton" name="buttonLStickMod">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Modifier</string>
@ -759,13 +778,13 @@
<widget class="QSpinBox" name="spinboxLStickRange">
<property name="minimumSize">
<size>
<width>55</width>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -966,18 +985,18 @@
<widget class="QPushButton" name="buttonDpadUp">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@ -1036,18 +1055,18 @@
<widget class="QPushButton" name="buttonDpadLeft">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@ -1085,18 +1104,18 @@
<widget class="QPushButton" name="buttonDpadRight">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@ -1167,18 +1186,18 @@
<widget class="QPushButton" name="buttonDpadDown">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@ -1292,18 +1311,18 @@
<widget class="QPushButton" name="buttonL">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>L</string>
@ -1341,18 +1360,18 @@
<widget class="QPushButton" name="buttonZL">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>ZL</string>
@ -1445,18 +1464,18 @@
<widget class="QPushButton" name="buttonMinus">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Minus</string>
@ -1494,18 +1513,18 @@
<widget class="QPushButton" name="buttonScreenshot">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Capture</string>
@ -1564,18 +1583,18 @@
<widget class="QPushButton" name="buttonPlus">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Plus</string>
@ -1613,18 +1632,18 @@
<widget class="QPushButton" name="buttonHome">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Home</string>
@ -1717,18 +1736,18 @@
<widget class="QPushButton" name="buttonR">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>R</string>
@ -1766,18 +1785,18 @@
<widget class="QPushButton" name="buttonZR">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>ZR</string>
@ -1870,18 +1889,18 @@
<widget class="QPushButton" name="buttonSL">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>SL</string>
@ -1919,18 +1938,18 @@
<widget class="QPushButton" name="buttonSR">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>SR</string>
@ -2027,18 +2046,18 @@
<widget class="QPushButton" name="buttonMotionLeft">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@ -2076,18 +2095,18 @@
<widget class="QPushButton" name="buttonMotionRight">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@ -2225,18 +2244,18 @@
<widget class="QPushButton" name="buttonX">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>X</string>
@ -2295,18 +2314,18 @@
<widget class="QPushButton" name="buttonY">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Y</string>
@ -2344,18 +2363,18 @@
<widget class="QPushButton" name="buttonA">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>A</string>
@ -2426,18 +2445,18 @@
<widget class="QPushButton" name="buttonB">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>B</string>
@ -2580,18 +2599,18 @@
<widget class="QPushButton" name="buttonRStickUp">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Up</string>
@ -2650,18 +2669,18 @@
<widget class="QPushButton" name="buttonRStickLeft">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Left</string>
@ -2699,18 +2718,18 @@
<widget class="QPushButton" name="buttonRStickRight">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Right</string>
@ -2781,18 +2800,18 @@
<widget class="QPushButton" name="buttonRStickDown">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Down</string>
@ -2851,18 +2870,18 @@
<widget class="QPushButton" name="buttonRStick">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Pressed</string>
@ -2900,18 +2919,18 @@
<widget class="QPushButton" name="buttonRStickMod">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 55px;</string>
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Modifier</string>
@ -2946,13 +2965,13 @@
<widget class="QSpinBox" name="spinboxRStickRange">
<property name="minimumSize">
<size>
<width>55</width>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>55</width>
<width>68</width>
<height>16777215</height>
</size>
</property>

View file

@ -0,0 +1,37 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "ui_configure_input_profile_dialog.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_profile_dialog.h"
ConfigureInputProfileDialog::ConfigureInputProfileDialog(
QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()),
profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) {
ui->setupUi(this);
ui->controllerLayout->addWidget(profile_widget);
connect(ui->clear_all_button, &QPushButton::clicked, this,
[this] { profile_widget->ClearAll(); });
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
[this] { profile_widget->RestoreDefaults(); });
RetranslateUI();
}
ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default;
void ConfigureInputProfileDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureInputProfileDialog::RetranslateUI() {
ui->retranslateUi(this);
}

View file

@ -0,0 +1,40 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
class QPushButton;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureInputProfileDialog;
}
class ConfigureInputProfileDialog : public QDialog {
Q_OBJECT
public:
explicit ConfigureInputProfileDialog(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles);
~ConfigureInputProfileDialog() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
std::unique_ptr<Ui::ConfigureInputProfileDialog> ui;
ConfigureInputPlayer* profile_widget;
};

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInputDialog</class>
<widget class="QDialog" name="ConfigureInputDialog">
<class>ConfigureInputProfileDialog</class>
<widget class="QDialog" name="ConfigureInputProfileDialog">
<property name="geometry">
<rect>
<x>0</x>
@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Configure Input</string>
<string>Create Input Profile</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
@ -30,10 +30,24 @@
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="inputLayout"/>
<layout class="QHBoxLayout" name="controllerLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="clear_all_button">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restore_defaults_button">
<property name="text">
<string>Defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
@ -50,7 +64,7 @@
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureInputDialog</receiver>
<receiver>ConfigureInputProfileDialog</receiver>
<slot>accept()</slot>
</connection>
</connections>

View file

@ -312,16 +312,6 @@
<signal>accepted()</signal>
<receiver>ConfigureMotionTouch</receiver>
<slot>ApplyConfiguration()</slot>
<hints>
<hint type="sourcelabel">
<x>220</x>
<y>380</y>
</hint>
<hint type="destinationlabel">
<x>220</x>
<y>200</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -15,7 +15,7 @@
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
min-width: 55px;
min-width: 60px;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@ -42,13 +42,13 @@
<widget class="QPushButton" name="forward_button">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -82,7 +82,7 @@
<widget class="QPushButton" name="back_button">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
@ -110,7 +110,7 @@
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
@ -138,13 +138,13 @@
<widget class="QPushButton" name="middle_button">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -204,13 +204,13 @@
<widget class="QPushButton" name="right_button">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -256,13 +256,13 @@
<widget class="QPushButton" name="buttonClearAll">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -275,13 +275,13 @@
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="minimumSize">
<size>
<width>57</width>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<width>68</width>
<height>16777215</height>
</size>
</property>
@ -324,32 +324,12 @@
<signal>accepted()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>124</x>
<y>266</y>
</hint>
<hint type="destinationlabel">
<x>124</x>
<y>143</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>124</x>
<y>266</y>
</hint>
<hint type="destinationlabel">
<x>124</x>
<y>143</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -29,7 +29,8 @@
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
game_config = std::make_unique<Config>(fmt::format("{:016X}.ini", title_id), false);
game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
Config::ConfigType::PerGameConfig);
Settings::SetConfiguringGlobal(false);

View file

@ -319,32 +319,12 @@
<signal>accepted()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -216,16 +216,6 @@ Drag points to change position, or double-click table cells to edit values.</str
<signal>rejected()</signal>
<receiver>ConfigureTouchFromButton</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>249</x>
<y>428</y>
</hint>
<hint type="destinationlabel">
<x>249</x>
<y>224</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -168,32 +168,12 @@
<signal>accepted()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTouchscreenAdvanced</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>140</x>
<y>318</y>
</hint>
<hint type="destinationlabel">
<x>140</x>
<y>169</y>
</hint>
</hints>
</connection>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,146 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <unordered_map>
#include <fmt/format.h>
#include "common/param_package.h"
#include "core/settings.h"
#include "ui_configure_vibration.h"
#include "yuzu/configuration/configure_vibration.h"
ConfigureVibration::ConfigureVibration(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureVibration>()) {
ui->setupUi(this);
vibration_groupboxes = {
ui->vibrationGroupPlayer1, ui->vibrationGroupPlayer2, ui->vibrationGroupPlayer3,
ui->vibrationGroupPlayer4, ui->vibrationGroupPlayer5, ui->vibrationGroupPlayer6,
ui->vibrationGroupPlayer7, ui->vibrationGroupPlayer8,
};
vibration_spinboxes = {
ui->vibrationSpinPlayer1, ui->vibrationSpinPlayer2, ui->vibrationSpinPlayer3,
ui->vibrationSpinPlayer4, ui->vibrationSpinPlayer5, ui->vibrationSpinPlayer6,
ui->vibrationSpinPlayer7, ui->vibrationSpinPlayer8,
};
const auto& players = Settings::values.players.GetValue();
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
vibration_groupboxes[i]->setChecked(players[i].vibration_enabled);
vibration_spinboxes[i]->setValue(players[i].vibration_strength);
}
ui->checkBoxAccurateVibration->setChecked(
Settings::values.enable_accurate_vibrations.GetValue());
if (!Settings::IsConfiguringGlobal()) {
ui->checkBoxAccurateVibration->setDisabled(true);
}
RetranslateUI();
}
ConfigureVibration::~ConfigureVibration() = default;
void ConfigureVibration::ApplyConfiguration() {
auto& players = Settings::values.players.GetValue();
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
players[i].vibration_enabled = vibration_groupboxes[i]->isChecked();
players[i].vibration_strength = vibration_spinboxes[i]->value();
}
Settings::values.enable_accurate_vibrations.SetValue(
ui->checkBoxAccurateVibration->isChecked());
}
void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
using namespace Settings::NativeButton;
static constexpr std::array<std::array<Settings::NativeButton::Values, 6>, 2> buttons{{
{DLeft, DUp, DRight, DDown, L, ZL}, // Left Buttons
{A, B, X, Y, R, ZR}, // Right Buttons
}};
auto& player = Settings::values.players.GetValue()[player_index];
for (std::size_t device_idx = 0; device_idx < buttons.size(); ++device_idx) {
std::unordered_map<std::string, int> params_count;
for (const auto button_index : buttons[device_idx]) {
const auto& player_button = player.buttons[button_index];
if (params_count.find(player_button) != params_count.end()) {
++params_count[player_button];
continue;
}
params_count.insert_or_assign(player_button, 1);
}
const auto it = std::max_element(
params_count.begin(), params_count.end(),
[](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
auto& vibration_param_str = player.vibrations[device_idx];
vibration_param_str.clear();
if (it->first.empty()) {
continue;
}
const auto param = Common::ParamPackage(it->first);
const auto engine = param.Get("engine", "");
const auto guid = param.Get("guid", "");
const auto port = param.Get("port", "");
if (engine.empty() || engine == "keyboard" || engine == "mouse") {
continue;
}
vibration_param_str += fmt::format("engine:{}", engine);
if (!port.empty()) {
vibration_param_str += fmt::format(",port:{}", port);
}
if (!guid.empty()) {
vibration_param_str += fmt::format(",guid:{}", guid);
}
}
if (player.vibrations[0] != player.vibrations[1]) {
return;
}
if (!player.vibrations[0].empty() &&
player.controller_type != Settings::ControllerType::RightJoycon) {
player.vibrations[1].clear();
} else if (!player.vibrations[1].empty() &&
player.controller_type == Settings::ControllerType::RightJoycon) {
player.vibrations[0].clear();
}
}
void ConfigureVibration::SetAllVibrationDevices() {
// Set vibration devices for all player indices including handheld
for (std::size_t player_idx = 0; player_idx < NUM_PLAYERS + 1; ++player_idx) {
SetVibrationDevices(player_idx);
}
}
void ConfigureVibration::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureVibration::RetranslateUI() {
ui->retranslateUi(this);
}

View file

@ -0,0 +1,43 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <QDialog>
class QGroupBox;
class QSpinBox;
namespace Ui {
class ConfigureVibration;
}
class ConfigureVibration : public QDialog {
Q_OBJECT
public:
explicit ConfigureVibration(QWidget* parent);
~ConfigureVibration() override;
void ApplyConfiguration();
static void SetVibrationDevices(std::size_t player_index);
static void SetAllVibrationDevices();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
std::unique_ptr<Ui::ConfigureVibration> ui;
static constexpr std::size_t NUM_PLAYERS = 8;
// Groupboxes encapsulating the vibration strength spinbox.
std::array<QGroupBox*, NUM_PLAYERS> vibration_groupboxes;
// Spinboxes representing the vibration strength percentage.
std::array<QSpinBox*, NUM_PLAYERS> vibration_spinboxes;
};

View file

@ -0,0 +1,546 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureVibration</class>
<widget class="QDialog" name="ConfigureVibration">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>364</width>
<height>242</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Vibration</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="vibrationStrengthGroup">
<property name="title">
<string>Vibration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" stretch="0,0">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QWidget" name="player14Widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer1">
<property name="title">
<string>Player 1</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer1">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer2">
<property name="title">
<string>Player 2</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer2">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer3">
<property name="title">
<string>Player 3</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer3">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer4">
<property name="title">
<string>Player 4</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer4">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="player58Widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer7">
<property name="title">
<string>Player 5</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer7">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer8">
<property name="title">
<string>Player 6</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer8">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer5">
<property name="title">
<string>Player 7</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer5">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroupPlayer6">
<property name="title">
<string>Player 8</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QSpinBox" name="vibrationSpinPlayer6">
<property name="minimumSize">
<size>
<width>68</width>
<height>21</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>150</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationSettingsGroup">
<property name="title">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="checkBoxAccurateVibration">
<property name="text">
<string>Enable Accurate Vibration</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="spacerVibration">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>167</width>
<height>55</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBoxVibration">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBoxVibration</sender>
<signal>accepted()</signal>
<receiver>ConfigureVibration</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBoxVibration</sender>
<signal>rejected()</signal>
<receiver>ConfigureVibration</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,131 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/input_profiles.h"
namespace FS = Common::FS;
namespace {
bool ProfileExistsInFilesystem(std::string_view profile_name) {
return FS::Exists(fmt::format("{}input" DIR_SEP "{}.ini",
FS::GetUserPath(FS::UserPath::ConfigDir), profile_name));
}
bool IsINI(std::string_view filename) {
const std::size_t index = filename.rfind('.');
if (index == std::string::npos) {
return false;
}
return filename.substr(index) == ".ini";
}
std::string GetNameWithoutExtension(const std::string& filename) {
const std::size_t index = filename.rfind('.');
if (index == std::string::npos) {
return filename;
}
return filename.substr(0, index);
}
} // namespace
InputProfiles::InputProfiles() {
const std::string input_profile_loc =
fmt::format("{}input", FS::GetUserPath(FS::UserPath::ConfigDir));
FS::ForeachDirectoryEntry(
nullptr, input_profile_loc,
[this](u64* entries_out, const std::string& directory, const std::string& filename) {
if (IsINI(filename) && IsProfileNameValid(GetNameWithoutExtension(filename))) {
map_profiles.insert_or_assign(
GetNameWithoutExtension(filename),
std::make_unique<Config>(GetNameWithoutExtension(filename),
Config::ConfigType::InputProfile));
}
return true;
});
}
InputProfiles::~InputProfiles() = default;
std::vector<std::string> InputProfiles::GetInputProfileNames() {
std::vector<std::string> profile_names;
profile_names.reserve(map_profiles.size());
for (const auto& [profile_name, config] : map_profiles) {
if (!ProfileExistsInFilesystem(profile_name)) {
DeleteProfile(profile_name);
continue;
}
profile_names.push_back(profile_name);
}
return profile_names;
}
bool InputProfiles::IsProfileNameValid(std::string_view profile_name) {
return profile_name.find_first_of("<>:;\"/\\|,.!?*") == std::string::npos;
}
bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t player_index) {
if (ProfileExistsInMap(profile_name)) {
return false;
}
map_profiles.insert_or_assign(
profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile));
return SaveProfile(profile_name, player_index);
}
bool InputProfiles::DeleteProfile(const std::string& profile_name) {
if (!ProfileExistsInMap(profile_name)) {
return false;
}
if (!ProfileExistsInFilesystem(profile_name) ||
FS::Delete(map_profiles[profile_name]->GetConfigFilePath())) {
map_profiles.erase(profile_name);
}
return !ProfileExistsInMap(profile_name) && !ProfileExistsInFilesystem(profile_name);
}
bool InputProfiles::LoadProfile(const std::string& profile_name, std::size_t player_index) {
if (!ProfileExistsInMap(profile_name)) {
return false;
}
if (!ProfileExistsInFilesystem(profile_name)) {
map_profiles.erase(profile_name);
return false;
}
map_profiles[profile_name]->ReadControlPlayerValue(player_index);
return true;
}
bool InputProfiles::SaveProfile(const std::string& profile_name, std::size_t player_index) {
if (!ProfileExistsInMap(profile_name)) {
return false;
}
map_profiles[profile_name]->SaveControlPlayerValue(player_index);
return true;
}
bool InputProfiles::ProfileExistsInMap(const std::string& profile_name) const {
return map_profiles.find(profile_name) != map_profiles.end();
}

View file

@ -0,0 +1,32 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <string_view>
#include <unordered_map>
class Config;
class InputProfiles {
public:
explicit InputProfiles();
virtual ~InputProfiles();
std::vector<std::string> GetInputProfileNames();
static bool IsProfileNameValid(std::string_view profile_name);
bool CreateProfile(const std::string& profile_name, std::size_t player_index);
bool DeleteProfile(const std::string& profile_name);
bool LoadProfile(const std::string& profile_name, std::size_t player_index);
bool SaveProfile(const std::string& profile_name, std::size_t player_index);
private:
bool ProfileExistsInMap(const std::string& profile_name) const;
std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles;
};

View file

@ -18,6 +18,7 @@
#include "applets/web_browser.h"
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_vibration.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/controller.h"
@ -50,12 +51,14 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDialogButtonBox>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QProgressBar>
#include <QProgressDialog>
#include <QPushButton>
#include <QShortcut>
#include <QStatusBar>
#include <QSysInfo>
@ -277,6 +280,8 @@ GMainWindow::GMainWindow()
if (args.length() >= 2) {
BootGame(args[1]);
}
MigrateConfigFiles();
}
GMainWindow::~GMainWindow() {
@ -288,6 +293,7 @@ GMainWindow::~GMainWindow() {
void GMainWindow::ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters) {
QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get());
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint |
Qt::WindowTitleHint | Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
@ -547,13 +553,14 @@ void GMainWindow::InitializeWidgets() {
dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
dock_status_button->setFocusPolicy(Qt::NoFocus);
connect(dock_status_button, &QPushButton::clicked, [&] {
Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
dock_status_button->setChecked(Settings::values.use_docked_mode);
OnDockedModeChanged(!Settings::values.use_docked_mode, Settings::values.use_docked_mode);
Settings::values.use_docked_mode.SetValue(!Settings::values.use_docked_mode.GetValue());
dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
Settings::values.use_docked_mode.GetValue());
});
dock_status_button->setText(tr("DOCK"));
dock_status_button->setCheckable(true);
dock_status_button->setChecked(Settings::values.use_docked_mode);
dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
statusBar()->insertPermanentWidget(0, dock_status_button);
// Setup ASync button
@ -792,10 +799,11 @@ void GMainWindow::InitializeHotkeys() {
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Change Docked Mode"), this),
&QShortcut::activated, this, [&] {
Settings::values.use_docked_mode = !Settings::values.use_docked_mode;
OnDockedModeChanged(!Settings::values.use_docked_mode,
Settings::values.use_docked_mode);
dock_status_button->setChecked(Settings::values.use_docked_mode);
Settings::values.use_docked_mode.SetValue(
!Settings::values.use_docked_mode.GetValue());
OnDockedModeChanged(!Settings::values.use_docked_mode.GetValue(),
Settings::values.use_docked_mode.GetValue());
dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
});
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Mute Audio"), this),
&QShortcut::activated, this,
@ -1087,9 +1095,11 @@ void GMainWindow::BootGame(const QString& filename) {
const auto loader = Loader::GetLoader(v_file);
if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) {
// Load per game settings
Config per_game_config(fmt::format("{:016X}.ini", title_id), false);
Config per_game_config(fmt::format("{:016X}", title_id), Config::ConfigType::PerGameConfig);
}
ConfigureVibration::SetAllVibrationDevices();
Settings::LogSettings();
if (UISettings::values.select_user_on_boot) {
@ -1577,7 +1587,8 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id) {
const QString config_dir =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir));
const QString custom_config_file_path =
config_dir + QString::fromStdString(fmt::format("{:016X}.ini", program_id));
config_dir + QStringLiteral("custom") + QDir::separator() +
QString::fromStdString(fmt::format("{:016X}.ini", program_id));
if (!QFile::exists(custom_config_file_path)) {
QMessageBox::warning(this, tr("Error Removing Custom Configuration"),
@ -2393,6 +2404,29 @@ void GMainWindow::OnCaptureScreenshot() {
OnStartGame();
}
// TODO: Written 2020-10-01: Remove per-game config migration code when it is irrelevant
void GMainWindow::MigrateConfigFiles() {
const std::string& config_dir_str = Common::FS::GetUserPath(Common::FS::UserPath::ConfigDir);
const QDir config_dir = QDir(QString::fromStdString(config_dir_str));
const QStringList config_dir_list = config_dir.entryList(QStringList(QStringLiteral("*.ini")));
Common::FS::CreateFullPath(fmt::format("{}custom" DIR_SEP, config_dir_str));
for (QStringList::const_iterator it = config_dir_list.constBegin();
it != config_dir_list.constEnd(); ++it) {
const auto filename = it->toStdString();
if (filename.find_first_not_of("0123456789abcdefACBDEF", 0) < 16) {
continue;
}
const auto origin = fmt::format("{}{}", config_dir_str, filename);
const auto destination = fmt::format("{}custom" DIR_SEP "{}", config_dir_str, filename);
LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
if (!Common::FS::Rename(origin, destination)) {
// Delete the old config file if one already exists in the new location.
Common::FS::Delete(origin);
}
}
}
void GMainWindow::UpdateWindowTitle(const std::string& title_name,
const std::string& title_version) {
const auto full_name = std::string(Common::g_build_fullname);
@ -2450,7 +2484,7 @@ void GMainWindow::UpdateStatusBar() {
}
void GMainWindow::UpdateStatusButtons() {
dock_status_button->setChecked(Settings::values.use_docked_mode);
dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
Settings::values.use_asynchronous_gpu_emulation.SetValue(
Settings::values.use_asynchronous_gpu_emulation.GetValue() ||

View file

@ -251,6 +251,7 @@ private:
std::optional<u64> SelectRomFSDumpTarget(const FileSys::ContentProvider&, u64 program_id);
InstallResult InstallNSPXCI(const QString& filename);
InstallResult InstallNCA(const QString& filename);
void MigrateConfigFiles();
void UpdateWindowTitle(const std::string& title_name = {},
const std::string& title_version = {});
void UpdateStatusBar();

View file

@ -228,24 +228,24 @@ static const std::array<int, 8> keyboard_mods{
void Config::ReadValues() {
// Controls
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
const auto group = fmt::format("ControlsP{}", p);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.players[p].buttons[i] =
Settings::values.players.GetValue()[p].buttons[i] =
sdl2_config->Get(group, Settings::NativeButton::mapping[i], default_param);
if (Settings::values.players[p].buttons[i].empty())
Settings::values.players[p].buttons[i] = default_param;
if (Settings::values.players.GetValue()[p].buttons[i].empty())
Settings::values.players.GetValue()[p].buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
Settings::values.players[p].analogs[i] =
Settings::values.players.GetValue()[p].analogs[i] =
sdl2_config->Get(group, Settings::NativeAnalog::mapping[i], default_param);
if (Settings::values.players[p].analogs[i].empty())
Settings::values.players[p].analogs[i] = default_param;
if (Settings::values.players.GetValue()[p].analogs[i].empty())
Settings::values.players.GetValue()[p].analogs[i] = default_param;
}
}
@ -288,10 +288,12 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = default_param;
}
Settings::values.vibration_enabled =
sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true);
Settings::values.motion_enabled =
sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true);
Settings::values.vibration_enabled.SetValue(
sdl2_config->GetBoolean("ControlsGeneral", "vibration_enabled", true));
Settings::values.enable_accurate_vibrations.SetValue(
sdl2_config->GetBoolean("ControlsGeneral", "enable_accurate_vibrations", false));
Settings::values.motion_enabled.SetValue(
sdl2_config->GetBoolean("ControlsGeneral", "motion_enabled", true));
Settings::values.touchscreen.enabled =
sdl2_config->GetBoolean("ControlsGeneral", "touch_enabled", true);
Settings::values.touchscreen.device =
@ -343,7 +345,8 @@ void Config::ReadValues() {
Settings::values.gamecard_path = sdl2_config->Get("Data Storage", "gamecard_path", "");
// System
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
Settings::values.use_docked_mode.SetValue(
sdl2_config->GetBoolean("System", "use_docked_mode", false));
const auto size = sdl2_config->GetInteger("System", "users_size", 0);
Settings::values.current_user = std::clamp<int>(

View file

@ -65,6 +65,14 @@ button_screenshot=
lstick=
rstick=
# Whether to enable or disable vibration
# 0: Disabled, 1 (default): Enabled
vibration_enabled=
# Whether to enable or disable accurate vibrations
# 0 (default): Disabled, 1: Enabled
enable_accurate_vibrations=
# for motion input, the following devices are available:
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
# - "update_period": update period in milliseconds (default to 100)

View file

@ -47,13 +47,13 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
void Config::ReadValues() {
// Controls
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
Settings::values.players[p].buttons[i] = "";
Settings::values.players.GetValue()[p].buttons[i] = "";
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
Settings::values.players[p].analogs[i] = "";
Settings::values.players.GetValue()[p].analogs[i] = "";
}
}
@ -75,8 +75,9 @@ void Config::ReadValues() {
Settings::values.debug_pad_analogs[i] = "";
}
Settings::values.vibration_enabled = true;
Settings::values.motion_enabled = true;
Settings::values.vibration_enabled.SetValue(true);
Settings::values.enable_accurate_vibrations.SetValue(false);
Settings::values.motion_enabled.SetValue(true);
Settings::values.touchscreen.enabled = "";
Settings::values.touchscreen.device = "";
Settings::values.touchscreen.finger = 0;
@ -84,8 +85,8 @@ void Config::ReadValues() {
Settings::values.touchscreen.diameter_x = 15;
Settings::values.touchscreen.diameter_y = 15;
Settings::values.use_docked_mode =
sdl2_config->GetBoolean("Controls", "use_docked_mode", false);
Settings::values.use_docked_mode.SetValue(
sdl2_config->GetBoolean("Controls", "use_docked_mode", false));
// Data Storage
Settings::values.use_virtual_sd =