Refactor C++ Input

This commit refactors the C++ end of Input so it'll be in line with the rest of the codebase and be ready for the extension with multiple players and controller configuration.
This commit is contained in:
◱ PixelyIon 2020-04-30 21:53:45 +00:00 committed by ◱ PixelyIon
parent 5fec7eefd0
commit 102f26d08e
13 changed files with 393 additions and 331 deletions

View File

@ -47,6 +47,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/gpu/engines/maxwell_3d.cpp ${source_DIR}/skyline/gpu/engines/maxwell_3d.cpp
${source_DIR}/skyline/input.cpp ${source_DIR}/skyline/input.cpp
${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/input/npad.cpp
${source_DIR}/skyline/input/npad_device.cpp
${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/os.cpp
${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/loader.cpp
${source_DIR}/skyline/loader/nro.cpp ${source_DIR}/skyline/loader/nro.cpp

View File

@ -99,12 +99,12 @@ extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIE
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jlong id, jint state) { extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jlong id, jint state) {
if (input) { if (input) {
skyline::input::npad::NpadButton button{.raw = static_cast<skyline::u64>(id)}; skyline::input::NpadButton button{.raw = static_cast<skyline::u64>(id)};
input->npad[0]->SetButtonState(button, static_cast<skyline::input::npad::NpadButtonState>(state)); input->npad.at(skyline::input::NpadId::Player1).SetButtonState(button, static_cast<skyline::input::NpadButtonState>(state));
} }
} }
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint id, jint value) { extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint id, jint value) {
if (input) if (input)
input->npad[0]->SetAxisValue(static_cast<skyline::input::npad::NpadAxisId>(id), value); input->npad.at(skyline::input::NpadId::Player1).SetAxisValue(static_cast<skyline::input::NpadAxisId>(id), value);
} }

View File

@ -74,6 +74,16 @@ namespace skyline {
return ((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency); return ((ticks / frequency) * constant::NsInSecond) + (((ticks % frequency) * constant::NsInSecond + (frequency / 2)) / frequency);
} }
/**
* @brief Returns the current time in arbitrary ticks
* @return The current time in ticks
*/
inline u64 GetTimeTicks() {
u64 ticks;
asm("MRS %0, CNTVCT_EL0" : "=r"(ticks));
return ticks;
}
/** /**
* @brief Aligns up a value to a multiple of two * @brief Aligns up a value to a multiple of two
* @tparam Type The type of the values * @tparam Type The type of the values

View File

@ -4,11 +4,5 @@
#include "input.h" #include "input.h"
namespace skyline::input { namespace skyline::input {
Input::Input(const DeviceState &state) : state(state), commonNpad(std::make_shared<npad::CommonNpad>(state)), hidKMem(std::make_shared<kernel::type::KSharedMemory>(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))) { Input::Input(const DeviceState &state) : state(state), kHid(std::make_shared<kernel::type::KSharedMemory>(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))), hid(reinterpret_cast<HidSharedMemory *>(kHid->kernel.address)), npad(state, hid) {}
hidMem = reinterpret_cast<HidSharedMemory *>(hidKMem->kernel.address);
for (uint i = 0; i < constant::NpadCount; i++) {
npad.at(i) = std::make_shared<npad::NpadDevice>(hidMem->npad.at(i), npad::IndexToNpadId(i));
}
}
} }

View File

@ -6,22 +6,22 @@
#include "common.h" #include "common.h"
#include "kernel/types/KSharedMemory.h" #include "kernel/types/KSharedMemory.h"
#include "input/shared_mem.h" #include "input/shared_mem.h"
#include "input/npad.h"
namespace skyline::input { namespace skyline::input {
/** /**
* @brief The Input class manages input devices * @brief The Input class manages translating host input to guest input
*/ */
class Input { class Input {
private: private:
const DeviceState &state; //!< The state of the device const DeviceState &state; //!< The state of the device
public: public:
std::shared_ptr<kernel::type::KSharedMemory> kHid; //!< The kernel shared memory object for HID Shared Memory
input::HidSharedMemory *hid; //!< A pointer to HID Shared Memory on the host
input::NpadManager npad; //!< This manages all the NPad controllers
Input(const DeviceState &state); Input(const DeviceState &state);
std::shared_ptr<npad::CommonNpad> commonNpad; //!< The common npad device
std::array<std::shared_ptr<npad::NpadDevice>, constant::NpadCount> npad; //!< Array of npad devices
std::shared_ptr<kernel::type::KSharedMemory> hidKMem; //!< The shared memory reserved for HID input
HidSharedMemory *hidMem; //!< A pointer to the root of HID shared memory
}; };
} }

View File

@ -4,174 +4,29 @@
#include <input.h> #include <input.h>
#include "npad.h" #include "npad.h"
namespace skyline::input::npad { namespace skyline::input {
u32 NpadIdToIndex(NpadId id) {
switch (id) { NpadManager::NpadManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), npads
case NpadId::Unknown: {NpadDevice{hid->npad[0], NpadId::Player1}, {hid->npad[1], NpadId::Player2},
return 8; {hid->npad[2], NpadId::Player3}, {hid->npad[3], NpadId::Player4},
case NpadId::Handheld: {hid->npad[4], NpadId::Player5}, {hid->npad[5], NpadId::Player6},
return 9; {hid->npad[6], NpadId::Player7}, {hid->npad[7], NpadId::Player8},
default: {hid->npad[8], NpadId::Unknown}, {hid->npad[9], NpadId::Handheld},
return static_cast<u32>(id); } {}
}
void NpadManager::Activate() {
if (styles.raw == 0) {
styles.proController = true;
styles.joyconHandheld = true;
} }
NpadId IndexToNpadId(u32 index) { at(NpadId::Player1).Connect(state.settings->GetBool("operation_mode") ? NpadControllerType::Handheld : NpadControllerType::ProController);
switch (index) {
case 8:
return NpadId::Unknown;
case 9:
return NpadId::Handheld;
default:
return static_cast<NpadId>(index);
}
} }
NpadDevice::NpadDevice(NpadSection &shmemSection, NpadId id) : shmemSection(shmemSection), id(id) {} void NpadManager::Deactivate() {
styles.raw = 0;
void NpadDevice::Disconnect() { for (auto &npad : npads)
connectionState.connected = false; npad.Disconnect();
}
void NpadDevice::Connect(NpadControllerType type) {
shmemSection.header.styles.raw = 0;
shmemSection.deviceType.raw = 0;
shmemSection.properties.raw = 0;
connectionState.raw = 0;
connectionState.connected = true;
switch (type) {
case NpadControllerType::Handheld:
shmemSection.header.styles.joyconHandheld = true;
shmemSection.deviceType.handheld = true;
shmemSection.deviceType.handheldLeft = true;
shmemSection.deviceType.handheldRight = true;
shmemSection.header.assignment = NpadJoyAssignment::Dual;
shmemSection.properties.ABXYButtonOriented = true;
shmemSection.properties.plusButtonCapability = true;
shmemSection.properties.minusButtonCapability = true;
connectionState.handheld = true;
connectionState.leftJoyconConnected = true;
connectionState.rightJoyconConnected = true;
connectionState.leftJoyconHandheld = true;
connectionState.rightJoyconHandheld = true;
break;
case NpadControllerType::ProController:
shmemSection.header.styles.proController = true;
shmemSection.deviceType.fullKey = true;
shmemSection.deviceType.joyconRight = true;
shmemSection.header.assignment = NpadJoyAssignment::Single;
shmemSection.properties.ABXYButtonOriented = true;
shmemSection.properties.plusButtonCapability = true;
shmemSection.properties.minusButtonCapability = true;
break;
default:
throw exception("Unsupported controller type: {}", type);
}
controllerType = type;
shmemSection.header.singleColourStatus = NpadColourReadStatus::Success;
shmemSection.header.singleColour = {0, 0};
shmemSection.header.dualColourStatus = NpadColourReadStatus::Success;
shmemSection.header.leftColour = {0, 0}; //TODO: make these configurable
shmemSection.header.rightColour = {0, 0};
shmemSection.batteryLevel[0] = constant::NpadBatteryFull;
shmemSection.batteryLevel[1] = constant::NpadBatteryFull;
shmemSection.batteryLevel[2] = constant::NpadBatteryFull;
// Set controllers initial state
SetButtonState(NpadButton{}, NpadButtonState::Released);
//TODO: signal npad event
}
NpadControllerInfo &NpadDevice::GetControllerDeviceInfo() {
switch (controllerType) {
case NpadControllerType::Handheld:
return shmemSection.handheldController;
case NpadControllerType::ProController:
default:
return shmemSection.fullKeyController;
}
}
void NpadDevice::UpdateHeaders(NpadControllerInfo &controller, uint lastStateEntryIndex) {
controller.header.entryCount = constant::HidEntryCount;
controller.header.maxEntry = constant::HidEntryCount - 1;
controller.header.currentEntry = stateEntryIndex;
controller.header.timestamp = util::GetTimeNs();
memcpy(reinterpret_cast<void *>(&controller.state.at(stateEntryIndex)), reinterpret_cast<void *>(&controller.state.at(lastStateEntryIndex)), sizeof(NpadControllerState));
controller.state.at(stateEntryIndex).globalTimestamp++;
controller.state.at(stateEntryIndex).localTimestamp++;
controller.state.at(stateEntryIndex).status.raw = connectionState.raw;
}
void NpadDevice::SetButtonState(NpadButton button, NpadButtonState state) {
NpadControllerInfo &controllerDeviceInfo = GetControllerDeviceInfo();
uint lastStateEntryIndex = stateEntryIndex;
stateEntryIndex = (stateEntryIndex + 1) % constant::HidEntryCount;
for (NpadControllerInfo &controllerInfo : {std::ref(controllerDeviceInfo), std::ref(shmemSection.systemExtController)}) {
UpdateHeaders(controllerInfo, lastStateEntryIndex);
if (state == NpadButtonState::Pressed)
controllerInfo.state.at(stateEntryIndex).controller.buttons.raw |= button.raw;
else
controllerInfo.state.at(stateEntryIndex).controller.buttons.raw &= ~(button.raw);
}
}
void NpadDevice::SetAxisValue(NpadAxisId axis, int value) {
NpadControllerInfo controllerDeviceInfo = GetControllerDeviceInfo();
uint lastStateEntryIndex = stateEntryIndex;
stateEntryIndex = (stateEntryIndex + 1) % constant::HidEntryCount;
for (NpadControllerInfo &controllerInfo : {std::ref(controllerDeviceInfo), std::ref(shmemSection.systemExtController)}) {
UpdateHeaders(controllerInfo, lastStateEntryIndex);
switch (axis) {
case NpadAxisId::LX:
controllerInfo.state.at(stateEntryIndex).controller.leftX = value;
break;
case NpadAxisId::LY:
controllerInfo.state.at(stateEntryIndex).controller.leftY = -value; // Invert Y axis
break;
case NpadAxisId::RX:
controllerInfo.state.at(stateEntryIndex).controller.rightX = value;
break;
case NpadAxisId::RY:
controllerInfo.state.at(stateEntryIndex).controller.rightY = -value; // Invert Y axis
break;
}
}
}
CommonNpad::CommonNpad(const DeviceState &state) : state(state) {}
void CommonNpad::Activate() {
// Always mark controllers we support as supported
if (supportedStyles.raw == 0) {
supportedStyles.proController = true;
supportedStyles.joyconHandheld = true;
}
for (uint i = 0; i < constant::NpadCount; i++) {
bool shouldConnect = (i == 0); // P1
//TODO: Read this as well as controller type from settings based off of NpadID
if (shouldConnect) {
if (state.settings->GetBool("operation_mode"))
state.input->npad.at(i)->Connect(NpadControllerType::Handheld);
else
state.input->npad.at(i)->Connect(NpadControllerType::ProController);
}
}
} }
} }

View File

@ -3,155 +3,66 @@
#pragma once #pragma once
#include "shared_mem.h" #include "npad_device.h"
namespace skyline::input::npad {
enum class NpadAxisId {
RX, //!< Right stick X
RY, //!< Right stick Y
LX, //!< Left stick X
LY //!< Left stick Y
};
enum class NpadButtonState : bool {
Released = false, //!< Released button
Pressed = true //!< Pressed button
};
namespace skyline::input {
/** /**
* @brief This enumerates all the possible IDs for an NPad (https://switchbrew.org/wiki/HID_services#NpadIdType) * @brief This class is used to
*/ */
enum class NpadId : u32 { class NpadManager {
Player1 = 0x0, //!< 1st Player
Player2 = 0x1, //!< 2nd Player
Player3 = 0x2, //!< 3rd Player
Player4 = 0x3, //!< 4th Player
Player5 = 0x4, //!< 5th Player
Player6 = 0x5, //!< 6th Player
Player7 = 0x6, //!< 7th Player
Player8 = 0x7, //!< 8th Player
Unknown = 0x10, //!< Unknown
Handheld = 0x20 //!< Handheld mode
};
/**
* @brief This enumerates all the orientations of the Joy-Con(s)
*/
enum class NpadJoyOrientation : u64 {
Vertical = 0, //!< The Joy-Con is held vertically
Horizontal = 1, //!< The Joy-Con is held horizontally
};
/**
* @brief This holds all the NPad styles (https://switchbrew.org/wiki/HID_services#NpadStyleTag)
*/
union NpadStyleSet {
struct {
bool proController : 1; //!< The Pro Controller
bool joyconHandheld : 1; //!< Joy-Cons in handheld mode
bool joyconDual : 1; //!< Joy-Cons in a pair
bool joyconLeft : 1; //!< Left Joy-Con only
bool joyconRight : 1; //!< Right Joy-Con only
bool gamecube : 1; //!< GameCube controller
bool palma : 1; //!< Poké Ball Plus controller
bool nes : 1; //!< NES controller
bool nesHandheld : 1; //!< NES controller in handheld mode
bool snes : 1; //!< SNES controller
};
u32 raw;
};
static_assert(sizeof(NpadStyleSet) == 0x4);
/**
* @brief Converts the ID of an npad to the index in shared memory
* @param id The ID of an npad
* @return The index in shared memory
*/
u32 NpadIdToIndex(NpadId id);
/**
* @brief Converts the index in shared memory to the ID of an npad
* @param id The index in shared memory
* @return The ID of the npad
*/
NpadId IndexToNpadId(u32 index);
class NpadDevice {
private: private:
NpadId id; //!< The ID of the npad const DeviceState &state; //!< The state of the device
NpadControllerType controllerType{NpadControllerType::None}; //!< The type of controller std::array<NpadDevice, constant::NpadCount> npads; //!< An array of all the NPad devices
uint stateEntryIndex{}; //!< The index of the current state entry
NpadConnectionState connectionState{}; //!< The state of the connection
NpadSection &shmemSection; //!< The section in HID shared memory for this controller
/** /**
* @brief Updates headers for a new shared memory entry * @brief This translates an NPad's ID into it's index in the array
* @param controller The controller to operate on * @param id The ID of the NPad to translate
* @param lastStateEntryIndex The index of the previous state entry * @return The corresponding index of the NPad in the array
*/ */
void UpdateHeaders(NpadControllerInfo &controller, uint lastStateEntryIndex); constexpr inline size_t Translate(NpadId id) {
switch (id) {
/** case NpadId::Unknown:
* @return The controller device info appropriate for the controllers type return 8;
*/ case NpadId::Handheld:
NpadControllerInfo &GetControllerDeviceInfo(); return 9;
default:
return static_cast<size_t>(id);
}
}
public: public:
bool supported{false}; //!< If the npad marked as supported NpadStyleSet styles{}; //!< The styles that are supported in accordance to the host input
NpadJoyOrientation orientation{}; //!< The Joy-Con orientation to use
/** /**
* @param shmemSection A reference to the controllers shared memory region * @param hid A pointer to HID Shared Memory on the host
* @param id The ID of the npad
*/ */
NpadDevice(NpadSection &shmemSection, NpadId id); NpadManager(const DeviceState &state, input::HidSharedMemory *hid);
/** /**
* @brief Sets the joycon assignment in shared memory * @param id The ID of the NPad to return
* @param assignment The assignment to set * @return A reference to the NPad with the specified ID
*/ */
inline void SetAssignment(NpadJoyAssignment assignment) { constexpr inline NpadDevice& at(NpadId id) {
shmemSection.header.assignment = assignment; return npads.at(Translate(id));
} }
/** /**
* @brief Connects a controller to the guest * @param id The ID of the NPad to return
* @param type The type of controller to connect * @return A reference to the NPad with the specified ID
*/ */
void Connect(NpadControllerType type); constexpr inline NpadDevice& operator[](NpadId id) noexcept {
return npads.operator[](Translate(id));
}
/** /**
* @brief Disconnects the controller from the guest * @brief This activates the controllers
*/
void Disconnect();
/**
* @brief Changes a button's state on the virtual controller
* @param button The button work on
* @param state Whether to release or press the button
*/
void SetButtonState(NpadButton button, NpadButtonState state);
/**
* @brief Sets an axis to a value on the virtual controller
* @param axis The axis to change
* @param value The value to use
*/
void SetAxisValue(NpadAxisId axis, int value);
};
class CommonNpad {
private:
const DeviceState &state; //!< The state of the device
public:
NpadStyleSet supportedStyles{}; //!< The supported style sets
NpadJoyOrientation orientation{NpadJoyOrientation::Unset}; //!< The Joy-Con orientation to use
CommonNpad(const DeviceState &state);
/**
* @brief Activates npad support
*/ */
void Activate(); void Activate();
/**
* @brief This deactivates the controllers
*/
void Deactivate();
}; };
} }

