Fix Joy-Con Pair Crash + Implement More HID Service Functions

This fixes a Joy-Con Pair bug which caused a crash when a partner device was set to none while being set as a partner. In addition, the following HID service functions were implemented:
* GetSupportedNpadStyleSet
* ActivateNpadWithRevision
* GetNpadJoyHoldType
* AcquireNpadStyleSetUpdateEventHandle
This commit is contained in:
◱ PixelyIon 2020-08-16 21:15:46 +05:30 committed by ◱ PixelyIon
parent 6a931b95b0
commit ee2fdbdf6a
12 changed files with 110 additions and 39 deletions

View File

@ -23,9 +23,6 @@ namespace skyline::input {
if (!activated) if (!activated)
return; return;
for (auto &npad : npads)
npad.Disconnect();
for (auto &controller : controllers) for (auto &controller : controllers)
controller.device = nullptr; controller.device = nullptr;
@ -75,6 +72,19 @@ namespace skyline::input {
} }
} }
} }
// We do this to prevent triggering the event unless there's a real change in a device's style, which would be caused if we disconnected all controllers then reconnected them
for (auto &device : npads) {
bool connected = false;
for (auto &controller : controllers) {
if (controller.device == &device) {
connected = true;
break;
}
}
if (!connected)
device.Disconnect();
}
} }
void NpadManager::Activate() { void NpadManager::Activate() {

View File

@ -22,6 +22,8 @@ namespace skyline::input {
bool activated{false}; //!< If this NpadManager is activated or not bool activated{false}; //!< If this NpadManager is activated or not
std::atomic<bool> updated{false}; //!< If this NpadManager has been updated by the guest std::atomic<bool> updated{false}; //!< If this NpadManager has been updated by the guest
friend NpadDevice;
/** /**
* @brief This translates an NPad's ID into it's index in the array * @brief This translates an NPad's ID into it's index in the array
* @param id The ID of the NPad to translate * @param id The ID of the NPad to translate

View File

@ -5,9 +5,12 @@
#include "npad.h" #include "npad.h"
namespace skyline::input { namespace skyline::input {
NpadDevice::NpadDevice(NpadManager &manager, NpadSection &section, NpadId id) : manager(manager), section(section), id(id) {} NpadDevice::NpadDevice(NpadManager &manager, NpadSection &section, NpadId id) : manager(manager), section(section), id(id), updateEvent(std::make_shared<kernel::type::KEvent>(manager.state)) {}
void NpadDevice::Connect(NpadControllerType newType) {
if (type == newType)
return;
void NpadDevice::Connect(NpadControllerType type) {
section.header.type = NpadControllerType::None; section.header.type = NpadControllerType::None;
section.deviceType.raw = 0; section.deviceType.raw = 0;
section.buttonProperties.raw = 0; section.buttonProperties.raw = 0;
@ -15,7 +18,7 @@ namespace skyline::input {
connectionState.raw = 0; connectionState.raw = 0;
connectionState.connected = true; connectionState.connected = true;
switch (type) { switch (newType) {
case NpadControllerType::ProController: case NpadControllerType::ProController:
section.header.type = NpadControllerType::ProController; section.header.type = NpadControllerType::ProController;
section.deviceType.fullKey = true; section.deviceType.fullKey = true;
@ -86,10 +89,10 @@ namespace skyline::input {
break; break;
default: default:
throw exception("Unsupported controller type: {}", type); throw exception("Unsupported controller type: {}", newType);
} }
switch (type) { switch (newType) {
case NpadControllerType::ProController: case NpadControllerType::ProController:
case NpadControllerType::JoyconLeft: case NpadControllerType::JoyconLeft:
case NpadControllerType::JoyconRight: case NpadControllerType::JoyconRight:
@ -114,19 +117,24 @@ namespace skyline::input {
section.leftBatteryLevel = NpadBatteryLevel::Full; section.leftBatteryLevel = NpadBatteryLevel::Full;
section.rightBatteryLevel = NpadBatteryLevel::Full; section.rightBatteryLevel = NpadBatteryLevel::Full;
this->type = type; type = newType;
controllerInfo = &GetControllerInfo(); controllerInfo = &GetControllerInfo();
SetButtonState(NpadButton{}, NpadButtonState::Released); updateEvent->Signal();
} }
void NpadDevice::Disconnect() { void NpadDevice::Disconnect() {
if (type == NpadControllerType::None)
return;
section = {}; section = {};
explicitAssignment = false; explicitAssignment = false;
globalTimestamp = 0; globalTimestamp = 0;
type = NpadControllerType::None; type = NpadControllerType::None;
controllerInfo = nullptr; controllerInfo = nullptr;
updateEvent->Signal();
} }
NpadControllerInfo &NpadDevice::GetControllerInfo() { NpadControllerInfo &NpadDevice::GetControllerInfo() {
@ -149,8 +157,9 @@ namespace skyline::input {
NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) { NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) {
auto &lastEntry = info.state.at(info.header.currentEntry); auto &lastEntry = info.state.at(info.header.currentEntry);
info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0;
info.header.timestamp = util::GetTimeTicks(); info.header.timestamp = util::GetTimeTicks();
info.header.entryCount = std::min(static_cast<u8>(info.header.entryCount + 1), constant::HidEntryCount);
info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0;
auto &entry = info.state.at(info.header.currentEntry); auto &entry = info.state.at(info.header.currentEntry);
@ -210,7 +219,7 @@ namespace skyline::input {
mask = orientedMask; mask = orientedMask;
} }
for (NpadControllerState& controllerEntry : {std::ref(GetNextEntry(section.defaultController)), std::ref(GetNextEntry(section.digitalController))}) for (NpadControllerState &controllerEntry : {std::ref(GetNextEntry(section.defaultController)), std::ref(GetNextEntry(section.digitalController))})
if (state == NpadButtonState::Pressed) if (state == NpadButtonState::Pressed)
controllerEntry.buttons.raw |= mask.raw; controllerEntry.buttons.raw |= mask.raw;
else else
@ -224,7 +233,7 @@ namespace skyline::input {
return; return;
auto &controllerEntry = GetNextEntry(*controllerInfo); auto &controllerEntry = GetNextEntry(*controllerInfo);
auto& defaultEntry = GetNextEntry(section.defaultController); auto &defaultEntry = GetNextEntry(section.defaultController);
if (manager.orientation == NpadJoyOrientation::Vertical && (type != NpadControllerType::JoyconLeft && type != NpadControllerType::JoyconRight)) { if (manager.orientation == NpadJoyOrientation::Vertical && (type != NpadControllerType::JoyconLeft && type != NpadControllerType::JoyconRight)) {
switch (axis) { switch (axis) {
@ -266,7 +275,7 @@ namespace skyline::input {
} }
} }
auto& digitalEntry = GetNextEntry(section.digitalController); auto &digitalEntry = GetNextEntry(section.digitalController);
constexpr i16 threshold = 3276; // A 10% deadzone for the stick constexpr i16 threshold = 3276; // A 10% deadzone for the stick
digitalEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold; digitalEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold;

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include <kernel/types/KEvent.h>
#include "shared_mem.h" #include "shared_mem.h"
namespace skyline::input { namespace skyline::input {
@ -96,6 +97,7 @@ namespace skyline::input {
NpadId id; //!< The ID of this controller NpadId id; //!< The ID of this controller
NpadControllerType type{}; //!< The type of this controller NpadControllerType type{}; //!< The type of this controller
NpadConnectionState connectionState{}; //!< The state of the connection NpadConnectionState connectionState{}; //!< The state of the connection
std::shared_ptr<kernel::type::KEvent> updateEvent; //!< This event is triggered on the controller's style changing
bool explicitAssignment{false}; //!< If an assignment has explicitly been set or is the default for this controller bool explicitAssignment{false}; //!< If an assignment has explicitly been set or is the default for this controller
NpadDevice(NpadManager &manager, NpadSection &section, NpadId id); NpadDevice(NpadManager &manager, NpadSection &section, NpadId id);
@ -123,9 +125,9 @@ namespace skyline::input {
/** /**
* @brief This connects this controller to the guest * @brief This connects this controller to the guest
* @param type The type of controller to connect as * @param newType The type of controller to connect as
*/ */
void Connect(NpadControllerType type); void Connect(NpadControllerType newType);
/** /**
* @brief This disconnects this controller from the guest * @brief This disconnects this controller from the guest

View File

@ -20,7 +20,7 @@ namespace skyline {
*/ */
struct CommonHeader { struct CommonHeader {
u64 timestamp; //!< The timestamp of the latest entry in ticks u64 timestamp; //!< The timestamp of the latest entry in ticks
u64 entryCount{constant::HidEntryCount}; //!< The number of entries (17) u64 entryCount; //!< The number of written entries
u64 currentEntry; //!< The index of the latest entry u64 currentEntry; //!< The index of the latest entry
u64 maxEntry{constant::HidEntryCount - 1}; //!< The maximum entry index (16) u64 maxEntry{constant::HidEntryCount - 1}; //!< The maximum entry index (16)
}; };

View File

@ -10,10 +10,14 @@ namespace skyline::service::hid {
IHidServer::IHidServer(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::hid_IHidServer, "hid:IHidServer", { IHidServer::IHidServer(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::hid_IHidServer, "hid:IHidServer", {
{0x0, SFUNC(IHidServer::CreateAppletResource)}, {0x0, SFUNC(IHidServer::CreateAppletResource)},
{0x64, SFUNC(IHidServer::SetSupportedNpadStyleSet)}, {0x64, SFUNC(IHidServer::SetSupportedNpadStyleSet)},
{0x64, SFUNC(IHidServer::GetSupportedNpadStyleSet)},
{0x66, SFUNC(IHidServer::SetSupportedNpadIdType)}, {0x66, SFUNC(IHidServer::SetSupportedNpadIdType)},
{0x67, SFUNC(IHidServer::ActivateNpad)}, {0x67, SFUNC(IHidServer::ActivateNpad)},
{0x68, SFUNC(IHidServer::DeactivateNpad)}, {0x68, SFUNC(IHidServer::DeactivateNpad)},
{0x6A, SFUNC(IHidServer::AcquireNpadStyleSetUpdateEventHandle)},
{0x6D, SFUNC(IHidServer::ActivateNpadWithRevision)},
{0x78, SFUNC(IHidServer::SetNpadJoyHoldType)}, {0x78, SFUNC(IHidServer::SetNpadJoyHoldType)},
{0x79, SFUNC(IHidServer::GetNpadJoyHoldType)},
{0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)}, {0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)} {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)}
@ -25,11 +29,17 @@ namespace skyline::service::hid {
void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto styleSet = request.Pop<NpadStyleSet>(); auto styleSet = request.Pop<NpadStyleSet>();
state.input->npad.styles = styleSet;
state.input->npad.Update();
state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast<bool>(styleSet.proController), static_cast<bool>(styleSet.joyconHandheld), static_cast<bool>(styleSet.joyconDual), static_cast<bool>(styleSet.joyconLeft), static_cast<bool> state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast<bool>(styleSet.proController), static_cast<bool>(styleSet.joyconHandheld), static_cast<bool>(styleSet.joyconDual), static_cast<bool>(styleSet.joyconLeft), static_cast<bool>
(styleSet.joyconRight), static_cast<bool>(styleSet.gamecube), static_cast<bool>(styleSet.palma), static_cast<bool>(styleSet.nes), static_cast<bool>(styleSet.nesHandheld), static_cast<bool>(styleSet.snes)); (styleSet.joyconRight), static_cast<bool>(styleSet.gamecube), static_cast<bool>(styleSet.palma), static_cast<bool>(styleSet.nes), static_cast<bool>(styleSet.nesHandheld), static_cast<bool>(styleSet.snes));
} }
void IHidServer::GetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(state.input->npad.styles);
}
void IHidServer::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
const auto &buffer = request.inputBuf.at(0); const auto &buffer = request.inputBuf.at(0);
u64 address = buffer.address; u64 address = buffer.address;
@ -53,11 +63,23 @@ namespace skyline::service::hid {
state.input->npad.Deactivate(); state.input->npad.Deactivate();
} }
void IHidServer::AcquireNpadStyleSetUpdateEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<NpadId>();
request.copyHandles.push_back(state.process->InsertItem(state.input->npad.at(id).updateEvent));
}
void IHidServer::ActivateNpadWithRevision(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.input->npad.Activate();
}
void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto appletResourceUID = request.Pop<u64>();
state.input->npad.orientation = request.Pop<NpadJoyOrientation>(); state.input->npad.orientation = request.Pop<NpadJoyOrientation>();
} }
void IHidServer::GetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(state.input->npad.orientation);
}
void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<NpadId>(); auto id = request.Pop<NpadId>();
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single, true); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single, true);

