diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 70cc61568..09b5d0965 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -98,6 +98,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/services/am/applet/ILibraryAppletAccessor.cpp ${source_DIR}/skyline/services/hid/IHidServer.cpp ${source_DIR}/skyline/services/hid/IAppletResource.cpp + ${source_DIR}/skyline/services/hid/IActiveVibrationDeviceList.cpp ${source_DIR}/skyline/services/timesrv/IStaticService.cpp ${source_DIR}/skyline/services/timesrv/ISystemClock.cpp ${source_DIR}/skyline/services/timesrv/ISteadyClock.cpp diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 336dbf4fc..0c96d09cc 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -116,7 +116,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonSt auto device = input->npad.controllers[index].device; if (device) device->SetButtonState(skyline::input::NpadButton{.raw = static_cast(mask)}, pressed); - } catch (const std::bad_weak_ptr&) { + } catch (const std::bad_weak_ptr &) { // We don't mind if we miss button updates while input hasn't been initialized } } @@ -127,7 +127,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValu auto device = input->npad.controllers[index].device; if (device) device->SetAxisValue(static_cast(axis), value); - } catch (const std::bad_weak_ptr&) { + } catch (const std::bad_weak_ptr &) { // We don't mind if we miss axis updates while input hasn't been initialized } } diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index 33867f6da..a021bb201 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include diff --git a/app/src/main/cpp/skyline/gpu/gpfifo.h b/app/src/main/cpp/skyline/gpu/gpfifo.h index 6e4a780b5..268c848c8 100644 --- a/app/src/main/cpp/skyline/gpu/gpfifo.h +++ b/app/src/main/cpp/skyline/gpu/gpfifo.h @@ -4,7 +4,6 @@ #pragma once #include -#include #include #include "engines/engine.h" #include "engines/gpfifo.h" @@ -177,4 +176,4 @@ namespace skyline::gpu { void Push(std::span entries); }; } -} \ No newline at end of file +} diff --git a/app/src/main/cpp/skyline/gpu/memory_manager.h b/app/src/main/cpp/skyline/gpu/memory_manager.h index adcea594e..459ed9e23 100644 --- a/app/src/main/cpp/skyline/gpu/memory_manager.h +++ b/app/src/main/cpp/skyline/gpu/memory_manager.h @@ -3,7 +3,6 @@ #pragma once -#include #include namespace skyline { diff --git a/app/src/main/cpp/skyline/input/npad.cpp b/app/src/main/cpp/skyline/input/npad.cpp index 65020dfd1..3e2aec782 100644 --- a/app/src/main/cpp/skyline/input/npad.cpp +++ b/app/src/main/cpp/skyline/input/npad.cpp @@ -50,9 +50,13 @@ namespace skyline::input { if (style.raw) { if (style.proController || style.joyconHandheld || style.joyconLeft || style.joyconRight) { device.Connect(controller.type); + device.index = static_cast(&controller - controllers.data()); + device.partnerIndex = -1; controller.device = &device; } else if (style.joyconDual && orientation == NpadJoyOrientation::Vertical && device.GetAssignment() == NpadJoyAssignment::Dual) { device.Connect(NpadControllerType::JoyconDual); + device.index = static_cast(&controller - controllers.data()); + device.partnerIndex = controller.partnerIndex; controller.device = &device; controllers.at(controller.partnerIndex).device = &device; } else { diff --git a/app/src/main/cpp/skyline/input/npad_device.cpp b/app/src/main/cpp/skyline/input/npad_device.cpp index bbbb8ed53..1fb0ed17f 100644 --- a/app/src/main/cpp/skyline/input/npad_device.cpp +++ b/app/src/main/cpp/skyline/input/npad_device.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) +#include #include "npad_device.h" #include "npad.h" @@ -155,6 +156,9 @@ namespace skyline::input { section = {}; globalTimestamp = 0; + index = -1; + partnerIndex = -1; + type = NpadControllerType::None; controllerInfo = nullptr; @@ -347,4 +351,91 @@ namespace skyline::input { globalTimestamp++; } + + void NpadDevice::VibrateDevice(i8 vibrateIndex, const NpadVibrationValue &value) { + std::array timings; + std::array amplitudes; + + jlong periodLow = 1000 / value.frequencyLow; + jlong periodHigh = 1000 / value.frequencyHigh; + + jint amplitudeLow = value.amplitudeLow * 127; + jint amplitudeHigh = value.amplitudeHigh * 127; + + if (amplitudeLow + amplitudeHigh == 0 || periodLow + periodHigh == 0) { + manager.state.jvm->ClearVibrationDevice(vibrateIndex); + return; + } + + if (periodLow == periodHigh) { + timings = {periodLow, periodHigh, 0}; + amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), 0, 0}; + } else if (periodLow < periodHigh) { + timings = {periodLow, periodHigh - periodLow, periodHigh}; + amplitudes = {std::min(amplitudeLow + amplitudeHigh, 255), amplitudeHigh, 0}; + } else if (periodHigh < periodLow) { + timings = {periodHigh, periodLow - periodHigh, periodLow}; + amplitudes = {std::min(amplitudeHigh + amplitudeLow, 255), amplitudeLow, 0}; + } + + manager.state.jvm->VibrateDevice(vibrateIndex, timings, amplitudes); + } + + void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) { + if (isRight) + vibrationRight = value; + else + vibrationLeft = value; + + if (vibrationRight) + Vibrate(vibrationLeft, *vibrationRight); + else + VibrateDevice(index, value); + } + + void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) { + if (partnerIndex == -1) { + std::array timings; + std::array amplitudes; + + std::array, 4> vibrations{std::pair{1000 / left.frequencyLow, left.amplitudeLow * 64}, + {1000 / left.frequencyHigh, left.amplitudeHigh * 64}, + {1000 / right.frequencyLow, right.amplitudeLow * 64}, + {1000 / right.frequencyHigh, right.amplitudeHigh * 64}, + }; + + jlong totalTime{}; + std::sort(vibrations.begin(), vibrations.end(), [](const std::pair &a, const std::pair &b) { + return a.first < b.first; + }); + + jint totalAmplitude{}; + for (const auto &vibration : vibrations) + totalAmplitude += vibration.second; + + if (totalAmplitude == 0 || vibrations[3].first == 0) { + manager.state.jvm->ClearVibrationDevice(index); + return; + } + + for (u8 i{0}; i < vibrations.size(); i++) { + const auto &vibration = vibrations[i]; + + auto time = vibration.first - totalTime; + timings[i] = time; + totalTime += time; + + amplitudes[i] = std::min(totalAmplitude, 255); + totalAmplitude -= vibration.second; + } + + timings[4] = totalTime; + amplitudes[4] = 0; + + manager.state.jvm->VibrateDevice(index, timings, amplitudes); + } else { + VibrateDevice(index, left); + VibrateDevice(partnerIndex, right); + } + } } diff --git a/app/src/main/cpp/skyline/input/npad_device.h b/app/src/main/cpp/skyline/input/npad_device.h index 3c7503d69..4b7d83edd 100644 --- a/app/src/main/cpp/skyline/input/npad_device.h +++ b/app/src/main/cpp/skyline/input/npad_device.h @@ -61,6 +61,50 @@ namespace skyline::input { Handheld = 0x20, }; + /** + * @brief A handle to a specific device addressed by it's ID and type + * @note This is used by both Six-Axis and Vibration + */ + union __attribute__((__packed__)) NpadDeviceHandle { + u32 raw; + struct { + u8 type; + NpadId id : 8; + bool isRight : 1; //!< If this is a right Joy-Con (Both) or right LRA in the Pro-Controller (Vibration) + bool isSixAxisSingle : 1; //!< If the Six-Axis device is a single unit, either Handheld or Pro-Controller + }; + + constexpr NpadControllerType GetType() { + switch (type) { + case 3: + return NpadControllerType::ProController; + case 4: + return NpadControllerType::Handheld; + case 5: + return NpadControllerType::JoyconDual; + case 6: + return NpadControllerType::JoyconLeft; + case 7: + return NpadControllerType::JoyconRight; + } + return NpadControllerType::None; + } + }; + + /** + * @brief The parameters to produce a vibration using an LRA + * @note The vibration is broken into a frequency band with the lower and high range supplied + * @note Amplitude is in arbitrary units from 0f to 1f + * @note Frequency is in Hertz + */ + struct NpadVibrationValue { + float amplitudeLow; + float frequencyLow; + float amplitudeHigh; + float frequencyHigh; + }; + static_assert(sizeof(NpadVibrationValue) == 0x10); + class NpadManager; /** @@ -85,8 +129,14 @@ namespace skyline::input { */ NpadControllerInfo &GetControllerInfo(); + void VibrateDevice(i8 index, const NpadVibrationValue &value); + public: NpadId id; + i8 index{-1}; //!< The index of the device assigned to this player + i8 partnerIndex{-1}; //!< The index of a partner device, if present + NpadVibrationValue vibrationLeft; //!< Vibration for the left Joy-Con (Handheld/Pair), left LRA in a Pro-Controller or individual Joy-Cons + std::optional vibrationRight; //!< Vibration for the right Joy-Con (Handheld/Pair) or right LRA in a Pro-Controller NpadControllerType type{}; NpadConnectionState connectionState{}; std::shared_ptr updateEvent; //!< This event is triggered on the controller's style changing @@ -132,5 +182,9 @@ namespace skyline::input { * @param value The value to set */ void SetAxisValue(NpadAxisId axis, i32 value); + + void Vibrate(bool isRight, const NpadVibrationValue &value); + + void Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right); }; } diff --git a/app/src/main/cpp/skyline/input/sections/Npad.h b/app/src/main/cpp/skyline/input/sections/Npad.h index 9322a1b45..22b473a7e 100644 --- a/app/src/main/cpp/skyline/input/sections/Npad.h +++ b/app/src/main/cpp/skyline/input/sections/Npad.h @@ -147,38 +147,38 @@ namespace skyline::input { /** * @brief This structure is used to hold a single sample of 3D data from the IMU */ - struct SixaxisVector { + struct SixAxisVector { float x; //!< The data in the X-axis float y; //!< The data in the Y-axis float z; //!< The data in the Z-axis }; - static_assert(sizeof(SixaxisVector) == 0xC); + static_assert(sizeof(SixAxisVector) == 0xC); /** - * @brief This structure contains data about the state of the controller's IMU (Sixaxis) (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSixAxisSensorHandheldState) + * @brief This structure contains data about the state of the controller's IMU (Six-Axis) (https://switchbrew.org/wiki/HID_Shared_Memory#NpadSixAxisSensorHandheldState) */ - struct NpadSixaxisState { + struct NpadSixAxisState { u64 globalTimestamp; //!< The global timestamp in samples u64 _unk0_; u64 localTimestamp; //!< The local timestamp in samples - SixaxisVector accelerometer; - SixaxisVector gyroscope; - SixaxisVector rotation; - std::array orientation; //!< The orientation basis data as a matrix + SixAxisVector accelerometer; + SixAxisVector gyroscope; + SixAxisVector rotation; + std::array orientation; //!< The orientation basis data as a matrix u64 _unk2_; //!< This is always 1 }; - static_assert(sizeof(NpadSixaxisState) == 0x68); + static_assert(sizeof(NpadSixAxisState) == 0x68); /** - * @brief This structure contains header and entries for the IMU (Sixaxis) data + * @brief This structure contains header and entries for the IMU (Six-Axis) data */ - struct NpadSixaxisInfo { + struct NpadSixAxisInfo { CommonHeader header; - std::array state; + std::array state; }; - static_assert(sizeof(NpadSixaxisInfo) == 0x708); + static_assert(sizeof(NpadSixAxisInfo) == 0x708); /** * @brief This is a bit-field of all the device types (https://switchbrew.org/wiki/HID_services#DeviceType) @@ -268,12 +268,12 @@ namespace skyline::input { NpadControllerInfo palmaController; //!< The Poké Ball Plus controller data 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 - NpadSixaxisInfo dualLeftSixaxis; //!< The Left Joy-Con in dual mode's IMU data - NpadSixaxisInfo dualRightSixaxis; //!< The Left Joy-Con in dual mode's IMU data - NpadSixaxisInfo leftSixaxis; //!< The Left Joy-Con IMU data - NpadSixaxisInfo rightSixaxis; //!< The Right Joy-Con IMU data + NpadSixAxisInfo fullKeySixAxis; //!< The Pro/GC IMU data + NpadSixAxisInfo handheldSixAxis; //!< The Handheld IMU data + NpadSixAxisInfo dualLeftSixAxis; //!< The Left Joy-Con in dual mode's IMU data + NpadSixAxisInfo dualRightSixAxis; //!< The Left Joy-Con in dual mode's IMU data + NpadSixAxisInfo leftSixAxis; //!< The Left Joy-Con IMU data + NpadSixAxisInfo rightSixAxis; //!< The Right Joy-Con IMU data NpadDeviceType deviceType; diff --git a/app/src/main/cpp/skyline/jvm.cpp b/app/src/main/cpp/skyline/jvm.cpp index 0e915cb5f..b18dd7171 100644 --- a/app/src/main/cpp/skyline/jvm.cpp +++ b/app/src/main/cpp/skyline/jvm.cpp @@ -6,12 +6,17 @@ thread_local JNIEnv *env; namespace skyline { - JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")) { + JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(environ->NewGlobalRef(instance)), instanceClass(reinterpret_cast(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")), vibrateDeviceId(environ->GetMethodID(instanceClass, "vibrateDevice", "(I[J[I)V")), clearVibrationDeviceId(environ->GetMethodID(instanceClass, "clearVibrationDevice", "(I)V")) { env = environ; if (env->GetJavaVM(&vm) < 0) throw exception("Cannot get JavaVM from environment"); } + JvmManager::~JvmManager() { + env->DeleteGlobalRef(instanceClass); + env->DeleteGlobalRef(instance); + } + void JvmManager::AttachThread() { if (!env) vm->AttachCurrentThread(&env, nullptr); @@ -41,4 +46,20 @@ namespace skyline { void JvmManager::InitializeControllers() { env->CallVoidMethod(instance, initializeControllersId); } + + void JvmManager::VibrateDevice(jint index, const std::span &timings, const std::span &litudes) { + auto jTimings = env->NewLongArray(timings.size()); + env->SetLongArrayRegion(jTimings, 0, timings.size(), timings.data()); + auto jAmplitudes = env->NewIntArray(amplitudes.size()); + env->SetIntArrayRegion(jAmplitudes, 0, amplitudes.size(), amplitudes.data()); + + env->CallVoidMethod(instance, vibrateDeviceId, index, jTimings, jAmplitudes); + + env->DeleteLocalRef(jTimings); + env->DeleteLocalRef(jAmplitudes); + } + + void JvmManager::ClearVibrationDevice(jint index) { + env->CallVoidMethod(instance, clearVibrationDeviceId, index); + } } diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index 2bdc8ff4e..42e5819ad 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -22,6 +22,8 @@ namespace skyline { */ JvmManager(JNIEnv *env, jobject instance); + ~JvmManager(); + /** * @brief Attach the current thread to the Java VM */ @@ -92,7 +94,19 @@ namespace skyline { */ void InitializeControllers(); + /** + * @brief A call to EmulationActivity.vibrateDevice in Kotlin + */ + void VibrateDevice(jint index, const std::span &timings, const std::span &litudes); + + /** + * @brief A call to EmulationActivity.clearVibrationDevice in Kotlin + */ + void ClearVibrationDevice(jint index); + private: jmethodID initializeControllersId; + jmethodID vibrateDeviceId; + jmethodID clearVibrationDeviceId; }; } diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 43d40b4d2..3280c91c9 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -16,6 +16,7 @@ extern skyline::GroupMutex JniMtx; namespace skyline { void NCE::KernelThread(pid_t thread) { + state.jvm->AttachThread(); try { state.thread = state.process->threads.at(thread); state.ctx = reinterpret_cast(state.thread->ctxMemory->kernel.address); @@ -76,6 +77,8 @@ namespace skyline { state.os->KillThread(thread); } } + + state.jvm->DetachThread(); } NCE::NCE(DeviceState &state) : state(state) {} diff --git a/app/src/main/cpp/skyline/services/account/IAccountServiceForApplication.cpp b/app/src/main/cpp/skyline/services/account/IAccountServiceForApplication.cpp index 4fdaac664..5abd0fa73 100644 --- a/app/src/main/cpp/skyline/services/account/IAccountServiceForApplication.cpp +++ b/app/src/main/cpp/skyline/services/account/IAccountServiceForApplication.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include #include #include "IManagerForApplication.h" #include "IProfile.h" diff --git a/app/src/main/cpp/skyline/services/base_service.h b/app/src/main/cpp/skyline/services/base_service.h index 60d6c4eb5..7c3627a4c 100644 --- a/app/src/main/cpp/skyline/services/base_service.h +++ b/app/src/main/cpp/skyline/services/base_service.h @@ -52,6 +52,7 @@ namespace skyline::service { audio_IAudioDevice, hid_IHidServer, hid_IAppletResource, + hid_IActiveVibrationDeviceList, timesrv_IStaticService, timesrv_ISystemClock, timesrv_ITimeZoneService, diff --git a/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.cpp b/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.cpp new file mode 100644 index 000000000..2286fc657 --- /dev/null +++ b/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "IActiveVibrationDeviceList.h" + +using namespace skyline::input; + +namespace skyline::service::hid { + IActiveVibrationDeviceList::IActiveVibrationDeviceList(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, Service::hid_IActiveVibrationDeviceList, "hid:IActiveVibrationDeviceList", { + {0x0, SFUNC(IActiveVibrationDeviceList::ActivateVibrationDevice)} + }) {} + + void IActiveVibrationDeviceList::ActivateVibrationDevice(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + auto handle = request.Pop(); + + if (!handle.isRight) + state.input->npad.at(handle.id).vibrationRight = NpadVibrationValue{}; + } +} diff --git a/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.h b/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.h new file mode 100644 index 000000000..492ae2c02 --- /dev/null +++ b/app/src/main/cpp/skyline/services/hid/IActiveVibrationDeviceList.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include +#include + +namespace skyline::service::hid { + /** + * @brief IActiveVibrationDeviceList is used to activate vibration on certain HID devices (https://switchbrew.org/wiki/HID_services#IActiveVibrationDeviceList) + */ + class IActiveVibrationDeviceList : public BaseService { + public: + IActiveVibrationDeviceList(const DeviceState &state, ServiceManager &manager); + + /** + * @brief Activates a vibration device with the specified #VibrationDeviceHandle (https://switchbrew.org/wiki/HID_services#ActivateVibrationDevice) + */ + void ActivateVibrationDevice(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + }; +} diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp index e93fd1645..df05833c9 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp @@ -3,6 +3,7 @@ #include #include "IHidServer.h" +#include "IActiveVibrationDeviceList.h" using namespace skyline::input; @@ -20,7 +21,9 @@ namespace skyline::service::hid { {0x79, SFUNC(IHidServer::GetNpadJoyHoldType)}, {0x7A, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingleByDefault)}, {0x7B, SFUNC(IHidServer::SetNpadJoyAssignmentModeSingle)}, - {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)} + {0x7C, SFUNC(IHidServer::SetNpadJoyAssignmentModeDual)}, + {0xCB, SFUNC(IHidServer::CreateActiveVibrationDeviceList)}, + {0xCE, SFUNC(IHidServer::SendVibrationValues)} }) {} void IHidServer::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -105,4 +108,34 @@ namespace skyline::service::hid { state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual); state.input->npad.Update(); } + + void IHidServer::CreateActiveVibrationDeviceList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + manager.RegisterService(SRVREG(IActiveVibrationDeviceList), session, response); + } + + void IHidServer::SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + request.Skip(); // appletResourceUserId + + auto &handleBuf = request.inputBuf.at(0); + std::span handles(reinterpret_cast(handleBuf.address), handleBuf.size / sizeof(NpadDeviceHandle)); + auto &valueBuf = request.inputBuf.at(1); + std::span values(reinterpret_cast(valueBuf.address), valueBuf.size / sizeof(NpadVibrationValue)); + + for (int i = 0; i < handles.size(); ++i) { + auto &handle = handles[i]; + + auto &device = state.input->npad.at(handle.id); + if (device.type == handle.GetType()) { + if (i + 1 != handles.size() && handles[i + 1].id == handle.id && handles[i + 1].isRight && !handle.isRight) { + state.logger->Info("Vibration #{}&{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz - {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, i + 1, u8(handle.id), u8(handle.type), values[i].amplitudeLow, values[i].frequencyLow, values[i].amplitudeHigh, values[i].frequencyHigh, values[i + 1].amplitudeLow, values[i + 1].frequencyLow, values[i + 1].amplitudeHigh, values[i + 1].frequencyHigh); + device.Vibrate(values[i], values[i + 1]); + i++; + } else { + auto &value = values[i]; + state.logger->Info("Vibration #{} - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", i, u8(handle.id), u8(handle.type), value.amplitudeLow, value.frequencyLow, value.amplitudeHigh, value.frequencyHigh); + device.Vibrate(handle.isRight, value); + } + } + } + } } diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.h b/app/src/main/cpp/skyline/services/hid/IHidServer.h index 633c83e7e..b94a19e73 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.h +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.h @@ -79,5 +79,15 @@ namespace skyline::service::hid { * @brief Sets the Joy-Con assignment mode to Dual (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeDual) */ void SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Returns an instance of #IActiveVibrationDeviceList (https://switchbrew.org/wiki/HID_services#CreateActiveVibrationDeviceList) + */ + void CreateActiveVibrationDeviceList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Send vibration values to an NPad (https://switchbrew.org/wiki/HID_services#SendVibrationValues) + */ + void SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); }; } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp index d3c7be90d..57c09198c 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_channel.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) -#include #include #include #include diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index 1a69eb46e..aeb5f3f0e 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -6,12 +6,10 @@ package emu.skyline import android.annotation.SuppressLint +import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.ConditionVariable -import android.os.ParcelFileDescriptor +import android.os.* import android.util.Log import android.view.* import androidx.appcompat.app.AppCompatActivity @@ -42,10 +40,15 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { */ private lateinit var input : InputManager + /** + * A map of [Vibrator]s that correspond to [InputManager.controllers] + */ + private var vibrators = HashMap() + /** * A boolean flag denoting the current operation mode of the emulator (Docked = true/Handheld = false) */ - private var operationMode : Boolean = true + private var operationMode = true /** * The surface object used for displaying frames @@ -260,7 +263,10 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { shouldFinish = false setHalt(true) - emulationThread.join() + emulationThread.join(1000) + + vibrators.forEach { (_, vibrator) -> vibrator.cancel() } + vibrators.clear() romFd.close() preferenceFd.close() @@ -383,4 +389,35 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { return super.onGenericMotionEvent(event) } + + @SuppressLint("WrongConstant") + fun vibrateDevice(index : Int, timing : LongArray, amplitude : IntArray) { + val vibrator = if (vibrators[index] != null) { + vibrators[index]!! + } else { + input.controllers[index]?.rumbleDeviceDescriptor?.let { + if (it == "builtin") { + val vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + vibrators[index] = vibrator + vibrator + } else { + for (id in InputDevice.getDeviceIds()) { + val device = InputDevice.getDevice(id) + if (device.descriptor == input.controllers[index]?.rumbleDeviceDescriptor) { + vibrators[index] = device.vibrator + device.vibrator + } + } + } + } + return + } + + val effect = VibrationEffect.createWaveform(timing, amplitude, 0) + vibrator.vibrate(effect) + } + + fun clearVibrationDevice(index : Int) { + vibrators[index]?.cancel() + } } diff --git a/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt b/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt index 1ba242ee3..a5a8dd9e8 100644 --- a/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt +++ b/app/src/main/java/emu/skyline/adapter/ControllerAdapter.kt @@ -83,7 +83,8 @@ class ControllerGeneralItem(val context : ControllerActivity, val type : General else context.getString(R.string.none) } - GeneralType.RumbleDevice -> controller.rumbleDevice?.second ?: context.getString(R.string.none) + + GeneralType.RumbleDevice -> controller.rumbleDeviceName ?: context.getString(R.string.none) } } } diff --git a/app/src/main/java/emu/skyline/input/Controller.kt b/app/src/main/java/emu/skyline/input/Controller.kt index ffda7fe06..b7dac0175 100644 --- a/app/src/main/java/emu/skyline/input/Controller.kt +++ b/app/src/main/java/emu/skyline/input/Controller.kt @@ -15,7 +15,7 @@ import java.io.Serializable * @param firstController If the type only applies to the first controller */ enum class ControllerType(val stringRes : Int, val firstController : Boolean, val sticks : Array = arrayOf(), val buttons : Array = arrayOf(), val id : Int) { - None(R.string.none, false, id=0b0), + 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), @@ -38,9 +38,10 @@ enum class GeneralType(val stringRes : Int, val compatibleControllers : Array? = null) : Serializable { +open class Controller(val id : Int, var type : ControllerType, var rumbleDeviceDescriptor : String? = null, var rumbleDeviceName : String? = null) : Serializable { /** * The current version of this class so that different versions won't be deserialized mistakenly */ diff --git a/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt b/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt index 020d52363..c3493111e 100644 --- a/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt +++ b/app/src/main/java/emu/skyline/input/dialog/RumbleDialog.kt @@ -6,8 +6,10 @@ package emu.skyline.input.dialog import android.animation.LayoutTransition +import android.content.Context import android.os.Bundle import android.os.VibrationEffect +import android.os.Vibrator import android.view.* import android.view.animation.LinearInterpolator import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -50,12 +52,26 @@ class RumbleDialog(val item : ControllerGeneralItem) : BottomSheetDialogFragment // Set up the reset button to clear out [Controller.rumbleDevice] when pressed rumble_reset.setOnClickListener { - controller.rumbleDevice = null + controller.rumbleDeviceDescriptor = null + controller.rumbleDeviceName = null item.update() dismiss() } + if (context.id == 0) { + rumble_builtin.visibility = View.VISIBLE + if (!(context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator).hasVibrator()) + rumble_builtin.isEnabled = false + rumble_builtin.setOnClickListener { + controller.rumbleDeviceDescriptor = "builtin" + controller.rumbleDeviceName = getString(R.string.builtin_vibrator) + item.update() + + dismiss() + } + } + // Ensure that layout animations are proper rumble_layout.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) rumble_controller.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) @@ -80,7 +96,8 @@ class RumbleDialog(val item : ControllerGeneralItem) : BottomSheetDialogFragment vibrator.vibrate(VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE)) } else { rumble_controller_supported.text = getString(R.string.not_supported) - rumble_title.text = getString(R.string.press_any_button) + dialog?.setOnKeyListener { _, _, _ -> false } + rumble_reset.requestFocus() } rumble_controller_icon.animate().apply { @@ -97,16 +114,16 @@ class RumbleDialog(val item : ControllerGeneralItem) : BottomSheetDialogFragment vibrator.hasVibrator() -> { vibrator.vibrate(VibrationEffect.createOneShot(250, VibrationEffect.DEFAULT_AMPLITUDE)) - controller.rumbleDevice = Pair(event.device.descriptor, event.device.name) + controller.rumbleDeviceDescriptor = event.device.descriptor + controller.rumbleDeviceName = event.device.name item.update() dismiss() } - // If the currently selected device doesn't have a vibrator then dismiss the dialog entirely else -> { - dismiss() + return@setOnKeyListener false } } } diff --git a/app/src/main/res/layout/rumble_dialog.xml b/app/src/main/res/layout/rumble_dialog.xml index 80f13cf17..9ad58a4cd 100644 --- a/app/src/main/res/layout/rumble_dialog.xml +++ b/app/src/main/res/layout/rumble_dialog.xml @@ -63,13 +63,29 @@ -