View File

@ -0,0 +1,149 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "npad_device.h"
namespace skyline::input {
NpadDevice::NpadDevice(NpadSection &section, NpadId id) : section(section), id(id) {}
void NpadDevice::Connect(NpadControllerType type) {
section.header.type = NpadControllerType::None;
section.deviceType.raw = 0;
section.buttonProperties.raw = 0;
connectionState.raw = 0;
connectionState.connected = true;
switch (type) {
case NpadControllerType::Handheld:
section.header.type = NpadControllerType::Handheld;
section.deviceType.handheldLeft = true;
section.deviceType.handheldRight = true;
section.header.assignment = NpadJoyAssignment::Dual;
section.systemProperties.ABXYButtonOriented = true;
section.systemProperties.plusButtonCapability = true;
section.systemProperties.minusButtonCapability = true;
connectionState.handheld = true;
connectionState.leftJoyconConnected = true;
connectionState.rightJoyconConnected = true;
connectionState.leftJoyconHandheld = true;
connectionState.rightJoyconHandheld = true;
break;
case NpadControllerType::ProController:
section.header.type = NpadControllerType::ProController;
section.deviceType.fullKey = true;
section.deviceType.joyconRight = true;
section.header.assignment = NpadJoyAssignment::Single;
section.systemProperties.ABXYButtonOriented = true;
section.systemProperties.plusButtonCapability = true;
section.systemProperties.minusButtonCapability = true;
break;
default:
throw exception("Unsupported controller type: {}", type);
}
controllerType = type;
section.header.singleColorStatus = NpadColorReadStatus::Success;
section.header.singleColor = {0, 0};
section.header.dualColorStatus = NpadColorReadStatus::Success;
section.header.leftColor = {0, 0}; //TODO: make these configurable
section.header.rightColor = {0, 0};
section.batteryLevel[0] = NpadBatteryLevel::Full;
section.batteryLevel[1] = NpadBatteryLevel::Full;
section.batteryLevel[2] = NpadBatteryLevel::Full;
SetButtonState(NpadButton{}, NpadButtonState::Released);
}
void NpadDevice::Disconnect() {
connectionState.connected = false;
GetNextEntry(GetControllerInfo());
GetNextEntry(section.systemExtController);
}
NpadControllerInfo &NpadDevice::GetControllerInfo() {
switch (controllerType) {
case NpadControllerType::ProController:
return section.fullKeyController;
case NpadControllerType::Handheld:
return section.handheldController;
case NpadControllerType::JoyconDual:
return section.dualController;
case NpadControllerType::JoyconLeft:
return section.leftController;
case NpadControllerType::JoyconRight:
return section.rightController;
default:
throw exception("Cannot find corresponding section for ControllerType: {}", controllerType);
}
}
NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) {
auto &lastEntry = info.state.at(info.header.currentEntry);
info.header.entryCount = constant::HidEntryCount;
info.header.maxEntry = constant::HidEntryCount - 1;
info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0;
info.header.timestamp = util::GetTimeTicks();
auto &entry = info.state.at(info.header.currentEntry);
entry.globalTimestamp = globalTimestamp;
entry.localTimestamp = lastEntry.localTimestamp + 1;
entry.buttons = lastEntry.buttons;
entry.leftX = lastEntry.leftX;
entry.leftY = lastEntry.leftY;
entry.rightX = lastEntry.rightX;
entry.rightY = lastEntry.rightY;
entry.status.raw = connectionState.raw;
return entry;
}
void NpadDevice::SetButtonState(NpadButton mask, NpadButtonState state) {
if (!connectionState.connected)
return;
for (NpadControllerInfo &controllerInfo : {std::ref(GetControllerInfo()), std::ref(section.systemExtController)}) {
auto &entry = GetNextEntry(controllerInfo);
if (state == NpadButtonState::Pressed)
entry.buttons.raw |= mask.raw;
else
entry.buttons.raw &= ~(mask.raw);
}
globalTimestamp++;
}
void NpadDevice::SetAxisValue(NpadAxisId axis, i32 value) {
if (!connectionState.connected)
return;
for (NpadControllerInfo &controllerInfo : {std::ref(GetControllerInfo()), std::ref(section.systemExtController)}) {
auto &entry = GetNextEntry(controllerInfo);
switch (axis) {
case NpadAxisId::LX:
entry.leftX = value;
break;
case NpadAxisId::LY:
entry.leftY = -value; // Invert Y axis
break;
case NpadAxisId::RX:
entry.rightX = value;
break;
case NpadAxisId::RY:
entry.rightY = -value; // Invert Y axis
break;
}
}
globalTimestamp++;
}
}