View File

@ -25,6 +25,11 @@ namespace skyline::service::hid {
*/ */
void SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This gets the style of controllers supported (https://switchbrew.org/wiki/HID_services#GetSupportedNpadStyleSet)
*/
void GetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief This sets the NpadIds which are supported (https://switchbrew.org/wiki/HID_services#SetSupportedNpadIdType) * @brief This sets the NpadIds which are supported (https://switchbrew.org/wiki/HID_services#SetSupportedNpadIdType)
*/ */
@ -40,11 +45,26 @@ namespace skyline::service::hid {
*/ */
void DeactivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void DeactivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This requests an event that's signalled on a specific NpadId changing (https://switchbrew.org/wiki/HID_services#AcquireNpadStyleSetUpdateEventHandle)
*/
void AcquireNpadStyleSetUpdateEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This requests the activation of controllers with a specific HID revision (https://switchbrew.org/wiki/HID_services#ActivateNpadWithRevision)
*/
void ActivateNpadWithRevision(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief Sets the Joy-Con hold mode (https://switchbrew.org/wiki/HID_services#SetNpadJoyHoldType) * @brief Sets the Joy-Con hold mode (https://switchbrew.org/wiki/HID_services#SetNpadJoyHoldType)
*/ */
void SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the Joy-Con hold mode (https://switchbrew.org/wiki/HID_services#GetNpadJoyHoldType)
*/
void GetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief Sets the Joy-Con assignment mode to Single by default (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeSingleByDefault) * @brief Sets the Joy-Con assignment mode to Single by default (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeSingleByDefault)
*/ */

View File

@ -19,7 +19,7 @@ class SettingsActivity : AppCompatActivity() {
/** /**
* This is the instance of [PreferenceFragment] that is shown inside [R.id.settings] * This is the instance of [PreferenceFragment] that is shown inside [R.id.settings]
*/ */
private val preferenceFragment : PreferenceFragment = PreferenceFragment() private val preferenceFragment = PreferenceFragment()
/** /**
* This is an instance of [InputManager] used by [ControllerPreference] * This is an instance of [InputManager] used by [ControllerPreference]

View File

@ -148,11 +148,6 @@ class ControllerActivity : AppCompatActivity(), View.OnClickListener {
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
window.decorView.findViewById<View>(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener {
if (!it)
toolbar_layout.setExpanded(false)
}
controller_list.layoutManager = LinearLayoutManager(this) controller_list.layoutManager = LinearLayoutManager(this)
controller_list.adapter = adapter controller_list.adapter = adapter
@ -173,15 +168,22 @@ class ControllerActivity : AppCompatActivity(), View.OnClickListener {
override fun onClick(v : View?) { override fun onClick(v : View?) {
when (val tag = v!!.tag) { when (val tag = v!!.tag) {
is ControllerTypeItem -> { is ControllerTypeItem -> {
val type = manager.controllers[id]!!.type val controller = manager.controllers[id]!!
val types = ControllerType.values().filter { !it.firstController || id == 0 } val types = ControllerType.values().filter { !it.firstController || id == 0 }
val typeNames = types.map { getString(it.stringRes) }.toTypedArray() val typeNames = types.map { getString(it.stringRes) }.toTypedArray()
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(tag.content) .setTitle(tag.content)
.setSingleChoiceItems(typeNames, types.indexOf(type)) { dialog, typeIndex -> .setSingleChoiceItems(typeNames, types.indexOf(controller.type)) { dialog, typeIndex ->
manager.controllers[id] = when (types[typeIndex]) { val selectedType = types[typeIndex]
if (controller.type != selectedType) {
if (controller is JoyConLeftController)
controller.partnerId?.let { (manager.controllers[it] as JoyConRightController).partnerId = null }
else if (controller is JoyConRightController)
controller.partnerId?.let { (manager.controllers[it] as JoyConLeftController).partnerId = null }
manager.controllers[id] = when (selectedType) {
ControllerType.None -> Controller(id, ControllerType.None) ControllerType.None -> Controller(id, ControllerType.None)
ControllerType.HandheldProController -> HandheldController(id) ControllerType.HandheldProController -> HandheldController(id)
ControllerType.ProController -> ProController(id) ControllerType.ProController -> ProController(id)
@ -190,6 +192,7 @@ class ControllerActivity : AppCompatActivity(), View.OnClickListener {
} }
update() update()
}
dialog.dismiss() dialog.dismiss()
} }

View File

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".input.ControllerActivity">
<include layout="@layout/titlebar" /> <include layout="@layout/titlebar" />

View File

@ -1,6 +1,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/titlebar" /> <include layout="@layout/titlebar" />
@ -8,5 +9,6 @@
<FrameLayout <FrameLayout
android:id="@+id/settings" android:id="@+id/settings"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
</LinearLayout> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -4,7 +4,8 @@
android:id="@+id/toolbar_layout" android:id="@+id/toolbar_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants" android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:liftOnScroll="true"> app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar