Merge pull request #4597 from Morph1984/mjolnir-p2
Project Mjölnir: Part 2 - Controller Appletmaster
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.5 KiB |
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/applets/controller.h"
|
||||||
|
#include "core/hle/service/hid/controllers/npad.h"
|
||||||
|
#include "core/hle/service/hid/hid.h"
|
||||||
|
#include "core/hle/service/sm/sm.h"
|
||||||
|
|
||||||
|
namespace Core::Frontend {
|
||||||
|
|
||||||
|
ControllerApplet::~ControllerApplet() = default;
|
||||||
|
|
||||||
|
DefaultControllerApplet::~DefaultControllerApplet() = default;
|
||||||
|
|
||||||
|
void DefaultControllerApplet::ReconfigureControllers(std::function<void()> callback,
|
||||||
|
ControllerParameters parameters) const {
|
||||||
|
LOG_INFO(Service_HID, "called, deducing the best configuration based on the given parameters!");
|
||||||
|
|
||||||
|
auto& npad =
|
||||||
|
Core::System::GetInstance()
|
||||||
|
.ServiceManager()
|
||||||
|
.GetService<Service::HID::Hid>("hid")
|
||||||
|
->GetAppletResource()
|
||||||
|
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
|
||||||
|
|
||||||
|
auto& players = Settings::values.players;
|
||||||
|
|
||||||
|
const std::size_t min_supported_players =
|
||||||
|
parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||||
|
|
||||||
|
// Disconnect Handheld first.
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Only connect the minimum number of required players.
|
||||||
|
if (index >= min_supported_players) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect controllers based on the following priority list from highest to lowest priority:
|
||||||
|
// Pro Controller -> Dual Joycons -> Left Joycon/Right Joycon -> Handheld
|
||||||
|
if (parameters.allow_pro_controller) {
|
||||||
|
npad.AddNewControllerAt(
|
||||||
|
npad.MapSettingsTypeToNPad(Settings::ControllerType::ProController), index);
|
||||||
|
} else if (parameters.allow_dual_joycons) {
|
||||||
|
npad.AddNewControllerAt(
|
||||||
|
npad.MapSettingsTypeToNPad(Settings::ControllerType::DualJoyconDetached), index);
|
||||||
|
} else if (parameters.allow_left_joycon && parameters.allow_right_joycon) {
|
||||||
|
// Assign left joycons to even player indices and right joycons to odd player indices.
|
||||||
|
// We do this since Captain Toad Treasure Tracker expects a left joycon for Player 1 and
|
||||||
|
// a right Joycon for Player 2 in 2 Player Assist mode.
|
||||||
|
if (index % 2 == 0) {
|
||||||
|
npad.AddNewControllerAt(
|
||||||
|
npad.MapSettingsTypeToNPad(Settings::ControllerType::LeftJoycon), index);
|
||||||
|
} else {
|
||||||
|
npad.AddNewControllerAt(
|
||||||
|
npad.MapSettingsTypeToNPad(Settings::ControllerType::RightJoycon), index);
|
||||||
|
}
|
||||||
|
} else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld &&
|
||||||
|
!Settings::values.use_docked_mode) {
|
||||||
|
// We should *never* reach here under any normal circumstances.
|
||||||
|
npad.AddNewControllerAt(npad.MapSettingsTypeToNPad(Settings::ControllerType::Handheld),
|
||||||
|
index);
|
||||||
|
} else {
|
||||||
|
UNREACHABLE_MSG("Unable to add a new controller based on the given parameters!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Core::Frontend
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Core::Frontend {
|
||||||
|
|
||||||
|
using BorderColor = std::array<u8, 4>;
|
||||||
|
using ExplainText = std::array<char, 0x81>;
|
||||||
|
|
||||||
|
struct ControllerParameters {
|
||||||
|
s8 min_players{};
|
||||||
|
s8 max_players{};
|
||||||
|
bool keep_controllers_connected{};
|
||||||
|
bool enable_single_mode{};
|
||||||
|
bool enable_border_color{};
|
||||||
|
std::vector<BorderColor> border_colors{};
|
||||||
|
bool enable_explain_text{};
|
||||||
|
std::vector<ExplainText> explain_text{};
|
||||||
|
bool allow_pro_controller{};
|
||||||
|
bool allow_handheld{};
|
||||||
|
bool allow_dual_joycons{};
|
||||||
|
bool allow_left_joycon{};
|
||||||
|
bool allow_right_joycon{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class ControllerApplet {
|
||||||
|
public:
|
||||||
|
virtual ~ControllerApplet();
|
||||||
|
|
||||||
|
virtual void ReconfigureControllers(std::function<void()> callback,
|
||||||
|
ControllerParameters parameters) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultControllerApplet final : public ControllerApplet {
|
||||||
|
public:
|
||||||
|
~DefaultControllerApplet() override;
|
||||||
|
|
||||||
|
void ReconfigureControllers(std::function<void()> callback,
|
||||||
|
ControllerParameters parameters) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Core::Frontend
|
@ -0,0 +1,210 @@
|
|||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/frontend/applets/controller.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/am/am.h"
|
||||||
|
#include "core/hle/service/am/applets/controller.h"
|
||||||
|
#include "core/hle/service/hid/controllers/npad.h"
|
||||||
|
|
||||||
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
|
// This error code (0x183ACA) is thrown when the applet fails to initialize.
|
||||||
|
[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3101{ErrorModule::HID, 3101};
|
||||||
|
// This error code (0x183CCA) is thrown when the u32 result in ControllerSupportResultInfo is 2.
|
||||||
|
[[maybe_unused]] constexpr ResultCode ERR_CONTROLLER_APPLET_3102{ErrorModule::HID, 3102};
|
||||||
|
|
||||||
|
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;
|
||||||
|
npad_style_set.raw = private_arg.style_set;
|
||||||
|
|
||||||
|
return {
|
||||||
|
.min_players = std::max(s8(1), header.player_count_min),
|
||||||
|
.max_players = header.player_count_max,
|
||||||
|
.keep_controllers_connected = header.enable_take_over_connection,
|
||||||
|
.enable_single_mode = header.enable_single_mode,
|
||||||
|
.enable_border_color = header.enable_identification_color,
|
||||||
|
.border_colors = identification_colors,
|
||||||
|
.enable_explain_text = enable_text,
|
||||||
|
.explain_text = text,
|
||||||
|
.allow_pro_controller = npad_style_set.pro_controller == 1,
|
||||||
|
.allow_handheld = npad_style_set.handheld == 1,
|
||||||
|
.allow_dual_joycons = npad_style_set.joycon_dual == 1,
|
||||||
|
.allow_left_joycon = npad_style_set.joycon_left == 1,
|
||||||
|
.allow_right_joycon = npad_style_set.joycon_right == 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller::Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_)
|
||||||
|
: Applet{system_.Kernel()}, frontend(frontend_) {}
|
||||||
|
|
||||||
|
Controller::~Controller() = default;
|
||||||
|
|
||||||
|
void Controller::Initialize() {
|
||||||
|
Applet::Initialize();
|
||||||
|
|
||||||
|
LOG_INFO(Service_HID, "Initializing Controller Applet.");
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_HID,
|
||||||
|
"Initializing Applet with common_args: arg_version={}, lib_version={}, "
|
||||||
|
"play_startup_sound={}, size={}, system_tick={}, theme_color={}",
|
||||||
|
common_args.arguments_version, common_args.library_version,
|
||||||
|
common_args.play_startup_sound, common_args.size, common_args.system_tick,
|
||||||
|
common_args.theme_color);
|
||||||
|
|
||||||
|
library_applet_version = LibraryAppletVersion{common_args.library_version};
|
||||||
|
|
||||||
|
const auto private_arg_storage = broker.PopNormalDataToApplet();
|
||||||
|
ASSERT(private_arg_storage != nullptr);
|
||||||
|
|
||||||
|
const auto& private_arg = private_arg_storage->GetData();
|
||||||
|
ASSERT(private_arg.size() == sizeof(ControllerSupportArgPrivate));
|
||||||
|
|
||||||
|
std::memcpy(&controller_private_arg, private_arg.data(), sizeof(ControllerSupportArgPrivate));
|
||||||
|
ASSERT_MSG(controller_private_arg.arg_private_size == sizeof(ControllerSupportArgPrivate),
|
||||||
|
"Unknown ControllerSupportArgPrivate revision={} with size={}",
|
||||||
|
library_applet_version, controller_private_arg.arg_private_size);
|
||||||
|
|
||||||
|
switch (controller_private_arg.mode) {
|
||||||
|
case ControllerSupportMode::ShowControllerSupport: {
|
||||||
|
const auto user_arg_storage = broker.PopNormalDataToApplet();
|
||||||
|
ASSERT(user_arg_storage != nullptr);
|
||||||
|
|
||||||
|
const auto& user_arg = user_arg_storage->GetData();
|
||||||
|
switch (library_applet_version) {
|
||||||
|
case LibraryAppletVersion::Version3:
|
||||||
|
case LibraryAppletVersion::Version4:
|
||||||
|
case LibraryAppletVersion::Version5:
|
||||||
|
ASSERT(user_arg.size() == sizeof(ControllerSupportArgOld));
|
||||||
|
std::memcpy(&controller_user_arg_old, user_arg.data(), sizeof(ControllerSupportArgOld));
|
||||||
|
break;
|
||||||
|
case LibraryAppletVersion::Version7:
|
||||||
|
ASSERT(user_arg.size() == sizeof(ControllerSupportArgNew));
|
||||||
|
std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Unknown ControllerSupportArg revision={} with size={}",
|
||||||
|
library_applet_version, controller_private_arg.arg_size);
|
||||||
|
ASSERT(user_arg.size() >= sizeof(ControllerSupportArgNew));
|
||||||
|
std::memcpy(&controller_user_arg_new, user_arg.data(), sizeof(ControllerSupportArgNew));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ControllerSupportMode::ShowControllerStrapGuide:
|
||||||
|
case ControllerSupportMode::ShowControllerFirmwareUpdate:
|
||||||
|
default: {
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented ControllerSupportMode={}", controller_private_arg.mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Controller::TransactionComplete() const {
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode Controller::GetStatus() const {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::ExecuteInteractive() {
|
||||||
|
UNREACHABLE_MSG("Attempted to call interactive execution on non-interactive applet.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::Execute() {
|
||||||
|
switch (controller_private_arg.mode) {
|
||||||
|
case ControllerSupportMode::ShowControllerSupport: {
|
||||||
|
const auto parameters = [this] {
|
||||||
|
switch (library_applet_version) {
|
||||||
|
case LibraryAppletVersion::Version3:
|
||||||
|
case LibraryAppletVersion::Version4:
|
||||||
|
case LibraryAppletVersion::Version5:
|
||||||
|
return ConvertToFrontendParameters(
|
||||||
|
controller_private_arg, controller_user_arg_old.header,
|
||||||
|
controller_user_arg_old.enable_explain_text,
|
||||||
|
std::vector<IdentificationColor>(
|
||||||
|
controller_user_arg_old.identification_colors.begin(),
|
||||||
|
controller_user_arg_old.identification_colors.end()),
|
||||||
|
std::vector<ExplainText>(controller_user_arg_old.explain_text.begin(),
|
||||||
|
controller_user_arg_old.explain_text.end()));
|
||||||
|
case LibraryAppletVersion::Version7:
|
||||||
|
default:
|
||||||
|
return ConvertToFrontendParameters(
|
||||||
|
controller_private_arg, controller_user_arg_new.header,
|
||||||
|
controller_user_arg_new.enable_explain_text,
|
||||||
|
std::vector<IdentificationColor>(
|
||||||
|
controller_user_arg_new.identification_colors.begin(),
|
||||||
|
controller_user_arg_new.identification_colors.end()),
|
||||||
|
std::vector<ExplainText>(controller_user_arg_new.explain_text.begin(),
|
||||||
|
controller_user_arg_new.explain_text.end()));
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
is_single_mode = parameters.enable_single_mode;
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_HID,
|
||||||
|
"Controller Parameters: min_players={}, max_players={}, "
|
||||||
|
"keep_controllers_connected={}, enable_single_mode={}, enable_border_color={}, "
|
||||||
|
"enable_explain_text={}, allow_pro_controller={}, allow_handheld={}, "
|
||||||
|
"allow_dual_joycons={}, allow_left_joycon={}, allow_right_joycon={}",
|
||||||
|
parameters.min_players, parameters.max_players,
|
||||||
|
parameters.keep_controllers_connected, parameters.enable_single_mode,
|
||||||
|
parameters.enable_border_color, parameters.enable_explain_text,
|
||||||
|
parameters.allow_pro_controller, parameters.allow_handheld,
|
||||||
|
parameters.allow_dual_joycons, parameters.allow_left_joycon,
|
||||||
|
parameters.allow_right_joycon);
|
||||||
|
|
||||||
|
frontend.ReconfigureControllers([this] { ConfigurationComplete(); }, parameters);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ControllerSupportMode::ShowControllerStrapGuide:
|
||||||
|
case ControllerSupportMode::ShowControllerFirmwareUpdate:
|
||||||
|
default: {
|
||||||
|
ConfigurationComplete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::ConfigurationComplete() {
|
||||||
|
ControllerSupportResultInfo result_info{};
|
||||||
|
|
||||||
|
const auto& players = Settings::values.players;
|
||||||
|
|
||||||
|
// If enable_single_mode is enabled, player_count is 1 regardless of any other parameters.
|
||||||
|
// Otherwise, only count connected players from P1-P8.
|
||||||
|
result_info.player_count =
|
||||||
|
is_single_mode ? 1
|
||||||
|
: static_cast<s8>(std::count_if(
|
||||||
|
players.begin(), players.end() - 2,
|
||||||
|
[](Settings::PlayerInput player) { return player.connected; }));
|
||||||
|
|
||||||
|
result_info.selected_id = HID::Controller_NPad::IndexToNPad(
|
||||||
|
std::distance(players.begin(),
|
||||||
|
std::find_if(players.begin(), players.end(),
|
||||||
|
[](Settings::PlayerInput player) { return player.connected; })));
|
||||||
|
|
||||||
|
result_info.result = 0;
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_HID, "Result Info: player_count={}, selected_id={}, result={}",
|
||||||
|
result_info.player_count, result_info.selected_id, result_info.result);
|
||||||
|
|
||||||
|
complete = true;
|
||||||
|
out_data = std::vector<u8>(sizeof(ControllerSupportResultInfo));
|
||||||
|
std::memcpy(out_data.data(), &result_info, out_data.size());
|
||||||
|
broker.PushNormalDataFromApplet(std::make_shared<IStorage>(std::move(out_data)));
|
||||||
|
broker.SignalStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::AM::Applets
|
@ -0,0 +1,123 @@
|
|||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/am/applets/applets.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Service::AM::Applets {
|
||||||
|
|
||||||
|
using IdentificationColor = std::array<u8, 4>;
|
||||||
|
using ExplainText = std::array<char, 0x81>;
|
||||||
|
|
||||||
|
enum class LibraryAppletVersion : u32_le {
|
||||||
|
Version3 = 0x3, // 1.0.0 - 2.3.0
|
||||||
|
Version4 = 0x4, // 3.0.0 - 5.1.0
|
||||||
|
Version5 = 0x5, // 6.0.0 - 7.0.1
|
||||||
|
Version7 = 0x7, // 8.0.0+
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControllerSupportMode : u8 {
|
||||||
|
ShowControllerSupport = 0,
|
||||||
|
ShowControllerStrapGuide = 1,
|
||||||
|
ShowControllerFirmwareUpdate = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ControllerSupportCaller : u8 {
|
||||||
|
Application = 0,
|
||||||
|
System = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ControllerSupportArgPrivate {
|
||||||
|
u32 arg_private_size{};
|
||||||
|
u32 arg_size{};
|
||||||
|
bool flag_0{};
|
||||||
|
bool flag_1{};
|
||||||
|
ControllerSupportMode mode{};
|
||||||
|
ControllerSupportCaller caller{};
|
||||||
|
u32 style_set{};
|
||||||
|
u32 joy_hold_type{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerSupportArgPrivate) == 0x14,
|
||||||
|
"ControllerSupportArgPrivate has incorrect size.");
|
||||||
|
|
||||||
|
struct ControllerSupportArgHeader {
|
||||||
|
s8 player_count_min{};
|
||||||
|
s8 player_count_max{};
|
||||||
|
bool enable_take_over_connection{};
|
||||||
|
bool enable_left_justify{};
|
||||||
|
bool enable_permit_joy_dual{};
|
||||||
|
bool enable_single_mode{};
|
||||||
|
bool enable_identification_color{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerSupportArgHeader) == 0x7,
|
||||||
|
"ControllerSupportArgHeader has incorrect size.");
|
||||||
|
|
||||||
|
// LibraryAppletVersion 0x3, 0x4, 0x5
|
||||||
|
struct ControllerSupportArgOld {
|
||||||
|
ControllerSupportArgHeader header{};
|
||||||
|
std::array<IdentificationColor, 4> identification_colors{};
|
||||||
|
bool enable_explain_text{};
|
||||||
|
std::array<ExplainText, 4> explain_text{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerSupportArgOld) == 0x21C,
|
||||||
|
"ControllerSupportArgOld has incorrect size.");
|
||||||
|
|
||||||
|
// LibraryAppletVersion 0x7
|
||||||
|
struct ControllerSupportArgNew {
|
||||||
|
ControllerSupportArgHeader header{};
|
||||||
|
std::array<IdentificationColor, 8> identification_colors{};
|
||||||
|
bool enable_explain_text{};
|
||||||
|
std::array<ExplainText, 8> explain_text{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerSupportArgNew) == 0x430,
|
||||||
|
"ControllerSupportArgNew has incorrect size.");
|
||||||
|
|
||||||
|
struct ControllerSupportResultInfo {
|
||||||
|
s8 player_count{};
|
||||||
|
INSERT_PADDING_BYTES(3);
|
||||||
|
u32 selected_id{};
|
||||||
|
u32 result{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerSupportResultInfo) == 0xC,
|
||||||
|
"ControllerSupportResultInfo has incorrect size.");
|
||||||
|
|
||||||
|
class Controller final : public Applet {
|
||||||
|
public:
|
||||||
|
explicit Controller(Core::System& system_, const Core::Frontend::ControllerApplet& frontend_);
|
||||||
|
~Controller() override;
|
||||||
|
|
||||||
|
void Initialize() override;
|
||||||
|
|
||||||
|
bool TransactionComplete() const override;
|
||||||
|
ResultCode GetStatus() const override;
|
||||||
|
void ExecuteInteractive() override;
|
||||||
|
void Execute() override;
|
||||||
|
|
||||||
|
void ConfigurationComplete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Core::Frontend::ControllerApplet& frontend;
|
||||||
|
|
||||||
|
LibraryAppletVersion library_applet_version;
|
||||||
|
ControllerSupportArgPrivate controller_private_arg;
|
||||||
|
ControllerSupportArgOld controller_user_arg_old;
|
||||||
|
ControllerSupportArgNew controller_user_arg_new;
|
||||||
|
bool complete{false};
|
||||||
|
ResultCode status{RESULT_SUCCESS};
|
||||||
|
bool is_single_mode{false};
|
||||||
|
std::vector<u8> out_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::AM::Applets
|
@ -0,0 +1,601 @@
|
|||||||
|
// Copyright 2020 yuzu Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/hle/lock.h"
|
||||||
|
#include "core/hle/service/hid/controllers/npad.h"
|
||||||
|
#include "core/hle/service/hid/hid.h"
|
||||||
|
#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/main.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr std::array<std::array<bool, 4>, 8> led_patterns = {{
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{1, 1, 0, 0},
|
||||||
|
{1, 1, 1, 0},
|
||||||
|
{1, 1, 1, 1},
|
||||||
|
{1, 0, 0, 1},
|
||||||
|
{1, 0, 1, 0},
|
||||||
|
{1, 0, 1, 1},
|
||||||
|
{0, 1, 1, 0},
|
||||||
|
}};
|
||||||
|
|
||||||
|
void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
|
||||||
|
bool connected) {
|
||||||
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
|
if (!system.IsPoweredOn()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service::SM::ServiceManager& sm = system.ServiceManager();
|
||||||
|
|
||||||
|
auto& npad =
|
||||||
|
sm.GetService<Service::HID::Hid>("hid")
|
||||||
|
->GetAppletResource()
|
||||||
|
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
|
||||||
|
|
||||||
|
npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if the given controller type is compatible with the given parameters.
|
||||||
|
bool IsControllerCompatible(Settings::ControllerType controller_type,
|
||||||
|
Core::Frontend::ControllerParameters parameters) {
|
||||||
|
switch (controller_type) {
|
||||||
|
case Settings::ControllerType::ProController:
|
||||||
|
return parameters.allow_pro_controller;
|
||||||
|
case Settings::ControllerType::DualJoyconDetached:
|
||||||
|
return parameters.allow_dual_joycons;
|
||||||
|
case Settings::ControllerType::LeftJoycon:
|
||||||
|
return parameters.allow_left_joycon;
|
||||||
|
case Settings::ControllerType::RightJoycon:
|
||||||
|
return parameters.allow_right_joycon;
|
||||||
|
case Settings::ControllerType::Handheld:
|
||||||
|
return parameters.enable_single_mode && parameters.allow_handheld;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the controller type combobox index to Controller Type enum
|
||||||
|
constexpr Settings::ControllerType GetControllerTypeFromIndex(int index) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
return Settings::ControllerType::ProController;
|
||||||
|
case 1:
|
||||||
|
return Settings::ControllerType::DualJoyconDetached;
|
||||||
|
case 2:
|
||||||
|
return Settings::ControllerType::LeftJoycon;
|
||||||
|
case 3:
|
||||||
|
return Settings::ControllerType::RightJoycon;
|
||||||
|
case 4:
|
||||||
|
return Settings::ControllerType::Handheld;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the Controller Type enum to controller type combobox index
|
||||||
|
constexpr int GetIndexFromControllerType(Settings::ControllerType type) {
|
||||||
|
switch (type) {
|
||||||
|
case Settings::ControllerType::ProController:
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
case Settings::ControllerType::DualJoyconDetached:
|
||||||
|
return 1;
|
||||||
|
case Settings::ControllerType::LeftJoycon:
|
||||||
|
return 2;
|
||||||
|
case Settings::ControllerType::RightJoycon:
|
||||||
|
return 3;
|
||||||
|
case Settings::ControllerType::Handheld:
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
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_) {
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
player_widgets = {
|
||||||
|
ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
|
||||||
|
ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
|
||||||
|
};
|
||||||
|
|
||||||
|
player_groupboxes = {
|
||||||
|
ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
|
||||||
|
ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
|
||||||
|
ui->groupPlayer7Connected, ui->groupPlayer8Connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
connected_controller_icons = {
|
||||||
|
ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
|
||||||
|
ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
|
||||||
|
};
|
||||||
|
|
||||||
|
led_patterns_boxes = {{
|
||||||
|
{ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
|
||||||
|
ui->checkboxPlayer1LED4},
|
||||||
|
{ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
|
||||||
|
ui->checkboxPlayer2LED4},
|
||||||
|
{ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
|
||||||
|
ui->checkboxPlayer3LED4},
|
||||||
|
{ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
|
||||||
|
ui->checkboxPlayer4LED4},
|
||||||
|
{ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
|
||||||
|
ui->checkboxPlayer5LED4},
|
||||||
|
{ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
|
||||||
|
ui->checkboxPlayer6LED4},
|
||||||
|
{ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
|
||||||
|
ui->checkboxPlayer7LED4},
|
||||||
|
{ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
|
||||||
|
ui->checkboxPlayer8LED4},
|
||||||
|
}};
|
||||||
|
|
||||||
|
explain_text_labels = {
|
||||||
|
ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
|
||||||
|
ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
|
||||||
|
ui->labelPlayer7Explain, ui->labelPlayer8Explain,
|
||||||
|
};
|
||||||
|
|
||||||
|
emulated_controllers = {
|
||||||
|
ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
|
||||||
|
ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
|
||||||
|
ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
|
||||||
|
};
|
||||||
|
|
||||||
|
player_labels = {
|
||||||
|
ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
|
||||||
|
ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
|
||||||
|
};
|
||||||
|
|
||||||
|
connected_controller_labels = {
|
||||||
|
ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
|
||||||
|
ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
|
||||||
|
ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
|
||||||
|
};
|
||||||
|
|
||||||
|
connected_controller_checkboxes = {
|
||||||
|
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
|
||||||
|
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
|
||||||
|
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup/load everything prior to setting up connections.
|
||||||
|
// This avoids unintentionally changing the states of elements while loading them in.
|
||||||
|
SetSupportedControllers();
|
||||||
|
DisableUnsupportedPlayers();
|
||||||
|
LoadConfiguration();
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
|
||||||
|
SetExplainText(i);
|
||||||
|
UpdateControllerIcon(i);
|
||||||
|
UpdateLEDPattern(i);
|
||||||
|
UpdateBorderColor(i);
|
||||||
|
|
||||||
|
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
for (std::size_t index = 0; index <= i; ++index) {
|
||||||
|
connected_controller_checkboxes[index]->setChecked(checked);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
|
||||||
|
connected_controller_checkboxes[index]->setChecked(checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||||
|
[this, i](int) {
|
||||||
|
UpdateControllerIcon(i);
|
||||||
|
UpdateControllerState(i);
|
||||||
|
UpdateLEDPattern(i);
|
||||||
|
CheckIfParametersMet();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
|
||||||
|
player_groupboxes[i]->setChecked(state == Qt::Checked);
|
||||||
|
UpdateControllerIcon(i);
|
||||||
|
UpdateControllerState(i);
|
||||||
|
UpdateLEDPattern(i);
|
||||||
|
UpdateBorderColor(i);
|
||||||
|
CheckIfParametersMet();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
|
||||||
|
[this](int index) {
|
||||||
|
UpdateDockedState(GetControllerTypeFromIndex(index) ==
|
||||||
|
Settings::ControllerType::Handheld);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(ui->inputConfigButton, &QPushButton::clicked, this,
|
||||||
|
&QtControllerSelectorDialog::CallConfigureInputDialog);
|
||||||
|
|
||||||
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||||
|
&QtControllerSelectorDialog::ApplyConfiguration);
|
||||||
|
|
||||||
|
// If keep_controllers_connected is false, forcefully disconnect all controllers
|
||||||
|
if (!parameters.keep_controllers_connected) {
|
||||||
|
for (auto player : player_groupboxes) {
|
||||||
|
player->setChecked(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Settings::values.vibration_enabled = ui->vibrationGroup->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);
|
||||||
|
player_groupboxes[index]->setChecked(connected);
|
||||||
|
connected_controller_checkboxes[index]->setChecked(connected);
|
||||||
|
emulated_controllers[index]->setCurrentIndex(
|
||||||
|
GetIndexFromControllerType(Settings::values.players[index].controller_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDockedState(Settings::values.players[8].connected);
|
||||||
|
|
||||||
|
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::CallConfigureInputDialog() {
|
||||||
|
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||||
|
|
||||||
|
ConfigureInputDialog dialog(this, max_supported_players, input_subsystem);
|
||||||
|
|
||||||
|
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
|
||||||
|
Qt::WindowSystemMenuHint);
|
||||||
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
|
dialog.exec();
|
||||||
|
|
||||||
|
dialog.ApplyConfiguration();
|
||||||
|
|
||||||
|
LoadConfiguration();
|
||||||
|
CheckIfParametersMet();
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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(),
|
||||||
|
[this](const QGroupBox* player) { return player->isChecked(); }));
|
||||||
|
|
||||||
|
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
|
||||||
|
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||||
|
|
||||||
|
// First, check against the number of connected players.
|
||||||
|
if (num_connected_players < min_supported_players ||
|
||||||
|
num_connected_players > max_supported_players) {
|
||||||
|
parameters_met = false;
|
||||||
|
ui->buttonBox->setEnabled(parameters_met);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, check against all connected controllers.
|
||||||
|
const auto all_controllers_compatible = [this] {
|
||||||
|
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
|
||||||
|
// Skip controllers that are not used, we only care about the currently connected ones.
|
||||||
|
if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto compatible = IsControllerCompatible(
|
||||||
|
GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex()),
|
||||||
|
parameters);
|
||||||
|
|
||||||
|
// If any controller is found to be incompatible, return false early.
|
||||||
|
if (!compatible) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reaching here means all currently connected controllers are compatible.
|
||||||
|
return true;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (!all_controllers_compatible) {
|
||||||
|
parameters_met = false;
|
||||||
|
ui->buttonBox->setEnabled(parameters_met);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters_met = true;
|
||||||
|
ui->buttonBox->setEnabled(parameters_met);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::SetSupportedControllers() {
|
||||||
|
const QString theme = [this] {
|
||||||
|
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
|
||||||
|
return QStringLiteral("_dark");
|
||||||
|
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
|
||||||
|
return QStringLiteral("_midnight");
|
||||||
|
} else {
|
||||||
|
return QString{};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (parameters.enable_single_mode && parameters.allow_handheld) {
|
||||||
|
ui->controllerSupported1->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
|
||||||
|
} else {
|
||||||
|
ui->controllerSupported1->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.allow_dual_joycons) {
|
||||||
|
ui->controllerSupported2->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
|
||||||
|
} else {
|
||||||
|
ui->controllerSupported2->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.allow_left_joycon) {
|
||||||
|
ui->controllerSupported3->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
|
||||||
|
} else {
|
||||||
|
ui->controllerSupported3->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.allow_right_joycon) {
|
||||||
|
ui->controllerSupported4->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
|
||||||
|
} else {
|
||||||
|
ui->controllerSupported4->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.allow_pro_controller) {
|
||||||
|
ui->controllerSupported5->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
|
||||||
|
} else {
|
||||||
|
ui->controllerSupported5->setStyleSheet(
|
||||||
|
QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
|
||||||
|
.arg(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable_single_mode overrides min_players and max_players.
|
||||||
|
if (parameters.enable_single_mode) {
|
||||||
|
ui->numberSupportedLabel->setText(QStringLiteral("1"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters.min_players == parameters.max_players) {
|
||||||
|
ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
|
||||||
|
} else {
|
||||||
|
ui->numberSupportedLabel->setText(
|
||||||
|
QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
|
||||||
|
if (!player_groupboxes[player_index]->isChecked()) {
|
||||||
|
connected_controller_icons[player_index]->setStyleSheet(QString{});
|
||||||
|
player_labels[player_index]->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString stylesheet = [this, player_index] {
|
||||||
|
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex())) {
|
||||||
|
case Settings::ControllerType::ProController:
|
||||||
|
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
|
||||||
|
case Settings::ControllerType::DualJoyconDetached:
|
||||||
|
return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
|
||||||
|
case Settings::ControllerType::LeftJoycon:
|
||||||
|
return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
|
||||||
|
case Settings::ControllerType::RightJoycon:
|
||||||
|
return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
|
||||||
|
case Settings::ControllerType::Handheld:
|
||||||
|
return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
|
||||||
|
default:
|
||||||
|
return QString{};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
const QString theme = [this] {
|
||||||
|
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
|
||||||
|
return QStringLiteral("_dark");
|
||||||
|
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
|
||||||
|
return QStringLiteral("_midnight");
|
||||||
|
} else {
|
||||||
|
return QString{};
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
|
||||||
|
player_labels[player_index]->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
|
||||||
|
auto& player = Settings::values.players[player_index];
|
||||||
|
|
||||||
|
player.controller_type =
|
||||||
|
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex());
|
||||||
|
player.connected = player_groupboxes[player_index]->isChecked();
|
||||||
|
|
||||||
|
// Player 2-8
|
||||||
|
if (player_index != 0) {
|
||||||
|
UpdateController(player.controller_type, player_index, player.connected);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateController(player.controller_type, player_index, player.connected);
|
||||||
|
UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
|
||||||
|
if (!player_groupboxes[player_index]->isChecked() ||
|
||||||
|
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex()) ==
|
||||||
|
Settings::ControllerType::Handheld) {
|
||||||
|
led_patterns_boxes[player_index][0]->setChecked(false);
|
||||||
|
led_patterns_boxes[player_index][1]->setChecked(false);
|
||||||
|
led_patterns_boxes[player_index][2]->setChecked(false);
|
||||||
|
led_patterns_boxes[player_index][3]->setChecked(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
|
||||||
|
led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
|
||||||
|
led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
|
||||||
|
led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
|
||||||
|
if (!parameters.enable_border_color ||
|
||||||
|
player_index >= static_cast<std::size_t>(parameters.max_players) ||
|
||||||
|
player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
player_groupboxes[player_index]->setStyleSheet(
|
||||||
|
player_groupboxes[player_index]->styleSheet().append(
|
||||||
|
QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
|
||||||
|
"{ border: 1px solid rgba(%2, %3, %4, %5); }")
|
||||||
|
.arg(player_index + 1)
|
||||||
|
.arg(parameters.border_colors[player_index][0])
|
||||||
|
.arg(parameters.border_colors[player_index][1])
|
||||||
|
.arg(parameters.border_colors[player_index][2])
|
||||||
|
.arg(parameters.border_colors[player_index][3])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
|
||||||
|
if (!parameters.enable_explain_text ||
|
||||||
|
player_index >= static_cast<std::size_t>(parameters.max_players)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
explain_text_labels[player_index]->setText(QString::fromStdString(
|
||||||
|
Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
|
||||||
|
parameters.explain_text[player_index].size())));
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
|
||||||
|
// Disallow changing the console mode if the controller type 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);
|
||||||
|
|
||||||
|
// Also force into undocked mode if the controller type is handheld.
|
||||||
|
if (is_handheld) {
|
||||||
|
ui->radioUndocked->setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
|
||||||
|
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
|
||||||
|
|
||||||
|
switch (max_supported_players) {
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return;
|
||||||
|
case 1:
|
||||||
|
ui->widgetSpacer->hide();
|
||||||
|
ui->widgetSpacer2->hide();
|
||||||
|
ui->widgetSpacer3->hide();
|
||||||
|
ui->widgetSpacer4->hide();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ui->widgetSpacer->hide();
|
||||||
|
ui->widgetSpacer2->hide();
|
||||||
|
ui->widgetSpacer3->hide();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ui->widgetSpacer->hide();
|
||||||
|
ui->widgetSpacer2->hide();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ui->widgetSpacer->hide();
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
// Hide the player widgets when max_supported_controllers is less than or equal to 4.
|
||||||
|
if (max_supported_players <= 4) {
|
||||||
|
player_widgets[index]->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable and hide the following to prevent these from interaction.
|
||||||
|
player_widgets[index]->setDisabled(true);
|
||||||
|
connected_controller_checkboxes[index]->setDisabled(true);
|
||||||
|
connected_controller_labels[index]->hide();
|
||||||
|
connected_controller_checkboxes[index]->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
|
||||||
|
connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
|
||||||
|
&GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
|
||||||
|
connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
|
||||||
|
&QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QtControllerSelector::~QtControllerSelector() = default;
|
||||||
|
|
||||||
|
void QtControllerSelector::ReconfigureControllers(
|
||||||
|
std::function<void()> callback, Core::Frontend::ControllerParameters parameters) const {
|
||||||
|
this->callback = std::move(callback);
|
||||||
|
emit MainWindowReconfigureControllers(parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtControllerSelector::MainWindowReconfigureFinished() {
|
||||||
|
// Acquire the HLE mutex
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||||
|
callback();
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
// 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>
|
||||||
|
#include "core/frontend/applets/controller.h"
|
||||||
|
|
||||||
|
class GMainWindow;
|
||||||
|
class QCheckBox;
|
||||||
|
class QComboBox;
|
||||||
|
class QDialogButtonBox;
|
||||||
|
class QGroupBox;
|
||||||
|
class QLabel;
|
||||||
|
|
||||||
|
namespace InputCommon {
|
||||||
|
class InputSubsystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class QtControllerSelectorDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QtControllerSelectorDialog final : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtControllerSelectorDialog(QWidget* parent,
|
||||||
|
Core::Frontend::ControllerParameters parameters_,
|
||||||
|
InputCommon::InputSubsystem* input_subsystem_);
|
||||||
|
~QtControllerSelectorDialog() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Applies the current configuration.
|
||||||
|
void ApplyConfiguration();
|
||||||
|
|
||||||
|
// Loads the current input configuration into the frontend applet.
|
||||||
|
void LoadConfiguration();
|
||||||
|
|
||||||
|
// Initializes the "Configure Input" Dialog.
|
||||||
|
void CallConfigureInputDialog();
|
||||||
|
|
||||||
|
// Checks the current configuration against the given parameters and
|
||||||
|
// sets the value of parameters_met.
|
||||||
|
void CheckIfParametersMet();
|
||||||
|
|
||||||
|
// Sets the controller icons for "Supported Controller Types".
|
||||||
|
void SetSupportedControllers();
|
||||||
|
|
||||||
|
// Updates the controller icons per player.
|
||||||
|
void UpdateControllerIcon(std::size_t player_index);
|
||||||
|
|
||||||
|
// Updates the controller state (type and connection status) per player.
|
||||||
|
void UpdateControllerState(std::size_t player_index);
|
||||||
|
|
||||||
|
// Updates the LED pattern per player.
|
||||||
|
void UpdateLEDPattern(std::size_t player_index);
|
||||||
|
|
||||||
|
// Updates the border color per player.
|
||||||
|
void UpdateBorderColor(std::size_t player_index);
|
||||||
|
|
||||||
|
// Sets the "Explain Text" per player.
|
||||||
|
void SetExplainText(std::size_t player_index);
|
||||||
|
|
||||||
|
// Updates the console mode.
|
||||||
|
void UpdateDockedState(bool is_handheld);
|
||||||
|
|
||||||
|
// Disables and disconnects unsupported players based on the given parameters.
|
||||||
|
void DisableUnsupportedPlayers();
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
|
||||||
|
|
||||||
|
// Parameters sent in from the backend HLE applet.
|
||||||
|
Core::Frontend::ControllerParameters parameters;
|
||||||
|
|
||||||
|
InputCommon::InputSubsystem* input_subsystem;
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
|
||||||
|
static constexpr std::size_t NUM_PLAYERS = 8;
|
||||||
|
|
||||||
|
// Widgets encapsulating the groupboxes and comboboxes per player.
|
||||||
|
std::array<QWidget*, NUM_PLAYERS> player_widgets;
|
||||||
|
|
||||||
|
// Groupboxes encapsulating the controller icons and LED patterns per player.
|
||||||
|
std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
|
||||||
|
|
||||||
|
// Icons for currently connected controllers/players.
|
||||||
|
std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
|
||||||
|
|
||||||
|
// Labels that represent the player numbers in place of the controller icons.
|
||||||
|
std::array<QLabel*, NUM_PLAYERS> player_labels;
|
||||||
|
|
||||||
|
// LED patterns for currently connected controllers/players.
|
||||||
|
std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
|
||||||
|
|
||||||
|
// Labels representing additional information known as "Explain Text" per player.
|
||||||
|
std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
|
||||||
|
|
||||||
|
// Comboboxes with a list of emulated controllers per player.
|
||||||
|
std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
|
||||||
|
|
||||||
|
// Labels representing the number of connected controllers
|
||||||
|
// above the "Connected Controllers" checkboxes.
|
||||||
|
std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
|
||||||
|
|
||||||
|
// Checkboxes representing the "Connected Controllers".
|
||||||
|
std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
|
||||||
|
};
|
||||||
|
|
||||||
|
class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QtControllerSelector(GMainWindow& parent);
|
||||||
|
~QtControllerSelector() override;
|
||||||
|
|
||||||
|
void ReconfigureControllers(std::function<void()> callback,
|
||||||
|
Core::Frontend::ControllerParameters parameters) const override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void MainWindowReconfigureControllers(Core::Frontend::ControllerParameters parameters) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void MainWindowReconfigureFinished();
|
||||||
|
|
||||||
|
mutable std::function<void()> callback;
|
||||||
|
};
|
@ -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_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);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
// 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;
|
||||||
|
};
|
@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ConfigureInputDialog</class>
|
||||||
|
<widget class="QDialog" name="ConfigureInputDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>70</width>
|
||||||
|
<height>540</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Configure Input</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>2</number>
|
||||||
|
</property>
|
||||||
|
<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>
|
||||||
|
<layout class="QHBoxLayout" name="inputLayout"/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>ConfigureInputDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|