View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "shared_mem.h"
namespace skyline::input {
/**
* @brief This enumerates all the orientations of the Joy-Con(s)
*/
enum class NpadJoyOrientation : u64 {
Vertical = 0, //!< The Joy-Con is held vertically
Horizontal = 1, //!< The Joy-Con is held horizontally
};
/**
* @brief This holds all the NPad styles (https://switchbrew.org/wiki/HID_services#NpadStyleTag)
*/
union NpadStyleSet {
struct {
bool proController : 1; //!< The Pro Controller
bool joyconHandheld : 1; //!< Joy-Cons in handheld mode
bool joyconDual : 1; //!< Joy-Cons in a pair
bool joyconLeft : 1; //!< Left Joy-Con only
bool joyconRight : 1; //!< Right Joy-Con only
bool gamecube : 1; //!< GameCube controller
bool palma : 1; //!< Poké Ball Plus controller
bool nes : 1; //!< NES controller
bool nesHandheld : 1; //!< NES controller in handheld mode
bool snes : 1; //!< SNES controller
};
u32 raw;
};
static_assert(sizeof(NpadStyleSet) == 0x4);
/**
* @brief This enumerates the states that a button can be in
*/
enum class NpadButtonState : bool {
Released = false, //!< The button isn't being pressed
Pressed = true //!< The button is being pressed
};
/**
* @brief This enumerates all of the axis on NPad controllers
*/
enum class NpadAxisId {
RX, //!< Right Stick X
RY, //!< Right Stick Y
LX, //!< Left Stick X
LY //!< Left Stick Y
};
/**
* @brief This enumerates all the possible IDs for an NPad (https://switchbrew.org/wiki/HID_services#NpadIdType)
*/
enum class NpadId : u32 {
Player1 = 0x0, //!< 1st Player
Player2 = 0x1, //!< 2nd Player
Player3 = 0x2, //!< 3rd Player
Player4 = 0x3, //!< 4th Player
Player5 = 0x4, //!< 5th Player
Player6 = 0x5, //!< 6th Player
Player7 = 0x6, //!< 7th Player
Player8 = 0x7, //!< 8th Player
Unknown = 0x10, //!< Unknown
Handheld = 0x20 //!< Handheld mode
};
class NpadDevice {
private:
NpadId id; //!< The ID of this controller
NpadControllerType controllerType{}; //!< The type of this controller
u8 globalTimestamp{}; //!< The global timestamp of the state entries
NpadConnectionState connectionState{}; //!< The state of the connection
NpadSection &section; //!< The section in HID shared memory for this controller
/**
* @brief This updates the headers and creates a new entry in HID Shared Memory
* @param info The controller info of the NPad that needs to be updated
* @return The next entry that has been created with values from the last entry
*/
NpadControllerState &GetNextEntry(NpadControllerInfo &info);
/**
* @return The NpadControllerInfo for this controller based on it's type
*/
NpadControllerInfo &GetControllerInfo();
public:
bool supported{false}; //!< If this specific NpadId was marked by the application as supported
NpadDevice(NpadSection &section, NpadId id);
/**
* @brief This sets a Joy-Con's Assignment Mode
* @param assignment The assignment mode to set this controller to
*/
inline void SetAssignment(NpadJoyAssignment assignment) {
section.header.assignment = assignment;
}
/**
* @brief This connects this controller to the guest
* @param type The type of controller to connect
*/
void Connect(NpadControllerType type);
/**
* @brief This disconnects this controller from the guest
*/
void Disconnect();
/**
* @brief This changes the state of buttons to the specified state
* @param mask A bit-field mask of all the buttons to change
* @param state The state to change the buttons to
*/
void SetButtonState(NpadButton mask, NpadButtonState state);
/**
* @brief This sets the value of an axis to the specified value
* @param axis The axis to set the value of
* @param value The value to set
*/
void SetAxisValue(NpadAxisId axis, i32 value);
};
}

