From ee2fdbdf6a587350588603eb4e55f631b7ace9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Sun, 16 Aug 2020 21:15:46 +0530 Subject: [PATCH] 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 --- app/src/main/cpp/skyline/input/npad.cpp | 16 +++++++-- app/src/main/cpp/skyline/input/npad.h | 2 ++ .../main/cpp/skyline/input/npad_device.cpp | 31 ++++++++++------- app/src/main/cpp/skyline/input/npad_device.h | 6 ++-- .../main/cpp/skyline/input/sections/common.h | 2 +- .../cpp/skyline/services/hid/IHidServer.cpp | 24 +++++++++++++- .../cpp/skyline/services/hid/IHidServer.h | 20 +++++++++++ .../main/java/emu/skyline/SettingsActivity.kt | 2 +- .../emu/skyline/input/ControllerActivity.kt | 33 ++++++++++--------- .../main/res/layout/controller_activity.xml | 2 +- app/src/main/res/layout/settings_activity.xml | 8 +++-- app/src/main/res/layout/titlebar.xml | 3 +- 12 files changed, 110 insertions(+), 39 deletions(-) diff --git a/app/src/main/cpp/skyline/input/npad.cpp b/app/src/main/cpp/skyline/input/npad.cpp index 83a0a33c..3c904d09 100644 --- a/app/src/main/cpp/skyline/input/npad.cpp +++ b/app/src/main/cpp/skyline/input/npad.cpp @@ -23,9 +23,6 @@ namespace skyline::input { if (!activated) return; - for (auto &npad : npads) - npad.Disconnect(); - for (auto &controller : controllers) 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() { diff --git a/app/src/main/cpp/skyline/input/npad.h b/app/src/main/cpp/skyline/input/npad.h index b1c091c7..32432fd6 100644 --- a/app/src/main/cpp/skyline/input/npad.h +++ b/app/src/main/cpp/skyline/input/npad.h @@ -22,6 +22,8 @@ namespace skyline::input { bool activated{false}; //!< If this NpadManager is activated or not std::atomic 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 * @param id The ID of the NPad to translate diff --git a/app/src/main/cpp/skyline/input/npad_device.cpp b/app/src/main/cpp/skyline/input/npad_device.cpp index 8c072c75..df125a59 100644 --- a/app/src/main/cpp/skyline/input/npad_device.cpp +++ b/app/src/main/cpp/skyline/input/npad_device.cpp @@ -5,9 +5,12 @@ #include "npad.h" namespace skyline::input { - NpadDevice::NpadDevice(NpadManager &manager, NpadSection §ion, NpadId id) : manager(manager), section(section), id(id) {} + NpadDevice::NpadDevice(NpadManager &manager, NpadSection §ion, NpadId id) : manager(manager), section(section), id(id), updateEvent(std::make_shared(manager.state)) {} + + void NpadDevice::Connect(NpadControllerType newType) { + if (type == newType) + return; - void NpadDevice::Connect(NpadControllerType type) { section.header.type = NpadControllerType::None; section.deviceType.raw = 0; section.buttonProperties.raw = 0; @@ -15,7 +18,7 @@ namespace skyline::input { connectionState.raw = 0; connectionState.connected = true; - switch (type) { + switch (newType) { case NpadControllerType::ProController: section.header.type = NpadControllerType::ProController; section.deviceType.fullKey = true; @@ -86,10 +89,10 @@ namespace skyline::input { break; default: - throw exception("Unsupported controller type: {}", type); + throw exception("Unsupported controller type: {}", newType); } - switch (type) { + switch (newType) { case NpadControllerType::ProController: case NpadControllerType::JoyconLeft: case NpadControllerType::JoyconRight: @@ -114,19 +117,24 @@ namespace skyline::input { section.leftBatteryLevel = NpadBatteryLevel::Full; section.rightBatteryLevel = NpadBatteryLevel::Full; - this->type = type; + type = newType; controllerInfo = &GetControllerInfo(); - SetButtonState(NpadButton{}, NpadButtonState::Released); + updateEvent->Signal(); } void NpadDevice::Disconnect() { + if (type == NpadControllerType::None) + return; + section = {}; explicitAssignment = false; globalTimestamp = 0; type = NpadControllerType::None; controllerInfo = nullptr; + + updateEvent->Signal(); } NpadControllerInfo &NpadDevice::GetControllerInfo() { @@ -149,8 +157,9 @@ namespace skyline::input { NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) { 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.entryCount = std::min(static_cast(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); @@ -210,7 +219,7 @@ namespace skyline::input { 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) controllerEntry.buttons.raw |= mask.raw; else @@ -224,7 +233,7 @@ namespace skyline::input { return; 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)) { 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 digitalEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold; diff --git a/app/src/main/cpp/skyline/input/npad_device.h b/app/src/main/cpp/skyline/input/npad_device.h index c303f06e..b69b1c82 100644 --- a/app/src/main/cpp/skyline/input/npad_device.h +++ b/app/src/main/cpp/skyline/input/npad_device.h @@ -3,6 +3,7 @@ #pragma once +#include #include "shared_mem.h" namespace skyline::input { @@ -96,6 +97,7 @@ namespace skyline::input { NpadId id; //!< The ID of this controller NpadControllerType type{}; //!< The type of this controller NpadConnectionState connectionState{}; //!< The state of the connection + std::shared_ptr 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 NpadDevice(NpadManager &manager, NpadSection §ion, NpadId id); @@ -123,9 +125,9 @@ namespace skyline::input { /** * @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 diff --git a/app/src/main/cpp/skyline/input/sections/common.h b/app/src/main/cpp/skyline/input/sections/common.h index 70f679b7..3df8b1e4 100644 --- a/app/src/main/cpp/skyline/input/sections/common.h +++ b/app/src/main/cpp/skyline/input/sections/common.h @@ -20,7 +20,7 @@ namespace skyline { */ struct CommonHeader { 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 maxEntry{constant::HidEntryCount - 1}; //!< The maximum entry index (16) }; diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp index f65cef17..29f42c32 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp @@ -10,10 +10,14 @@ namespace skyline::service::hid { IHidServer::IHidServer(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::hid_IHidServer, "hid:IHidServer", { {0x0, SFUNC(IHidServer::CreateAppletResource)}, {0x64, SFUNC(IHidServer::SetSupportedNpadStyleSet)}, + {0x64, SFUNC(IHidServer::GetSupportedNpadStyleSet)}, {0x66, SFUNC(IHidServer::SetSupportedNpadIdType)}, {0x67, SFUNC(IHidServer::ActivateNpad)}, {0x68, SFUNC(IHidServer::DeactivateNpad)}, + {0x6A, SFUNC(IHidServer::AcquireNpadStyleSetUpdateEventHandle)}, + {0x6D, SFUNC(IHidServer::ActivateNpadWithRevision)}, {0x78, SFUNC(IHidServer::SetNpadJoyHoldType)}, + {0x79, SFUNC(IHidServer::GetNpadJoyHoldType)}, {0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)}, {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)} @@ -25,11 +29,17 @@ namespace skyline::service::hid { void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto styleSet = request.Pop(); + 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(styleSet.proController), static_cast(styleSet.joyconHandheld), static_cast(styleSet.joyconDual), static_cast(styleSet.joyconLeft), static_cast (styleSet.joyconRight), static_cast(styleSet.gamecube), static_cast(styleSet.palma), static_cast(styleSet.nes), static_cast(styleSet.nesHandheld), static_cast(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) { const auto &buffer = request.inputBuf.at(0); u64 address = buffer.address; @@ -53,11 +63,23 @@ namespace skyline::service::hid { state.input->npad.Deactivate(); } + void IHidServer::AcquireNpadStyleSetUpdateEventHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto id = request.Pop(); + 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) { - auto appletResourceUID = request.Pop(); state.input->npad.orientation = request.Pop(); } + 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) { auto id = request.Pop(); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single, true); diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.h b/app/src/main/cpp/skyline/services/hid/IHidServer.h index 1a36a9f4..633c83e7 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.h +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.h @@ -25,6 +25,11 @@ namespace skyline::service::hid { */ 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) */ @@ -40,11 +45,26 @@ namespace skyline::service::hid { */ 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) */ 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) */ diff --git a/app/src/main/java/emu/skyline/SettingsActivity.kt b/app/src/main/java/emu/skyline/SettingsActivity.kt index 327a8413..6760fae9 100644 --- a/app/src/main/java/emu/skyline/SettingsActivity.kt +++ b/app/src/main/java/emu/skyline/SettingsActivity.kt @@ -19,7 +19,7 @@ class SettingsActivity : AppCompatActivity() { /** * 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] diff --git a/app/src/main/java/emu/skyline/input/ControllerActivity.kt b/app/src/main/java/emu/skyline/input/ControllerActivity.kt index 45bcdb10..d907f16b 100644 --- a/app/src/main/java/emu/skyline/input/ControllerActivity.kt +++ b/app/src/main/java/emu/skyline/input/ControllerActivity.kt @@ -148,11 +148,6 @@ class ControllerActivity : AppCompatActivity(), View.OnClickListener { setSupportActionBar(toolbar) supportActionBar?.setDisplayHomeAsUpEnabled(true) - window.decorView.findViewById(android.R.id.content).viewTreeObserver.addOnTouchModeChangeListener { - if (!it) - toolbar_layout.setExpanded(false) - } - controller_list.layoutManager = LinearLayoutManager(this) controller_list.adapter = adapter @@ -173,23 +168,31 @@ class ControllerActivity : AppCompatActivity(), View.OnClickListener { override fun onClick(v : View?) { when (val tag = v!!.tag) { is ControllerTypeItem -> { - val type = manager.controllers[id]!!.type + val controller = manager.controllers[id]!! val types = ControllerType.values().filter { !it.firstController || id == 0 } val typeNames = types.map { getString(it.stringRes) }.toTypedArray() MaterialAlertDialogBuilder(this) .setTitle(tag.content) - .setSingleChoiceItems(typeNames, types.indexOf(type)) { dialog, typeIndex -> - manager.controllers[id] = when (types[typeIndex]) { - ControllerType.None -> Controller(id, ControllerType.None) - ControllerType.HandheldProController -> HandheldController(id) - ControllerType.ProController -> ProController(id) - ControllerType.JoyConLeft -> JoyConLeftController(id) - ControllerType.JoyConRight -> JoyConRightController(id) - } + .setSingleChoiceItems(typeNames, types.indexOf(controller.type)) { dialog, 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 } - update() + manager.controllers[id] = when (selectedType) { + ControllerType.None -> Controller(id, ControllerType.None) + ControllerType.HandheldProController -> HandheldController(id) + ControllerType.ProController -> ProController(id) + ControllerType.JoyConLeft -> JoyConLeftController(id) + ControllerType.JoyConRight -> JoyConRightController(id) + } + + update() + } dialog.dismiss() } diff --git a/app/src/main/res/layout/controller_activity.xml b/app/src/main/res/layout/controller_activity.xml index 7147a7bc..2b908cc5 100644 --- a/app/src/main/res/layout/controller_activity.xml +++ b/app/src/main/res/layout/controller_activity.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context=".input.ControllerActivity"> diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml index ff5af0a6..52fc00f4 100644 --- a/app/src/main/res/layout/settings_activity.xml +++ b/app/src/main/res/layout/settings_activity.xml @@ -1,6 +1,7 @@ - @@ -8,5 +9,6 @@ - + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" /> + diff --git a/app/src/main/res/layout/titlebar.xml b/app/src/main/res/layout/titlebar.xml index c5fc1cac..963ea708 100644 --- a/app/src/main/res/layout/titlebar.xml +++ b/app/src/main/res/layout/titlebar.xml @@ -4,7 +4,8 @@ android:id="@+id/toolbar_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:descendantFocusability="afterDescendants" + android:fitsSystemWindows="true" + app:layout_scrollFlags="scroll|exitUntilCollapsed" app:liftOnScroll="true">