mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-27 13:55:29 +03:00
Implement C++ Support for Controller Configuration
This commit adds support to the C++ end of things for controller configuration. It isn't targeting being 1:1 to HOS for controller assignment but is rather based on intuition of how things should be.
This commit is contained in:
parent
75d485a9a7
commit
6a931b95b0
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -10,12 +10,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Git Checkout
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Restore Gradle Cache
|
||||
uses: actions/cache@v1.0.3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /root/.gradle/
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/build.gradle') }}
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Restore CXX Cache
|
||||
uses: actions/cache@v1.0.3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: app/.cxx/
|
||||
key: ${{ runner.os }}-cxx-${{ hashFiles('**/CMakeLists.txt') }}
|
||||
@ -37,7 +37,7 @@ jobs:
|
||||
run: ./gradlew --stacktrace lint
|
||||
|
||||
- name: Upload Lint Report
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lint-result.html
|
||||
path: app/build/reports/lint-results.html
|
||||
@ -46,19 +46,19 @@ jobs:
|
||||
run: ./gradlew assemble
|
||||
|
||||
- name: Upload Debug APK
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-debug.apk
|
||||
path: app/build/outputs/apk/debug/
|
||||
|
||||
- name: Upload Release APK
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: app-release.apk
|
||||
path: app/build/outputs/apk/release/
|
||||
|
||||
- name: Upload R8 Mapping
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: mapping.txt
|
||||
path: app/build/outputs/mapping/release/
|
||||
|
@ -36,7 +36,7 @@ This can also be done by using `Ctrl + Alt + L` on Windows, `Ctrl + Shift + Alt
|
||||
* Parameters: `camelCase`
|
||||
* Files and Directories: `snake_case` except for when they correspond to a HOS structure (EG: Services, Kernel Objects)
|
||||
|
||||
**(1)** Except when the whole name is an abbreviation such as `OS` and `NCE` but not `JVMManager`
|
||||
**(1)** Except when the whole name is an abbreviation use UPPERCASE such as `OS` and `NCE` but not `JVMManager`
|
||||
|
||||
### Comments
|
||||
Use doxygen style comments for:
|
||||
|
@ -97,14 +97,31 @@ extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIE
|
||||
return static_cast<float>(frametime) / 100;
|
||||
}
|
||||
|
||||
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_setController(JNIEnv *, jobject, jint index, jint type, jint partnerIndex) {
|
||||
while (input == nullptr)
|
||||
asm("yield");
|
||||
input->npad.controllers[index] = skyline::input::GuestController{static_cast<skyline::input::NpadControllerType>(type), static_cast<skyline::i8>(partnerIndex)};
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_updateControllers(JNIEnv *, jobject) {
|
||||
while (input == nullptr)
|
||||
asm("yield");
|
||||
input->npad.Update(true);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jint state) {
|
||||
if (input) {
|
||||
skyline::input::NpadButton button{.raw = static_cast<skyline::u64>(id)};
|
||||
input->npad.at(skyline::input::NpadId::Player1).SetButtonState(button, static_cast<skyline::input::NpadButtonState>(state));
|
||||
auto device = input->npad.controllers[index].device;
|
||||
skyline::input::NpadButton button{.raw = static_cast<skyline::u64>(mask)};
|
||||
if (device)
|
||||
device->SetButtonState(button, static_cast<skyline::input::NpadButtonState>(state));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint id, jint value) {
|
||||
if (input)
|
||||
input->npad.at(skyline::input::NpadId::Player1).SetAxisValue(static_cast<skyline::input::NpadAxisId>(id), value);
|
||||
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValue(JNIEnv *, jobject, jint index, jint axis, jint value) {
|
||||
if (input) {
|
||||
auto device = input->npad.controllers[index].device;
|
||||
if (device)
|
||||
device->SetAxisValue(static_cast<skyline::input::NpadAxisId>(axis), value);
|
||||
}
|
||||
}
|
||||
|
@ -5,28 +5,95 @@
|
||||
#include "npad.h"
|
||||
|
||||
namespace skyline::input {
|
||||
|
||||
NpadManager::NpadManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), npads
|
||||
{NpadDevice{hid->npad[0], NpadId::Player1}, {hid->npad[1], NpadId::Player2},
|
||||
{hid->npad[2], NpadId::Player3}, {hid->npad[3], NpadId::Player4},
|
||||
{hid->npad[4], NpadId::Player5}, {hid->npad[5], NpadId::Player6},
|
||||
{hid->npad[6], NpadId::Player7}, {hid->npad[7], NpadId::Player8},
|
||||
{hid->npad[8], NpadId::Unknown}, {hid->npad[9], NpadId::Handheld},
|
||||
{NpadDevice{*this, hid->npad[0], NpadId::Player1}, {*this, hid->npad[1], NpadId::Player2},
|
||||
{*this, hid->npad[2], NpadId::Player3}, {*this, hid->npad[3], NpadId::Player4},
|
||||
{*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6},
|
||||
{*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8},
|
||||
{*this, hid->npad[8], NpadId::Unknown}, {*this, hid->npad[9], NpadId::Handheld},
|
||||
} {}
|
||||
|
||||
void NpadManager::Activate() {
|
||||
if (styles.raw == 0) {
|
||||
styles.proController = true;
|
||||
styles.joyconHandheld = true;
|
||||
}
|
||||
void NpadManager::Update(bool host) {
|
||||
if (host)
|
||||
updated = true;
|
||||
else
|
||||
while (!updated)
|
||||
asm("yield");
|
||||
|
||||
at(NpadId::Player1).Connect(state.settings->GetBool("operation_mode") ? NpadControllerType::Handheld : NpadControllerType::ProController);
|
||||
}
|
||||
|
||||
void NpadManager::Deactivate() {
|
||||
styles.raw = 0;
|
||||
if (!activated)
|
||||
return;
|
||||
|
||||
for (auto &npad : npads)
|
||||
npad.Disconnect();
|
||||
|
||||
for (auto &controller : controllers)
|
||||
controller.device = nullptr;
|
||||
|
||||
for (auto &id : supportedIds) {
|
||||
if (id == NpadId::Unknown)
|
||||
continue;
|
||||
|
||||
auto &device = at(id);
|
||||
|
||||
for (auto &controller : controllers) {
|
||||
if (controller.device)
|
||||
continue;
|
||||
|
||||
NpadStyleSet style{};
|
||||
if (id != NpadId::Handheld) {
|
||||
if (controller.type == NpadControllerType::ProController)
|
||||
style.proController = true;
|
||||
else if (controller.type == NpadControllerType::JoyconLeft)
|
||||
style.joyconLeft = true;
|
||||
else if (controller.type == NpadControllerType::JoyconRight)
|
||||
style.joyconRight = true;
|
||||
if (controller.type == NpadControllerType::JoyconDual || controller.partnerIndex != -1)
|
||||
style.joyconDual = true;
|
||||
} else if (controller.type == NpadControllerType::Handheld) {
|
||||
style.joyconHandheld = true;
|
||||
}
|
||||
style = NpadStyleSet{.raw = style.raw & styles.raw};
|
||||
|
||||
if (style.raw) {
|
||||
if (style.proController) {
|
||||
device.Connect(NpadControllerType::ProController);
|
||||
controller.device = &device;
|
||||
} else if (style.joyconHandheld) {
|
||||
device.Connect(NpadControllerType::Handheld);
|
||||
controller.device = &device;
|
||||
} else if (style.joyconDual && device.GetAssignment() == NpadJoyAssignment::Dual) {
|
||||
device.Connect(NpadControllerType::JoyconDual);
|
||||
controller.device = &device;
|
||||
controllers.at(controller.partnerIndex).device = &device;
|
||||
} else if (style.joyconLeft || style.joyconRight) {
|
||||
device.Connect(controller.type);
|
||||
controller.device = &device;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NpadManager::Activate() {
|
||||
supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8};
|
||||
styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true};
|
||||
activated = true;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void NpadManager::Deactivate() {
|
||||
supportedIds = {};
|
||||
styles = {};
|
||||
activated = false;
|
||||
|
||||
for (auto &npad : npads)
|
||||
npad.Disconnect();
|
||||
|
||||
for (auto &controller : controllers)
|
||||
controller.device = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -6,24 +6,32 @@
|
||||
#include "npad_device.h"
|
||||
|
||||
namespace skyline::input {
|
||||
struct GuestController {
|
||||
NpadControllerType type{}; //!< The type of the controller
|
||||
i8 partnerIndex{-1}; //!< The index of a Joy-Con partner, if this has one
|
||||
NpadDevice *device{nullptr}; //!< A pointer to the NpadDevice that all events from this are redirected to
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class is used to
|
||||
* @brief This class is used to manage all NPad devices and their allocations to Player objects
|
||||
*/
|
||||
class NpadManager {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
std::array<NpadDevice, constant::NpadCount> npads; //!< An array of all the NPad devices
|
||||
bool activated{false}; //!< If this NpadManager is activated or not
|
||||
std::atomic<bool> updated{false}; //!< If this NpadManager has been updated by the guest
|
||||
|
||||
/**
|
||||
* @brief This translates an NPad's ID into it's index in the array
|
||||
* @param id The ID of the NPad to translate
|
||||
* @return The corresponding index of the NPad in the array
|
||||
*/
|
||||
constexpr inline size_t Translate(NpadId id) {
|
||||
static constexpr inline size_t Translate(NpadId id) {
|
||||
switch (id) {
|
||||
case NpadId::Unknown:
|
||||
return 8;
|
||||
case NpadId::Handheld:
|
||||
return 8;
|
||||
case NpadId::Unknown:
|
||||
return 9;
|
||||
default:
|
||||
return static_cast<size_t>(id);
|
||||
@ -31,7 +39,9 @@ namespace skyline::input {
|
||||
}
|
||||
|
||||
public:
|
||||
NpadStyleSet styles{}; //!< The styles that are supported in accordance to the host input
|
||||
std::array<GuestController, constant::ControllerCount> controllers; //!< An array of all the available guest controllers
|
||||
std::vector<NpadId> supportedIds; //!< The NpadId(s) that are supported by the application
|
||||
NpadStyleSet styles; //!< The styles that are supported by the application
|
||||
NpadJoyOrientation orientation{}; //!< The Joy-Con orientation to use
|
||||
|
||||
/**
|
||||
@ -43,7 +53,7 @@ namespace skyline::input {
|
||||
* @param id The ID of the NPad to return
|
||||
* @return A reference to the NPad with the specified ID
|
||||
*/
|
||||
constexpr inline NpadDevice& at(NpadId id) {
|
||||
constexpr inline NpadDevice &at(NpadId id) {
|
||||
return npads.at(Translate(id));
|
||||
}
|
||||
|
||||
@ -51,17 +61,23 @@ namespace skyline::input {
|
||||
* @param id The ID of the NPad to return
|
||||
* @return A reference to the NPad with the specified ID
|
||||
*/
|
||||
constexpr inline NpadDevice& operator[](NpadId id) noexcept {
|
||||
constexpr inline NpadDevice &operator[](NpadId id) noexcept {
|
||||
return npads.operator[](Translate(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This activates the controllers
|
||||
* @brief This deduces all the mappings from guest controllers -> players based on the configuration supplied by HID services and available controllers
|
||||
* @param host If the update is host-initiated rather than the guest
|
||||
*/
|
||||
void Update(bool host = false);
|
||||
|
||||
/**
|
||||
* @brief This activates the mapping between guest controllers -> players, a call to this is required for function
|
||||
*/
|
||||
void Activate();
|
||||
|
||||
/**
|
||||
* @brief This deactivates the controllers
|
||||
* @brief This disables any activate mappings from guest controllers -> players till Activate has been called
|
||||
*/
|
||||
void Deactivate();
|
||||
};
|
||||
|
@ -2,9 +2,10 @@
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "npad_device.h"
|
||||
#include "npad.h"
|
||||
|
||||
namespace skyline::input {
|
||||
NpadDevice::NpadDevice(NpadSection §ion, NpadId id) : section(section), id(id) {}
|
||||
NpadDevice::NpadDevice(NpadManager &manager, NpadSection §ion, NpadId id) : manager(manager), section(section), id(id) {}
|
||||
|
||||
void NpadDevice::Connect(NpadControllerType type) {
|
||||
section.header.type = NpadControllerType::None;
|
||||
@ -15,59 +16,121 @@ namespace skyline::input {
|
||||
connectionState.connected = true;
|
||||
|
||||
switch (type) {
|
||||
case NpadControllerType::ProController:
|
||||
section.header.type = NpadControllerType::ProController;
|
||||
section.deviceType.fullKey = true;
|
||||
|
||||
section.systemProperties.directionalButtonsSupported = true;
|
||||
section.systemProperties.abxyButtonsOriented = true;
|
||||
section.systemProperties.plusButtonCapability = true;
|
||||
section.systemProperties.minusButtonCapability = true;
|
||||
|
||||
connectionState.connected = true;
|
||||
break;
|
||||
|
||||
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.directionalButtonsSupported = true;
|
||||
section.systemProperties.abxyButtonsOriented = 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;
|
||||
|
||||
case NpadControllerType::JoyconDual:
|
||||
section.header.type = NpadControllerType::JoyconDual;
|
||||
section.deviceType.joyconLeft = true;
|
||||
section.deviceType.joyconRight = true;
|
||||
section.header.assignment = NpadJoyAssignment::Single;
|
||||
section.systemProperties.ABXYButtonOriented = true;
|
||||
section.header.assignment = NpadJoyAssignment::Dual;
|
||||
|
||||
section.systemProperties.directionalButtonsSupported = true;
|
||||
section.systemProperties.abxyButtonsOriented = true;
|
||||
section.systemProperties.plusButtonCapability = true;
|
||||
section.systemProperties.minusButtonCapability = true;
|
||||
|
||||
connectionState.connected = true;
|
||||
connectionState.leftJoyconConnected = true;
|
||||
connectionState.rightJoyconConnected = true;
|
||||
break;
|
||||
|
||||
case NpadControllerType::JoyconLeft:
|
||||
section.header.type = NpadControllerType::JoyconLeft;
|
||||
section.deviceType.joyconLeft = true;
|
||||
section.header.assignment = NpadJoyAssignment::Single;
|
||||
|
||||
section.systemProperties.directionalButtonsSupported = true;
|
||||
section.systemProperties.slSrButtonOriented = true;
|
||||
section.systemProperties.minusButtonCapability = true;
|
||||
|
||||
connectionState.connected = true;
|
||||
connectionState.leftJoyconConnected = true;
|
||||
break;
|
||||
|
||||
case NpadControllerType::JoyconRight:
|
||||
section.header.type = NpadControllerType::JoyconRight;
|
||||
section.deviceType.joyconRight = true;
|
||||
section.header.assignment = NpadJoyAssignment::Single;
|
||||
|
||||
section.systemProperties.abxyButtonsOriented = true;
|
||||
section.systemProperties.slSrButtonOriented = true;
|
||||
section.systemProperties.plusButtonCapability = true;
|
||||
|
||||
connectionState.connected = true;
|
||||
connectionState.rightJoyconConnected = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw exception("Unsupported controller type: {}", type);
|
||||
}
|
||||
|
||||
controllerType = type;
|
||||
switch (type) {
|
||||
case NpadControllerType::ProController:
|
||||
case NpadControllerType::JoyconLeft:
|
||||
case NpadControllerType::JoyconRight:
|
||||
section.header.singleColorStatus = NpadColorReadStatus::Success;
|
||||
section.header.dualColorStatus = NpadColorReadStatus::Disconnected;
|
||||
section.header.singleColor = {0, 0};
|
||||
break;
|
||||
|
||||
section.header.singleColorStatus = NpadColorReadStatus::Success;
|
||||
section.header.singleColor = {0, 0};
|
||||
case NpadControllerType::Handheld:
|
||||
case NpadControllerType::JoyconDual:
|
||||
section.header.singleColorStatus = NpadColorReadStatus::Disconnected;
|
||||
section.header.dualColorStatus = NpadColorReadStatus::Success;
|
||||
section.header.leftColor = {0, 0};
|
||||
section.header.rightColor = {0, 0};
|
||||
break;
|
||||
|
||||
section.header.dualColorStatus = NpadColorReadStatus::Success;
|
||||
section.header.leftColor = {0, 0}; //TODO: make these configurable
|
||||
section.header.rightColor = {0, 0};
|
||||
case NpadControllerType::None:
|
||||
break;
|
||||
}
|
||||
|
||||
section.batteryLevel[0] = NpadBatteryLevel::Full;
|
||||
section.batteryLevel[1] = NpadBatteryLevel::Full;
|
||||
section.batteryLevel[2] = NpadBatteryLevel::Full;
|
||||
section.singleBatteryLevel = NpadBatteryLevel::Full;
|
||||
section.leftBatteryLevel = NpadBatteryLevel::Full;
|
||||
section.rightBatteryLevel = NpadBatteryLevel::Full;
|
||||
|
||||
this->type = type;
|
||||
controllerInfo = &GetControllerInfo();
|
||||
|
||||
SetButtonState(NpadButton{}, NpadButtonState::Released);
|
||||
}
|
||||
|
||||
void NpadDevice::Disconnect() {
|
||||
connectionState.connected = false;
|
||||
section = {};
|
||||
explicitAssignment = false;
|
||||
globalTimestamp = 0;
|
||||
|
||||
GetNextEntry(GetControllerInfo());
|
||||
GetNextEntry(section.systemExtController);
|
||||
type = NpadControllerType::None;
|
||||
controllerInfo = nullptr;
|
||||
}
|
||||
|
||||
NpadControllerInfo &NpadDevice::GetControllerInfo() {
|
||||
switch (controllerType) {
|
||||
switch (type) {
|
||||
case NpadControllerType::ProController:
|
||||
return section.fullKeyController;
|
||||
case NpadControllerType::Handheld:
|
||||
@ -79,15 +142,13 @@ namespace skyline::input {
|
||||
case NpadControllerType::JoyconRight:
|
||||
return section.rightController;
|
||||
default:
|
||||
throw exception("Cannot find corresponding section for ControllerType: {}", controllerType);
|
||||
throw exception("Cannot find corresponding section for ControllerType: {}", type);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -106,44 +167,118 @@ namespace skyline::input {
|
||||
}
|
||||
|
||||
void NpadDevice::SetButtonState(NpadButton mask, NpadButtonState state) {
|
||||
if (!connectionState.connected)
|
||||
if (!connectionState.connected || !controllerInfo)
|
||||
return;
|
||||
|
||||
for (NpadControllerInfo &controllerInfo : {std::ref(GetControllerInfo()), std::ref(section.systemExtController)}) {
|
||||
auto &entry = GetNextEntry(controllerInfo);
|
||||
auto &entry = GetNextEntry(*controllerInfo);
|
||||
|
||||
if (state == NpadButtonState::Pressed)
|
||||
entry.buttons.raw |= mask.raw;
|
||||
else
|
||||
entry.buttons.raw &= ~(mask.raw);
|
||||
if (state == NpadButtonState::Pressed)
|
||||
entry.buttons.raw |= mask.raw;
|
||||
else
|
||||
entry.buttons.raw &= ~mask.raw;
|
||||
|
||||
if ((type == NpadControllerType::JoyconLeft || type == NpadControllerType::JoyconRight) && manager.orientation == NpadJoyOrientation::Horizontal) {
|
||||
NpadButton orientedMask{};
|
||||
|
||||
if (mask.dpadUp)
|
||||
orientedMask.dpadLeft = true;
|
||||
if (mask.dpadDown)
|
||||
orientedMask.dpadRight = true;
|
||||
if (mask.dpadLeft)
|
||||
orientedMask.dpadDown = true;
|
||||
if (mask.dpadRight)
|
||||
orientedMask.dpadUp = true;
|
||||
|
||||
if (mask.leftSl || mask.rightSl)
|
||||
orientedMask.l = true;
|
||||
if (mask.leftSr || mask.rightSr)
|
||||
orientedMask.r = true;
|
||||
|
||||
orientedMask.a = mask.a;
|
||||
orientedMask.b = mask.b;
|
||||
orientedMask.x = mask.x;
|
||||
orientedMask.y = mask.y;
|
||||
orientedMask.leftStick = mask.leftStick;
|
||||
orientedMask.rightStick = mask.rightStick;
|
||||
orientedMask.plus = mask.plus;
|
||||
orientedMask.minus = mask.minus;
|
||||
orientedMask.leftSl = mask.leftSl;
|
||||
orientedMask.leftSr = mask.leftSr;
|
||||
orientedMask.rightSl = mask.rightSl;
|
||||
orientedMask.rightSr = mask.rightSr;
|
||||
|
||||
mask = orientedMask;
|
||||
}
|
||||
|
||||
for (NpadControllerState& controllerEntry : {std::ref(GetNextEntry(section.defaultController)), std::ref(GetNextEntry(section.digitalController))})
|
||||
if (state == NpadButtonState::Pressed)
|
||||
controllerEntry.buttons.raw |= mask.raw;
|
||||
else
|
||||
controllerEntry.buttons.raw &= ~mask.raw;
|
||||
|
||||
globalTimestamp++;
|
||||
}
|
||||
|
||||
void NpadDevice::SetAxisValue(NpadAxisId axis, i32 value) {
|
||||
if (!connectionState.connected)
|
||||
if (!connectionState.connected || !controllerInfo)
|
||||
return;
|
||||
|
||||
for (NpadControllerInfo &controllerInfo : {std::ref(GetControllerInfo()), std::ref(section.systemExtController)}) {
|
||||
auto &entry = GetNextEntry(controllerInfo);
|
||||
auto &controllerEntry = GetNextEntry(*controllerInfo);
|
||||
auto& defaultEntry = GetNextEntry(section.defaultController);
|
||||
|
||||
if (manager.orientation == NpadJoyOrientation::Vertical && (type != NpadControllerType::JoyconLeft && type != NpadControllerType::JoyconRight)) {
|
||||
switch (axis) {
|
||||
case NpadAxisId::LX:
|
||||
entry.leftX = value;
|
||||
controllerEntry.leftX = value;
|
||||
defaultEntry.leftX = value;
|
||||
break;
|
||||
case NpadAxisId::LY:
|
||||
entry.leftY = -value; // Invert Y axis
|
||||
controllerEntry.leftY = value;
|
||||
defaultEntry.leftY = value;
|
||||
break;
|
||||
case NpadAxisId::RX:
|
||||
entry.rightX = value;
|
||||
controllerEntry.rightX = value;
|
||||
defaultEntry.rightX = value;
|
||||
break;
|
||||
case NpadAxisId::RY:
|
||||
entry.rightY = -value; // Invert Y axis
|
||||
controllerEntry.rightY = value;
|
||||
defaultEntry.rightY = value;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (axis) {
|
||||
case NpadAxisId::LX:
|
||||
controllerEntry.leftX = value;
|
||||
defaultEntry.leftY = value;
|
||||
break;
|
||||
case NpadAxisId::LY:
|
||||
controllerEntry.leftY = value;
|
||||
defaultEntry.leftX = -value;
|
||||
break;
|
||||
case NpadAxisId::RX:
|
||||
controllerEntry.rightX = value;
|
||||
defaultEntry.rightY = value;
|
||||
break;
|
||||
case NpadAxisId::RY:
|
||||
controllerEntry.rightY = value;
|
||||
defaultEntry.rightX = -value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto& digitalEntry = GetNextEntry(section.digitalController);
|
||||
constexpr i16 threshold = 3276; // A 10% deadzone for the stick
|
||||
|
||||
digitalEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold;
|
||||
digitalEntry.buttons.leftStickDown = defaultEntry.leftY <= -threshold;
|
||||
digitalEntry.buttons.leftStickLeft = defaultEntry.leftX <= -threshold;
|
||||
digitalEntry.buttons.leftStickRight = defaultEntry.leftX >= threshold;
|
||||
|
||||
digitalEntry.buttons.rightStickUp = defaultEntry.rightY >= threshold;
|
||||
digitalEntry.buttons.rightStickDown = defaultEntry.rightY <= -threshold;
|
||||
digitalEntry.buttons.rightStickLeft = defaultEntry.rightX <= -threshold;
|
||||
digitalEntry.buttons.rightStickRight = defaultEntry.rightX >= threshold;
|
||||
|
||||
globalTimestamp++;
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ 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
|
||||
enum class NpadJoyOrientation : i64 {
|
||||
Vertical = 0, //!< The Joy-Con is held vertically (Default)
|
||||
Horizontal = 1, //!< The Joy-Con is held horizontally
|
||||
};
|
||||
|
||||
@ -18,6 +18,7 @@ namespace skyline::input {
|
||||
* @brief This holds all the NPad styles (https://switchbrew.org/wiki/HID_services#NpadStyleTag)
|
||||
*/
|
||||
union NpadStyleSet {
|
||||
u32 raw;
|
||||
struct {
|
||||
bool proController : 1; //!< The Pro Controller
|
||||
bool joyconHandheld : 1; //!< Joy-Cons in handheld mode
|
||||
@ -30,7 +31,6 @@ namespace skyline::input {
|
||||
bool nesHandheld : 1; //!< NES controller in handheld mode
|
||||
bool snes : 1; //!< SNES controller
|
||||
};
|
||||
u32 raw;
|
||||
};
|
||||
static_assert(sizeof(NpadStyleSet) == 0x4);
|
||||
|
||||
@ -46,10 +46,10 @@ namespace skyline::input {
|
||||
* @brief This enumerates all of the axis on NPad controllers
|
||||
*/
|
||||
enum class NpadAxisId {
|
||||
LX, //!< Left Stick X
|
||||
LY, //!< Left Stick Y
|
||||
RX, //!< Right Stick X
|
||||
RY, //!< Right Stick Y
|
||||
LX, //!< Left Stick X
|
||||
LY //!< Left Stick Y
|
||||
};
|
||||
|
||||
/**
|
||||
@ -68,14 +68,17 @@ namespace skyline::input {
|
||||
Handheld = 0x20 //!< Handheld mode
|
||||
};
|
||||
|
||||
class NpadManager;
|
||||
|
||||
/**
|
||||
* @brief This class abstracts a single NPad device that controls it's own state and shared memory section
|
||||
*/
|
||||
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
|
||||
NpadManager &manager; //!< The manager responsible for managing this NpadDevice
|
||||
NpadSection §ion; //!< The section in HID shared memory for this controller
|
||||
NpadControllerInfo *controllerInfo; //!< The controller info for this controller's type
|
||||
u64 globalTimestamp{}; //!< The global timestamp of the state entries
|
||||
|
||||
/**
|
||||
* @brief This updates the headers and creates a new entry in HID Shared Memory
|
||||
@ -90,21 +93,37 @@ namespace skyline::input {
|
||||
NpadControllerInfo &GetControllerInfo();
|
||||
|
||||
public:
|
||||
bool supported{false}; //!< If this specific NpadId was marked by the application as supported
|
||||
NpadId id; //!< The ID of this controller
|
||||
NpadControllerType type{}; //!< The type of this controller
|
||||
NpadConnectionState connectionState{}; //!< The state of the connection
|
||||
bool explicitAssignment{false}; //!< If an assignment has explicitly been set or is the default for this controller
|
||||
|
||||
NpadDevice(NpadSection §ion, NpadId id);
|
||||
NpadDevice(NpadManager &manager, NpadSection §ion, NpadId id);
|
||||
|
||||
/**
|
||||
* @brief This sets a Joy-Con's Assignment Mode
|
||||
* @param assignment The assignment mode to set this controller to
|
||||
* @param isDefault If this is setting the default assignment mode of the controller
|
||||
*/
|
||||
inline void SetAssignment(NpadJoyAssignment assignment) {
|
||||
section.header.assignment = assignment;
|
||||
inline void SetAssignment(NpadJoyAssignment assignment, bool isDefault) {
|
||||
if (!isDefault) {
|
||||
section.header.assignment = assignment;
|
||||
explicitAssignment = true;
|
||||
} else if (!explicitAssignment) {
|
||||
section.header.assignment = assignment;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The assignment mode of this Joy-Con
|
||||
*/
|
||||
inline NpadJoyAssignment GetAssignment() {
|
||||
return section.header.assignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This connects this controller to the guest
|
||||
* @param type The type of controller to connect
|
||||
* @param type The type of controller to connect as
|
||||
*/
|
||||
void Connect(NpadControllerType type);
|
||||
|
||||
|
@ -10,6 +10,7 @@ namespace skyline::input {
|
||||
* @brief This enumerates all the modifier keys that can be used
|
||||
*/
|
||||
union ModifierKey {
|
||||
u64 raw;
|
||||
struct {
|
||||
bool LControl : 1; //!< Left Control Key
|
||||
bool LShift : 1; //!< Left Shift Key
|
||||
@ -23,7 +24,6 @@ namespace skyline::input {
|
||||
bool ScrLock : 1; //!< Scroll-Lock Key
|
||||
bool NumLock : 1; //!< Num-Lock Key
|
||||
};
|
||||
u64 raw;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@ namespace skyline::input {
|
||||
* @brief This enumerates all the possible assignments of the Joy-Con(s)
|
||||
*/
|
||||
enum class NpadJoyAssignment : u32 {
|
||||
Dual = 0, //!< Dual Joy-Cons
|
||||
Dual = 0, //!< Dual Joy-Cons (Default)
|
||||
Single = 1, //!< Single Joy-Con
|
||||
};
|
||||
|
||||
@ -66,6 +66,7 @@ namespace skyline::input {
|
||||
* @brief This is a bit-field of all the buttons on an NPad (https://switchbrew.org/wiki/HID_Shared_Memory#NpadButton)
|
||||
*/
|
||||
union NpadButton {
|
||||
u64 raw;
|
||||
struct {
|
||||
bool a : 1; //!< The A button
|
||||
bool b : 1; //!< The B button
|
||||
@ -91,12 +92,11 @@ namespace skyline::input {
|
||||
bool rightStickUp : 1; //!< Right stick up
|
||||
bool rightStickRight : 1; //!< Right stick right
|
||||
bool rightStickDown : 1; //!< Right stick down
|
||||
bool leftSL : 1; //!< Left Joy-Con SL button
|
||||
bool leftSl : 1; //!< Left Joy-Con SL button
|
||||
bool leftSr : 1; //!< Left Joy-Con SR button
|
||||
bool rightSl : 1; //!< Right Joy-Con SL button
|
||||
bool rightSr : 1; //!< Right Joy-Con SR button
|
||||
};
|
||||
u64 raw;
|
||||
};
|
||||
static_assert(sizeof(NpadButton) == 0x8);
|
||||
|
||||
@ -104,15 +104,15 @@ namespace skyline::input {
|
||||
* @brief This structure holds data about the state of the connection with the controller
|
||||
*/
|
||||
union NpadConnectionState {
|
||||
u64 raw;
|
||||
struct {
|
||||
bool connected : 1; //!< If the controller is connected
|
||||
bool connected : 1; //!< If the controller is connected (Not in handheld mode)
|
||||
bool handheld : 1; //!< If the controller is in handheld mode
|
||||
bool leftJoyconConnected : 1; //!< If the left Joy-Con is connected
|
||||
bool leftJoyconConnected : 1; //!< If the left Joy-Con is connected (Not in handheld mode)
|
||||
bool leftJoyconHandheld : 1; //!< If the left Joy-Con is handheld
|
||||
bool rightJoyconConnected : 1; //!< If the right Joy-Con is connected
|
||||
bool rightJoyconConnected : 1; //!< If the right Joy-Con is connected (Not in handheld mode)
|
||||
bool rightJoyconHandheld : 1; //!< If the right Joy-Con is handheld
|
||||
};
|
||||
u64 raw;
|
||||
};
|
||||
static_assert(sizeof(NpadConnectionState) == 0x8);
|
||||
|
||||
@ -184,6 +184,7 @@ namespace skyline::input {
|
||||
* @brief This is a bit-field of all the device types (https://switchbrew.org/wiki/HID_services#DeviceType)
|
||||
*/
|
||||
union NpadDeviceType {
|
||||
u32 raw;
|
||||
struct {
|
||||
bool fullKey : 1; //!< Pro/GC controller
|
||||
bool debugPad : 1; //!< Debug controller
|
||||
@ -204,7 +205,6 @@ namespace skyline::input {
|
||||
u32 _unk_ : 15;
|
||||
bool system : 1; //!< Generic controller
|
||||
};
|
||||
u32 raw;
|
||||
};
|
||||
static_assert(sizeof(NpadDeviceType) == 0x4);
|
||||
|
||||
@ -212,23 +212,23 @@ namespace skyline::input {
|
||||
* @brief This structure holds the system properties of this NPad (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSystemProperties)
|
||||
*/
|
||||
union NpadSystemProperties {
|
||||
struct {
|
||||
bool singleCharging : 1; //!< Info 0 Charging
|
||||
bool leftCharging : 1; //!< Info 1 Charging
|
||||
bool rightCharging : 1; //!< Info 2 Charging
|
||||
bool singlePowerConnected : 1; //!< Info 0 Connected
|
||||
bool leftPowerConnected : 1; //!< Info 1 Connected
|
||||
bool rightPowerConnected : 1; //!< Info 2 Connected
|
||||
u64 _unk_ : 3;
|
||||
bool unsupportedButtonPressedSystem : 1; //!< Unsupported buttons are pressed on system controller
|
||||
bool unsupportedButtonPressedSystemExt : 1; //!< Unsupported buttons are pressed on system external controller
|
||||
bool ABXYButtonOriented : 1; //!< Are the ABXY Buttons oriented
|
||||
bool SLSRuttonOriented : 1; //!< Are the SLSR Buttons oriented
|
||||
bool plusButtonCapability : 1; //!< Does the + button exist
|
||||
bool minusButtonCapability : 1; //!< Does the - button exist
|
||||
bool directionalButtonsSupported : 1; //!< Does the controller have a D-Pad
|
||||
};
|
||||
u64 raw;
|
||||
struct {
|
||||
bool singleCharging : 1; //!< If a single unit is charging (Handheld, Pro-Con)
|
||||
bool leftCharging : 1; //!< If the left Joy-Con is charging
|
||||
bool rightCharging : 1; //!< If the right Joy-Con is charging
|
||||
bool singlePowerConnected : 1; //!< If a single unit is connected to a power source (Handheld, Pro-Con)
|
||||
bool leftPowerConnected : 1; //!< If the left Joy-Con is connected to a power source
|
||||
bool rightPowerConnected : 1; //!< If the right Joy-Con is connected to a power source
|
||||
u64 _unk_ : 3;
|
||||
bool unsupportedButtonPressedSystem : 1; //!< If an unsupported buttons was pressed on system controller
|
||||
bool unsupportedButtonPressedSystemExt : 1; //!< If an unsupported buttons was pressed on system external controller
|
||||
bool abxyButtonsOriented : 1; //!< If the ABXY Buttons oriented
|
||||
bool slSrButtonOriented : 1; //!< If the SL/SR Buttons oriented
|
||||
bool plusButtonCapability : 1; //!< If the + button exists
|
||||
bool minusButtonCapability : 1; //!< If the - button exists
|
||||
bool directionalButtonsSupported : 1; //!< If the controller has a D-Pad
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(NpadSystemProperties) == 0x8);
|
||||
|
||||
@ -236,10 +236,10 @@ namespace skyline::input {
|
||||
* @brief This structure holds data about the System Buttons (Home, Sleep and Capture) on an NPad (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSystemButtonProperties)
|
||||
*/
|
||||
union NpadSystemButtonProperties {
|
||||
u32 raw;
|
||||
struct {
|
||||
bool unintendedHomeButtonInputProtectionEnabled : 1; //!< If the Unintended Home Button Input Protection is enabled or not
|
||||
};
|
||||
u32 raw;
|
||||
};
|
||||
static_assert(sizeof(NpadSystemButtonProperties) == 0x4);
|
||||
|
||||
@ -262,11 +262,11 @@ namespace skyline::input {
|
||||
|
||||
NpadControllerInfo fullKeyController; //!< The Pro/GC controller data
|
||||
NpadControllerInfo handheldController; //!< The Handheld controller data
|
||||
NpadControllerInfo dualController; //!< The Dual Joy-Con controller data
|
||||
NpadControllerInfo leftController; //!< The Left Joy-Con controller data
|
||||
NpadControllerInfo rightController; //!< The Right Joy-Con controller data
|
||||
NpadControllerInfo palmaController; //!< The Poké Ball Plus controller data
|
||||
NpadControllerInfo systemExtController; //!< The System External controller data
|
||||
NpadControllerInfo dualController; //!< The Dual Joy-Con controller data (Only in Dual Mode, no input rotation based on rotation)
|
||||
NpadControllerInfo leftController; //!< The Left Joy-Con controller data (Only in Single Mode, no input rotation based on rotation)
|
||||
NpadControllerInfo rightController; //!< The Right Joy-Con controller data (Only in Single Mode, no input rotation based on rotation)
|
||||
NpadControllerInfo digitalController; //!< The Default Digital controller data (Same as Default but Analog Sticks are converted into 8-directional Digital Sticks)
|
||||
NpadControllerInfo defaultController; //!< The Default controller data (Inputs are rotated based on orientation and SL/SR are mapped to L/R incase it is a single JC)
|
||||
|
||||
NpadSixaxisInfo fullKeySixaxis; //!< The Pro/GC IMU data
|
||||
NpadSixaxisInfo handheldSixaxis; //!< The Handheld IMU data
|
||||
@ -281,7 +281,9 @@ namespace skyline::input {
|
||||
|
||||
NpadSystemProperties systemProperties; //!< The system properties of this controller
|
||||
NpadSystemButtonProperties buttonProperties; //!< The system button properties of this controller
|
||||
NpadBatteryLevel batteryLevel[3]; //!< The battery level of this controller
|
||||
NpadBatteryLevel singleBatteryLevel; //!< The battery level of a single unit (Handheld, Pro-Con)
|
||||
NpadBatteryLevel leftBatteryLevel; //!< The battery level of the left Joy-Con
|
||||
NpadBatteryLevel rightBatteryLevel; //!< The battery level of the right Joy-Con
|
||||
|
||||
u32 _pad1_[0x395];
|
||||
};
|
||||
|
@ -9,7 +9,8 @@
|
||||
namespace skyline {
|
||||
namespace constant {
|
||||
constexpr u8 HidEntryCount = 17; //!< The amount of entries in each HID device
|
||||
constexpr u8 NpadCount = 10; //!< The number of npads in shared memory
|
||||
constexpr u8 NpadCount = 10; //!< The amount of NPads in shared memory
|
||||
constexpr u8 ControllerCount = 8; //!< The maximum amount of host controllers
|
||||
constexpr u32 NpadBatteryFull = 2; //!< The full battery state of an npad
|
||||
}
|
||||
|
||||
@ -19,9 +20,9 @@ namespace skyline {
|
||||
*/
|
||||
struct CommonHeader {
|
||||
u64 timestamp; //!< The timestamp of the latest entry in ticks
|
||||
u64 entryCount; //!< The number of entries (17)
|
||||
u64 entryCount{constant::HidEntryCount}; //!< The number of entries (17)
|
||||
u64 currentEntry; //!< The index of the latest entry
|
||||
u64 maxEntry; //!< The maximum entry index (16)
|
||||
u64 maxEntry{constant::HidEntryCount - 1}; //!< The maximum entry index (16)
|
||||
};
|
||||
static_assert(sizeof(CommonHeader) == 0x20);
|
||||
}
|
||||
|
@ -89,11 +89,9 @@ namespace skyline {
|
||||
try {
|
||||
while (true) {
|
||||
std::lock_guard guard(JniMtx);
|
||||
|
||||
if (Halt)
|
||||
break;
|
||||
|
||||
state.gpu->Loop();
|
||||
if (!Halt) {
|
||||
state.gpu->Loop();
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
state.logger->Error(e.what());
|
||||
|
@ -17,9 +17,7 @@ namespace skyline::service::hid {
|
||||
{0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)},
|
||||
{0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)},
|
||||
{0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)}
|
||||
}) {
|
||||
state.input->npad.Activate();
|
||||
}
|
||||
}) {}
|
||||
|
||||
void IHidServer::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
manager.RegisterService(SRVREG(IAppletResource), session, response);
|
||||
@ -36,13 +34,15 @@ namespace skyline::service::hid {
|
||||
const auto &buffer = request.inputBuf.at(0);
|
||||
u64 address = buffer.address;
|
||||
size_t size = buffer.size / sizeof(NpadId);
|
||||
std::vector<NpadId> supportedIds;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
auto id = state.process->GetObject<NpadId>(address);
|
||||
state.input->npad.at(id).supported = true;
|
||||
|
||||
supportedIds.push_back(state.process->GetObject<NpadId>(address));
|
||||
address += sizeof(NpadId);
|
||||
}
|
||||
|
||||
state.input->npad.supportedIds = supportedIds;
|
||||
state.input->npad.Update();
|
||||
}
|
||||
|
||||
void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
@ -60,16 +60,17 @@ namespace skyline::service::hid {
|
||||
|
||||
void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto id = request.Pop<NpadId>();
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single, true);
|
||||
state.input->npad.Update();
|
||||
}
|
||||
|
||||
void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto id = request.Pop<NpadId>();
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single, false);
|
||||
}
|
||||
|
||||
void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto id = request.Pop<NpadId>();
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual);
|
||||
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual, false);
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,7 @@ import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.PreferenceManager
|
||||
import emu.skyline.input.AxisId
|
||||
import emu.skyline.input.ButtonId
|
||||
import emu.skyline.input.ButtonState
|
||||
import emu.skyline.input.*
|
||||
import emu.skyline.loader.getRomFormat
|
||||
import kotlinx.android.synthetic.main.emu_activity.*
|
||||
import java.io.File
|
||||
@ -38,14 +36,26 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
*/
|
||||
private lateinit var preferenceFd : ParcelFileDescriptor
|
||||
|
||||
/**
|
||||
* The [InputManager] class handles loading/saving the input data
|
||||
*/
|
||||
lateinit var input : InputManager
|
||||
|
||||
/**
|
||||
* A boolean flag denoting the current operation mode of the emulator (Docked = true/Handheld = false)
|
||||
*/
|
||||
private var operationMode : Boolean = true
|
||||
|
||||
/**
|
||||
* The surface object used for displaying frames
|
||||
*/
|
||||
@Volatile
|
||||
private var surface : Surface? = null
|
||||
|
||||
/**
|
||||
* A boolean flag denoting if the emulation thread should call finish() or not
|
||||
*/
|
||||
@Volatile
|
||||
private var shouldFinish : Boolean = true
|
||||
|
||||
/**
|
||||
@ -89,14 +99,63 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
private external fun getFrametime() : Float
|
||||
|
||||
/**
|
||||
* This sets the state of a specific button
|
||||
* This initializes a guest controller in libskyline
|
||||
*
|
||||
* @param index The arbitrary index of the controller, this is to handle matching with a partner Joy-Con
|
||||
* @param type The type of the host controller
|
||||
* @param partnerIndex The index of a partner Joy-Con if there is one
|
||||
*/
|
||||
private external fun setButtonState(id : Long, state : Int)
|
||||
private external fun setController(index : Int, type : Int, partnerIndex : Int = -1)
|
||||
|
||||
/**
|
||||
* This sets the value of a specific axis
|
||||
* This flushes the controller updates on the guest
|
||||
*/
|
||||
private external fun setAxisValue(id : Int, value : Int)
|
||||
private external fun updateControllers()
|
||||
|
||||
/**
|
||||
* This sets the state of the buttons specified in the mask on a specific controller
|
||||
*
|
||||
* @param index The index of the controller this is directed to
|
||||
* @param mask The mask of the button that are being set
|
||||
* @param state The state to set the button to
|
||||
*/
|
||||
private external fun setButtonState(index : Int, mask : Long, state : Int)
|
||||
|
||||
/**
|
||||
* This sets the value of a specific axis on a specific controller
|
||||
*
|
||||
* @param index The index of the controller this is directed to
|
||||
* @param axis The ID of the axis that is being modified
|
||||
* @param value The value to set the axis to
|
||||
*/
|
||||
private external fun setAxisValue(index : Int, axis : Int, value : Int)
|
||||
|
||||
/**
|
||||
* This initializes all of the controllers from [input] on the guest
|
||||
*/
|
||||
private fun initializeControllers() {
|
||||
for (entry in input.controllers) {
|
||||
val controller = entry.value
|
||||
|
||||
if (controller.type != ControllerType.None) {
|
||||
val type : Int = when (controller.type) {
|
||||
ControllerType.None -> throw IllegalArgumentException()
|
||||
ControllerType.HandheldProController -> if (operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id
|
||||
ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id
|
||||
}
|
||||
|
||||
val partnerIndex : Int? = when (controller) {
|
||||
is JoyConLeftController -> controller.partnerId
|
||||
is JoyConRightController -> controller.partnerId
|
||||
else -> null
|
||||
}
|
||||
|
||||
setController(entry.key, type, partnerIndex ?: -1)
|
||||
}
|
||||
}
|
||||
|
||||
updateControllers()
|
||||
}
|
||||
|
||||
/**
|
||||
* This executes the specified ROM, [preferenceFd] is assumed to be valid beforehand
|
||||
@ -108,9 +167,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
romFd = contentResolver.openFileDescriptor(rom, "r")!!
|
||||
|
||||
emulationThread = Thread {
|
||||
while ((surface == null))
|
||||
while (surface == null)
|
||||
Thread.yield()
|
||||
|
||||
runOnUiThread { initializeControllers() }
|
||||
|
||||
executeApplication(Uri.decode(rom.toString()), romType, romFd.fd, preferenceFd.fd, applicationContext.filesDir.canonicalPath + "/")
|
||||
|
||||
if (shouldFinish)
|
||||
@ -142,6 +203,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
||||
}
|
||||
|
||||
input = InputManager(this)
|
||||
|
||||
val preference = File("${applicationInfo.dataDir}/shared_prefs/${applicationInfo.packageName}_preferences.xml")
|
||||
preferenceFd = ParcelFileDescriptor.open(preference, ParcelFileDescriptor.MODE_READ_WRITE)
|
||||
|
||||
@ -150,16 +213,20 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
if (sharedPreferences.getBoolean("perf_stats", false)) {
|
||||
lateinit var perfRunnable : Runnable
|
||||
|
||||
perfRunnable = Runnable {
|
||||
perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms"
|
||||
perf_stats.postDelayed(perfRunnable, 250)
|
||||
val perfRunnable = object : Runnable {
|
||||
override fun run() {
|
||||
perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms"
|
||||
perf_stats.postDelayed(this, 250)
|
||||
}
|
||||
}
|
||||
|
||||
perf_stats.postDelayed(perfRunnable, 250)
|
||||
}
|
||||
|
||||
operationMode = sharedPreferences.getBoolean("operation_mode", operationMode)
|
||||
|
||||
windowManager.defaultDisplay.supportedModes.maxBy { it.refreshRate + (it.physicalHeight * it.physicalWidth) }?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||
|
||||
executeApplication(intent.data!!)
|
||||
}
|
||||
|
||||
@ -222,111 +289,91 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles passing on any key events to libskyline
|
||||
* This handles translating any [KeyHostEvent]s to a [GuestEvent] that is passed into libskyline
|
||||
*/
|
||||
override fun dispatchKeyEvent(event : KeyEvent) : Boolean {
|
||||
if (event.repeatCount != 0)
|
||||
return super.dispatchKeyEvent(event)
|
||||
|
||||
val action : ButtonState = when (event.action) {
|
||||
KeyEvent.ACTION_DOWN -> ButtonState.Pressed
|
||||
KeyEvent.ACTION_UP -> ButtonState.Released
|
||||
else -> return false
|
||||
else -> return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
val buttonMap : Map<Int, ButtonId> = mapOf(
|
||||
KeyEvent.KEYCODE_BUTTON_A to ButtonId.A,
|
||||
KeyEvent.KEYCODE_BUTTON_B to ButtonId.B,
|
||||
KeyEvent.KEYCODE_BUTTON_X to ButtonId.X,
|
||||
KeyEvent.KEYCODE_BUTTON_Y to ButtonId.Y,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBL to ButtonId.LeftStick,
|
||||
KeyEvent.KEYCODE_BUTTON_THUMBR to ButtonId.RightStick,
|
||||
KeyEvent.KEYCODE_BUTTON_L1 to ButtonId.L,
|
||||
KeyEvent.KEYCODE_BUTTON_R1 to ButtonId.R,
|
||||
KeyEvent.KEYCODE_BUTTON_L2 to ButtonId.ZL,
|
||||
KeyEvent.KEYCODE_BUTTON_R2 to ButtonId.ZR,
|
||||
KeyEvent.KEYCODE_BUTTON_START to ButtonId.Plus,
|
||||
KeyEvent.KEYCODE_BUTTON_SELECT to ButtonId.Minus,
|
||||
KeyEvent.KEYCODE_DPAD_DOWN to ButtonId.DpadDown,
|
||||
KeyEvent.KEYCODE_DPAD_UP to ButtonId.DpadUp,
|
||||
KeyEvent.KEYCODE_DPAD_LEFT to ButtonId.DpadLeft,
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT to ButtonId.DpadRight)
|
||||
return when (val guestEvent = input.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
|
||||
is ButtonGuestEvent -> {
|
||||
if (guestEvent.button != ButtonId.Menu)
|
||||
setButtonState(guestEvent.id, guestEvent.button.value(), action.ordinal)
|
||||
true
|
||||
}
|
||||
|
||||
return try {
|
||||
setButtonState(buttonMap.getValue(event.keyCode).value(), action.ordinal)
|
||||
true
|
||||
} catch (ignored : NoSuchElementException) {
|
||||
super.dispatchKeyEvent(event)
|
||||
is AxisGuestEvent -> {
|
||||
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (if (action == ButtonState.Pressed) if (guestEvent.polarity) Short.MAX_VALUE else Short.MIN_VALUE else 0).toInt())
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.dispatchKeyEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the controller HAT X value
|
||||
* The last value of the axes so the stagnant axes can be eliminated to not wastefully look them up
|
||||
*/
|
||||
private var controllerHatX : Float = 0.0f
|
||||
private val axesHistory = arrayOfNulls<Float>(MotionHostEvent.axes.size)
|
||||
|
||||
/**
|
||||
* This is the controller HAT Y value
|
||||
* The last value of the HAT axes so it can be ignored in [onGenericMotionEvent] so they are handled by [dispatchKeyEvent] instead
|
||||
*/
|
||||
private var controllerHatY : Float = 0.0f
|
||||
private var oldHat = Pair(0.0f, 0.0f)
|
||||
|
||||
/**
|
||||
* This handles passing on any motion events to libskyline
|
||||
* This handles translating any [MotionHostEvent]s to a [GuestEvent] that is passed into libskyline
|
||||
*/
|
||||
override fun dispatchGenericMotionEvent(event : MotionEvent) : Boolean {
|
||||
if ((event.source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD ||
|
||||
(event.source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) {
|
||||
val hatXMap : Map<Float, ButtonId> = mapOf(
|
||||
-1.0f to ButtonId.DpadLeft,
|
||||
+1.0f to ButtonId.DpadRight)
|
||||
override fun onGenericMotionEvent(event : MotionEvent) : Boolean {
|
||||
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE) {
|
||||
val hat = Pair(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y))
|
||||
|
||||
val hatYMap : Map<Float, ButtonId> = mapOf(
|
||||
-1.0f to ButtonId.DpadUp,
|
||||
+1.0f to ButtonId.DpadDown)
|
||||
if (hat == oldHat) {
|
||||
for (axisItem in MotionHostEvent.axes.withIndex()) {
|
||||
val axis = axisItem.value
|
||||
var value = event.getAxisValue(axis)
|
||||
|
||||
if (controllerHatX != event.getAxisValue(MotionEvent.AXIS_HAT_X)) {
|
||||
if (event.getAxisValue(MotionEvent.AXIS_HAT_X) == 0.0f)
|
||||
setButtonState(hatXMap.getValue(controllerHatX).value(), ButtonState.Released.ordinal)
|
||||
else
|
||||
setButtonState(hatXMap.getValue(event.getAxisValue(MotionEvent.AXIS_HAT_X)).value(), ButtonState.Pressed.ordinal)
|
||||
if ((event.historySize != 0 && value != event.getHistoricalAxisValue(axis, 0)) || (axesHistory[axisItem.index]?.let { it == value } == false)) {
|
||||
var polarity = value >= 0
|
||||
|
||||
controllerHatX = event.getAxisValue(MotionEvent.AXIS_HAT_X)
|
||||
val guestEvent = input.eventMap[MotionHostEvent(event.device.descriptor, axis, polarity)] ?: if (value == 0f) {
|
||||
polarity = false
|
||||
input.eventMap[MotionHostEvent(event.device.descriptor, axis, polarity)]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
when (guestEvent) {
|
||||
is ButtonGuestEvent -> {
|
||||
if (guestEvent.button != ButtonId.Menu)
|
||||
setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.ordinal else ButtonState.Released.ordinal)
|
||||
}
|
||||
|
||||
if (controllerHatY != event.getAxisValue(MotionEvent.AXIS_HAT_Y)) {
|
||||
if (event.getAxisValue(MotionEvent.AXIS_HAT_Y) == 0.0f)
|
||||
setButtonState(hatYMap.getValue(controllerHatY).value(), ButtonState.Released.ordinal)
|
||||
else
|
||||
setButtonState(hatYMap.getValue(event.getAxisValue(MotionEvent.AXIS_HAT_Y)).value(), ButtonState.Pressed.ordinal)
|
||||
is AxisGuestEvent -> {
|
||||
value = guestEvent.value(value)
|
||||
value = if (polarity) abs(value) else -abs(value)
|
||||
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value // TODO: Test this
|
||||
|
||||
controllerHatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (value * Short.MAX_VALUE).toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if ((event.source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.action == MotionEvent.ACTION_MOVE) {
|
||||
val axisMap : Map<Int, AxisId> = mapOf(
|
||||
MotionEvent.AXIS_X to AxisId.LX,
|
||||
MotionEvent.AXIS_Y to AxisId.LY,
|
||||
MotionEvent.AXIS_Z to AxisId.RX,
|
||||
MotionEvent.AXIS_RZ to AxisId.RY)
|
||||
|
||||
//TODO: Digital inputs based off of analog sticks
|
||||
event.device.motionRanges.forEach {
|
||||
if (axisMap.containsKey(it.axis)) {
|
||||
var axisValue : Float = event.getAxisValue(it.axis)
|
||||
if (abs(axisValue) <= it.flat)
|
||||
axisValue = 0.0f
|
||||
|
||||
val ratio : Float = axisValue / (it.max - it.min)
|
||||
val rangedAxisValue : Int = (ratio * (Short.MAX_VALUE - Short.MIN_VALUE)).toInt()
|
||||
|
||||
setAxisValue(axisMap.getValue(it.axis).ordinal, rangedAxisValue)
|
||||
axesHistory[axisItem.index] = value
|
||||
}
|
||||
|
||||
return true
|
||||
} else {
|
||||
oldHat = hat
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return super.dispatchGenericMotionEvent(event)
|
||||
return super.onGenericMotionEvent(event)
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ import java.io.Serializable
|
||||
* @param stringRes The string resource of the controller's name
|
||||
* @param firstController If the type only applies to the first controller
|
||||
*/
|
||||
enum class ControllerType(val stringRes : Int, val firstController : Boolean, val sticks : Array<StickId> = arrayOf(), val buttons : Array<ButtonId> = arrayOf()) {
|
||||
None(R.string.none, false),
|
||||
HandheldProController(R.string.handheld_procon, true, arrayOf(StickId.Left, StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.R, ButtonId.ZL, ButtonId.ZR, ButtonId.Plus, ButtonId.Minus)),
|
||||
ProController(R.string.procon, false, arrayOf(StickId.Left, StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.R, ButtonId.ZL, ButtonId.ZR, ButtonId.Plus, ButtonId.Minus)),
|
||||
JoyConLeft(R.string.ljoycon, false, arrayOf(StickId.Left), arrayOf(ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.ZL, ButtonId.Minus, ButtonId.LeftSL, ButtonId.LeftSR)),
|
||||
JoyConRight(R.string.rjoycon, false, arrayOf(StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.R, ButtonId.ZR, ButtonId.Plus, ButtonId.RightSL, ButtonId.RightSR)),
|
||||
enum class ControllerType(val stringRes : Int, val firstController : Boolean, val sticks : Array<StickId> = arrayOf(), val buttons : Array<ButtonId> = arrayOf(), val id : Int) {
|
||||
None(R.string.none, false, id=0b0),
|
||||
ProController(R.string.procon, false, arrayOf(StickId.Left, StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.R, ButtonId.ZL, ButtonId.ZR, ButtonId.Plus, ButtonId.Minus), 0b1),
|
||||
HandheldProController(R.string.handheld_procon, true, arrayOf(StickId.Left, StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.R, ButtonId.ZL, ButtonId.ZR, ButtonId.Plus, ButtonId.Minus), 0b10),
|
||||
JoyConLeft(R.string.ljoycon, false, arrayOf(StickId.Left), arrayOf(ButtonId.DpadUp, ButtonId.DpadDown, ButtonId.DpadLeft, ButtonId.DpadRight, ButtonId.L, ButtonId.ZL, ButtonId.Minus, ButtonId.LeftSL, ButtonId.LeftSR), 0b1000),
|
||||
JoyConRight(R.string.rjoycon, false, arrayOf(StickId.Right), arrayOf(ButtonId.A, ButtonId.B, ButtonId.X, ButtonId.Y, ButtonId.R, ButtonId.ZR, ButtonId.Plus, ButtonId.RightSL, ButtonId.RightSR), 0b10000),
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,10 +63,10 @@ enum class ButtonState(val state : Boolean) {
|
||||
* This enumerates all of the axes on a controller that the emulator recognizes
|
||||
*/
|
||||
enum class AxisId {
|
||||
RX,
|
||||
RY,
|
||||
LX,
|
||||
LY,
|
||||
RX,
|
||||
RY,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,6 +32,9 @@ abstract class HostEvent(val descriptor : String = "") : Serializable {
|
||||
abstract override fun hashCode() : Int
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents all events on the host that arise from a [KeyEvent]
|
||||
*/
|
||||
class KeyHostEvent(descriptor : String = "", val keyCode : Int) : HostEvent(descriptor) {
|
||||
/**
|
||||
* This returns the string representation of [keyCode]
|
||||
@ -49,7 +52,17 @@ class KeyHostEvent(descriptor : String = "", val keyCode : Int) : HostEvent(desc
|
||||
override fun hashCode() : Int = Objects.hash(descriptor, keyCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents all events on the host that arise from a [MotionEvent]
|
||||
*/
|
||||
class MotionHostEvent(descriptor : String = "", val axis : Int, val polarity : Boolean) : HostEvent(descriptor) {
|
||||
companion object {
|
||||
/**
|
||||
* This is an array of all the axes that are checked during a [MotionEvent]
|
||||
*/
|
||||
val axes = arrayOf(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER, MotionEvent.AXIS_THROTTLE, MotionEvent.AXIS_RUDDER, MotionEvent.AXIS_WHEEL, MotionEvent.AXIS_GAS, MotionEvent.AXIS_BRAKE).plus(IntRange(MotionEvent.AXIS_GENERIC_1, MotionEvent.AXIS_GENERIC_16).toList())
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns the string representation of [axis] combined with [polarity]
|
||||
*/
|
||||
|
@ -197,7 +197,7 @@ class ButtonDialog(val item : ControllerButtonItem) : BottomSheetDialogFragment(
|
||||
context.axisMap[(guestEvent as AxisGuestEvent).axis]?.update()
|
||||
}
|
||||
|
||||
guestEvent = ButtonGuestEvent(controller.id, item.button)
|
||||
guestEvent = ButtonGuestEvent(controller.id, item.button, threshold)
|
||||
|
||||
context.manager.eventMap.filterValues { it == guestEvent }.keys.forEach { context.manager.eventMap.remove(it) }
|
||||
|
||||
|
@ -17,6 +17,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import emu.skyline.R
|
||||
import emu.skyline.adapter.ControllerStickItem
|
||||
import emu.skyline.input.*
|
||||
import emu.skyline.input.MotionHostEvent.Companion.axes
|
||||
import kotlinx.android.synthetic.main.stick_dialog.*
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
@ -265,10 +266,6 @@ class StickDialog(val item : ControllerStickItem) : BottomSheetDialogFragment()
|
||||
var axisPolarity = false // The polarity of the axis for the currently selected event
|
||||
var axisRunnable : Runnable? = null // The Runnable that is used for counting down till an axis is selected
|
||||
|
||||
// The last values of the HAT axes so that they can be ignored in [View.OnGenericMotionListener] so they are passed onto [DialogInterface.OnKeyListener] as [KeyEvent]s
|
||||
var oldDpadX = 0.0f
|
||||
var oldDpadY = 0.0f
|
||||
|
||||
stick_next.setOnClickListener {
|
||||
gotoStage(1)
|
||||
|
||||
@ -304,11 +301,7 @@ class StickDialog(val item : ControllerStickItem) : BottomSheetDialogFragment()
|
||||
}
|
||||
|
||||
is AxisGuestEvent -> {
|
||||
val coefficient = if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
if (guestEvent.polarity) 1 else -1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val coefficient = if (event.action == KeyEvent.ACTION_DOWN) if (guestEvent.polarity) 1 else -1 else 0
|
||||
|
||||
if (guestEvent.axis == item.stick.xAxis) {
|
||||
stick_container?.translationX = dipToPixels(16.5f) * coefficient
|
||||
@ -402,18 +395,17 @@ class StickDialog(val item : ControllerStickItem) : BottomSheetDialogFragment()
|
||||
}
|
||||
}
|
||||
|
||||
val axes = arrayOf(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER, MotionEvent.AXIS_THROTTLE, MotionEvent.AXIS_RUDDER, MotionEvent.AXIS_WHEEL, MotionEvent.AXIS_GAS, MotionEvent.AXIS_BRAKE).plus(IntRange(MotionEvent.AXIS_GENERIC_1, MotionEvent.AXIS_GENERIC_16).toList())
|
||||
|
||||
val axesHistory = arrayOfNulls<Float>(axes.size) // The last recorded value of an axis, this is used to eliminate any stagnant axes
|
||||
val axesMax = Array(axes.size) { 0f } // The maximum recorded value of the axis, this is to scale the axis to a stick accordingly (The value is also checked at runtime, so it's fine if this isn't the true maximum)
|
||||
|
||||
var oldHat = Pair(0.0f, 0.0f) // The last values of the HAT axes so that they can be ignored in [View.OnGenericMotionListener] so they are passed onto [DialogInterface.OnKeyListener] as [KeyEvent]s
|
||||
|
||||
view?.setOnGenericMotionListener { _, event ->
|
||||
// We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler
|
||||
val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X)
|
||||
val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||
val hat = Pair(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y))
|
||||
|
||||
// We want all input events from Joysticks and Buttons that are [MotionEvent.ACTION_MOVE] and not from the D-pad
|
||||
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE && dpadX == oldDpadX && dpadY == oldDpadY) {
|
||||
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE && hat == oldHat) {
|
||||
if (stage == DialogStage.Stick) {
|
||||
// When the stick is being previewed after everything is mapped we do a lookup into [InputManager.eventMap] to find a corresponding [GuestEvent] and animate the stick correspondingly
|
||||
for (axisItem in axes.withIndex()) {
|
||||
@ -592,8 +584,7 @@ class StickDialog(val item : ControllerStickItem) : BottomSheetDialogFragment()
|
||||
|
||||
true
|
||||
} else {
|
||||
oldDpadX = dpadX
|
||||
oldDpadY = dpadY
|
||||
oldHat = hat
|
||||
|
||||
false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user