View File

@ -10,7 +10,7 @@ namespace skyline::service::hid {
}) {} }) {}
void IAppletResource::GetSharedMemoryHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IAppletResource::GetSharedMemoryHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto handle = state.process->InsertItem<type::KSharedMemory>(state.input->hidKMem); auto handle = state.process->InsertItem<type::KSharedMemory>(state.input->kHid);
state.logger->Debug("HID Shared Memory Handle: 0x{:X}", handle); state.logger->Debug("HID Shared Memory Handle: 0x{:X}", handle);
response.copyHandles.push_back(handle); response.copyHandles.push_back(handle);

View File

@ -12,12 +12,13 @@ namespace skyline::service::hid {
{0x64, SFUNC(IHidServer::SetSupportedNpadStyleSet)}, {0x64, SFUNC(IHidServer::SetSupportedNpadStyleSet)},
{0x66, SFUNC(IHidServer::SetSupportedNpadIdType)}, {0x66, SFUNC(IHidServer::SetSupportedNpadIdType)},
{0x67, SFUNC(IHidServer::ActivateNpad)}, {0x67, SFUNC(IHidServer::ActivateNpad)},
{0x68, SFUNC(IHidServer::DeactivateNpad)},
{0x78, SFUNC(IHidServer::SetNpadJoyHoldType)}, {0x78, SFUNC(IHidServer::SetNpadJoyHoldType)},
{0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)}, {0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)} {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)}
}) { }) {
state.input->commonNpad->Activate(); state.input->npad.Activate();
} }
void IHidServer::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -25,7 +26,7 @@ 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<npad::NpadStyleSet>(); auto styleSet = request.Pop<NpadStyleSet>();
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));
@ -33,36 +34,42 @@ namespace skyline::service::hid {
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);
size_t numId = buffer.size / sizeof(npad::NpadId);
u64 address = buffer.address; u64 address = buffer.address;
size_t size = buffer.size / sizeof(NpadId);
for (size_t i = 0; i < numId; i++) { for (size_t i = 0; i < size; i++) {
auto id = state.process->GetObject<npad::NpadId>(address); auto id = state.process->GetObject<NpadId>(address);
state.input->npad.at(NpadIdToIndex(id))->supported = true; state.input->npad.at(id).supported = true;
address += sizeof(npad::NpadId); address += sizeof(NpadId);
} }
} }
void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {} void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.input->npad.Activate();
}
void IHidServer::DeactivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
state.input->npad.Deactivate();
}
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>(); auto appletResourceUID = request.Pop<u64>();
state.input->commonNpad->orientation = request.Pop<npad::NpadJoyOrientation>(); state.input->npad.orientation = request.Pop<NpadJoyOrientation>();
} }
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<npad::NpadId>(); auto id = request.Pop<NpadId>();
state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Single); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
} }
void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<npad::NpadId>(); auto id = request.Pop<NpadId>();
state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Single); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
} }
void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<npad::NpadId>(); auto id = request.Pop<NpadId>();
state.input->npad.at(npad::NpadIdToIndex(id))->SetAssignment(npad::NpadJoyAssignment::Dual); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual);
} }
} }

