Project Mjölnir: Part 2 - Controller Applet
Co-authored-by: Its-Rei <kupfel@gmail.com>nce_cpp
-
BINdist/icons/controller/applet_dual_joycon.png
-
BINdist/icons/controller/applet_dual_joycon_dark.png
-
BINdist/icons/controller/applet_dual_joycon_dark_disabled.png
-
BINdist/icons/controller/applet_dual_joycon_disabled.png
-
BINdist/icons/controller/applet_dual_joycon_midnight.png
-
BINdist/icons/controller/applet_dual_joycon_midnight_disabled.png
-
BINdist/icons/controller/applet_handheld.png
-
BINdist/icons/controller/applet_handheld_dark.png
-
BINdist/icons/controller/applet_handheld_dark_disabled.png
-
BINdist/icons/controller/applet_handheld_disabled.png
-
BINdist/icons/controller/applet_handheld_midnight.png
-
BINdist/icons/controller/applet_handheld_midnight_disabled.png
-
BINdist/icons/controller/applet_pro_controller.png
-
BINdist/icons/controller/applet_pro_controller_dark.png
-
BINdist/icons/controller/applet_pro_controller_dark_disabled.png
-
BINdist/icons/controller/applet_pro_controller_disabled.png
-
BINdist/icons/controller/applet_pro_controller_midnight.png
-
BINdist/icons/controller/applet_pro_controller_midnight_disabled.png
-
BINdist/icons/controller/applet_single_joycon_left.png
-
BINdist/icons/controller/applet_single_joycon_left_dark.png
-
BINdist/icons/controller/applet_single_joycon_left_dark_disabled.png
-
BINdist/icons/controller/applet_single_joycon_left_disabled.png
-
BINdist/icons/controller/applet_single_joycon_left_midnight.png
-
BINdist/icons/controller/applet_single_joycon_left_midnight_disabled.png
-
BINdist/icons/controller/applet_single_joycon_right.png
-
BINdist/icons/controller/applet_single_joycon_right_dark.png
-
BINdist/icons/controller/applet_single_joycon_right_dark_disabled.png
-
BINdist/icons/controller/applet_single_joycon_right_disabled.png
-
BINdist/icons/controller/applet_single_joycon_right_midnight.png
-
BINdist/icons/controller/applet_single_joycon_right_midnight_disabled.png
-
30dist/icons/controller/controller.qrc
-
206dist/qt_themes/default/style.qss
-
290dist/qt_themes/qdarkstyle/style.qss
-
270dist/qt_themes/qdarkstyle_midnight_blue/style.qss
-
4src/core/CMakeLists.txt
-
40src/core/frontend/applets/controller.cpp
-
45src/core/frontend/applets/controller.h
-
71src/core/hle/service/am/applets/applets.cpp
-
19src/core/hle/service/am/applets/applets.h
-
197src/core/hle/service/am/applets/controller.cpp
-
119src/core/hle/service/am/applets/controller.h
-
28src/core/hle/service/hid/controllers/npad.cpp
-
6src/core/hle/service/hid/controllers/npad.h
-
12src/yuzu/CMakeLists.txt
-
568src/yuzu/applets/controller.cpp
-
125src/yuzu/applets/controller.h
-
2432src/yuzu/applets/controller.ui
-
21src/yuzu/configuration/configure_input.cpp
-
2src/yuzu/configuration/configure_input.h
-
37src/yuzu/configuration/configure_input_dialog.cpp
-
38src/yuzu/configuration/configure_input_dialog.h
-
57src/yuzu/configuration/configure_input_dialog.ui
-
35src/yuzu/main.cpp
-
6src/yuzu/main.h
|
After Width: 70 | Height: 70 | Size: 3.5 KiB |
|
After Width: 70 | Height: 70 | Size: 3.5 KiB |
|
After Width: 70 | Height: 70 | Size: 3.4 KiB |
|
After Width: 70 | Height: 70 | Size: 3.2 KiB |
|
After Width: 70 | Height: 70 | Size: 3.5 KiB |
|
After Width: 70 | Height: 70 | Size: 3.5 KiB |
|
After Width: 70 | Height: 70 | Size: 1.6 KiB |
|
After Width: 70 | Height: 70 | Size: 1.6 KiB |
|
After Width: 70 | Height: 70 | Size: 2.6 KiB |
|
After Width: 70 | Height: 70 | Size: 2.2 KiB |
|
After Width: 70 | Height: 70 | Size: 1.6 KiB |
|
After Width: 70 | Height: 70 | Size: 2.6 KiB |
|
After Width: 70 | Height: 70 | Size: 4.3 KiB |
|
After Width: 70 | Height: 70 | Size: 4.1 KiB |
|
After Width: 70 | Height: 70 | Size: 4.4 KiB |
|
After Width: 70 | Height: 70 | Size: 4.1 KiB |
|
After Width: 70 | Height: 70 | Size: 4.3 KiB |
|
After Width: 70 | Height: 70 | Size: 4.4 KiB |
|
After Width: 70 | Height: 70 | Size: 2.0 KiB |
|
After Width: 70 | Height: 70 | Size: 2.0 KiB |
|
After Width: 70 | Height: 70 | Size: 2.5 KiB |
|
After Width: 70 | Height: 70 | Size: 2.1 KiB |
|
After Width: 70 | Height: 70 | Size: 2.0 KiB |
|
After Width: 70 | Height: 70 | Size: 2.5 KiB |
|
After Width: 70 | Height: 70 | Size: 2.1 KiB |
|
After Width: 70 | Height: 70 | Size: 2.1 KiB |
|
After Width: 70 | Height: 70 | Size: 2.5 KiB |
|
After Width: 70 | Height: 70 | Size: 2.2 KiB |
|
After Width: 70 | Height: 70 | Size: 2.1 KiB |
|
After Width: 70 | Height: 70 | Size: 2.5 KiB |
@ -0,0 +1,40 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#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; |
|||
|
|||
// Deduce the best configuration based on the input parameters.
|
|||
for (std::size_t index = 0; index < players.size(); ++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); |
|||
} |
|||
|
|||
callback(); |
|||
} |
|||
|
|||
} // namespace Core::Frontend
|
|||
@ -0,0 +1,45 @@ |
|||
// 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>; |
|||
|
|||
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 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,197 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#include "common/string_util.h"
|
|||
#include "core/core.h"
|
|||
#include "core/frontend/applets/controller.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, |
|||
std::vector<IdentificationColor> identification_colors) { |
|||
HID::Controller_NPad::NPadType npad_style_set; |
|||
npad_style_set.raw = private_arg.style_set; |
|||
|
|||
return { |
|||
.min_players = 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, |
|||
.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, |
|||
std::vector<IdentificationColor>( |
|||
controller_user_arg_old.identification_colors.begin(), |
|||
controller_user_arg_old.identification_colors.end())); |
|||
case LibraryAppletVersion::Version7: |
|||
default: |
|||
return ConvertToFrontendParameters( |
|||
controller_private_arg, controller_user_arg_new.header, |
|||
std::vector<IdentificationColor>( |
|||
controller_user_arg_new.identification_colors.begin(), |
|||
controller_user_arg_new.identification_colors.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={}, 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.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,119 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
|
|||
#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>; |
|||
|
|||
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<std::array<char, 0x81>, 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<std::array<char, 0x81>, 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,568 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
|
|||
#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"
|
|||
|
|||
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}, |
|||
}}; |
|||
|
|||
namespace { |
|||
|
|||
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); |
|||
} |
|||
|
|||
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}, |
|||
}}; |
|||
|
|||
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, |
|||
}; |
|||
|
|||
for (std::size_t i = 0; i < player_widgets.size(); ++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 < player_widgets.size(); ++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); |
|||
|
|||
SetSupportedControllers(); |
|||
DisableUnsupportedPlayers(); |
|||
LoadConfiguration(); |
|||
|
|||
// 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() { |
|||
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::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& players = Settings::values.players; |
|||
|
|||
const auto num_connected_players = |
|||
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 < player_widgets.size(); ++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 >= 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::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 < player_widgets.size(); ++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(); |
|||
} |
|||
} |
|||
|
|||
void QtControllerSelectorDialog::LoadConfiguration() { |
|||
for (std::size_t index = 0; index < player_widgets.size(); ++index) { |
|||
const auto connected = Settings::values.players[index].connected || |
|||
(index == 0 && Settings::values.players[8].connected); |
|||
player_groupboxes[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); |
|||
} |
|||
|
|||
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,125 @@ |
|||
// 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(); |
|||
|
|||
// 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); |
|||
|
|||
// Updates the console mode. |
|||
void UpdateDockedState(bool is_handheld); |
|||
|
|||
// Disables and disconnects unsupported players based on the given parameters. |
|||
void DisableUnsupportedPlayers(); |
|||
|
|||
// Loads the current input configuration into the frontend applet. |
|||
void LoadConfiguration(); |
|||
|
|||
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}; |
|||
|
|||
// Widgets encapsulating the groupboxes and comboboxes per player. |
|||
std::array<QWidget*, 8> player_widgets; |
|||
|
|||
// Groupboxes encapsulating the controller icons and LED patterns per player. |
|||
std::array<QGroupBox*, 8> player_groupboxes; |
|||
|
|||
// Icons for currently connected controllers/players. |
|||
std::array<QWidget*, 8> connected_controller_icons; |
|||
|
|||
// Labels that represent the player numbers in place of the controller icons. |
|||
std::array<QLabel*, 8> player_labels; |
|||
|
|||
// LED patterns for currently connected controllers/players. |
|||
std::array<std::array<QCheckBox*, 4>, 8> led_patterns_boxes; |
|||
|
|||
// Comboboxes with a list of emulated controllers per player. |
|||
std::array<QComboBox*, 8> emulated_controllers; |
|||
|
|||
// Labels representing the number of connected controllers |
|||
// above the "Connected Controllers" checkboxes. |
|||
std::array<QLabel*, 8> connected_controller_labels; |
|||
|
|||
// Checkboxes representing the "Connected Controllers". |
|||
std::array<QCheckBox*, 8> 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; |
|||
}; |
|||
2432
src/yuzu/applets/controller.ui
File diff suppressed because it is too large
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_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> |
|||