View File

@ -31,10 +31,15 @@ namespace skyline::service::hid {
void SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief This requests the activation of a controller. This is stubbed as we don't have to activate anything. * @brief This requests the activation of controllers (https://switchbrew.org/wiki/HID_services#ActivateNpad)
*/ */
void ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This requests the deactivation of controllers (https://switchbrew.org/wiki/HID_services#DeactivateNpad)
*/
void DeactivateNpad(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)
*/ */

View File

@ -200,7 +200,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* This sets [surface] to [holder].surface and passes it into libskyline * This sets [surface] to [holder].surface and passes it into libskyline
*/ */
override fun surfaceCreated(holder : SurfaceHolder) { override fun surfaceCreated(holder : SurfaceHolder) {
Log.d("surfaceCreated", "Holder: ${holder.toString()}") Log.d("surfaceCreated", "Holder: $holder")
surface = holder.surface surface = holder.surface
setSurface(surface) setSurface(surface)
} }
@ -209,14 +209,14 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* This is purely used for debugging surface changes * This is purely used for debugging surface changes
*/ */
override fun surfaceChanged(holder : SurfaceHolder, format : Int, width : Int, height : Int) { override fun surfaceChanged(holder : SurfaceHolder, format : Int, width : Int, height : Int) {
Log.d("surfaceChanged", "Holder: ${holder.toString()}, Format: $format, Width: $width, Height: $height") Log.d("surfaceChanged", "Holder: $holder, Format: $format, Width: $width, Height: $height")
} }
/** /**
* This sets [surface] to null and passes it into libskyline * This sets [surface] to null and passes it into libskyline
*/ */
override fun surfaceDestroyed(holder : SurfaceHolder) { override fun surfaceDestroyed(holder : SurfaceHolder) {
Log.d("surfaceDestroyed", "Holder: ${holder.toString()}") Log.d("surfaceDestroyed", "Holder: $holder")
surface = null surface = null
setSurface(surface) setSurface(surface)
} }