diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 09b5d096..94562f77 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -48,6 +48,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/input.cpp ${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/input/npad_device.cpp + ${source_DIR}/skyline/input/touch.cpp ${source_DIR}/skyline/os.cpp ${source_DIR}/skyline/loader/loader.cpp ${source_DIR}/skyline/loader/nro.cpp diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 0c96d09c..84451b69 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -131,3 +131,19 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValu // We don't mind if we miss axis updates while input hasn't been initialized } } + +extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setTouchState(JNIEnv *env, jobject, jintArray pointsJni) { + try { + using Point = skyline::input::TouchScreenPoint; + + auto input = inputWeak.lock(); + jboolean isCopy{false}; + + std::span points(reinterpret_cast(env->GetIntArrayElements(pointsJni, &isCopy)), env->GetArrayLength(pointsJni) / (sizeof(Point) / sizeof(jint))); + input->touch.SetState(points); + + env->ReleaseIntArrayElements(pointsJni, reinterpret_cast(points.data()), JNI_ABORT); + } 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 13b75b96..d35db0ea 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -31,7 +31,7 @@ namespace skyline { */ union Result { u32 raw{}; - struct { + struct __attribute__((packed)) { u16 module : 9; u16 id : 12; }; @@ -39,7 +39,7 @@ namespace skyline { /** * @note Success is 0, 0 - it is the only error that's not specific to a module */ - Result() {} + Result() = default; constexpr Result(u16 module, u16 id) { this->module = module; diff --git a/app/src/main/cpp/skyline/input.cpp b/app/src/main/cpp/skyline/input.cpp index a2431681..48184edd 100644 --- a/app/src/main/cpp/skyline/input.cpp +++ b/app/src/main/cpp/skyline/input.cpp @@ -4,5 +4,5 @@ #include "input.h" namespace skyline::input { - Input::Input(const DeviceState &state) : state(state), kHid(std::make_shared(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))), hid(reinterpret_cast(kHid->kernel.address)), npad(state, hid) {} + Input::Input(const DeviceState &state) : state(state), kHid(std::make_shared(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))), hid(reinterpret_cast(kHid->kernel.address)), npad(state, hid), touch(state, hid) {} } diff --git a/app/src/main/cpp/skyline/input.h b/app/src/main/cpp/skyline/input.h index 085bc30d..fbd2b75b 100644 --- a/app/src/main/cpp/skyline/input.h +++ b/app/src/main/cpp/skyline/input.h @@ -7,6 +7,7 @@ #include "kernel/types/KSharedMemory.h" #include "input/shared_mem.h" #include "input/npad.h" +#include "input/touch.h" namespace skyline::input { /** @@ -20,7 +21,8 @@ namespace skyline::input { std::shared_ptr kHid; //!< The kernel shared memory object for HID Shared Memory HidSharedMemory *hid; //!< A pointer to HID Shared Memory on the host - NpadManager npad; //!< This manages all the NPad controllers + NpadManager npad; + TouchManager touch; Input(const DeviceState &state); }; diff --git a/app/src/main/cpp/skyline/input/sections/TouchScreen.h b/app/src/main/cpp/skyline/input/sections/TouchScreen.h index 3e74500b..015aa0b9 100644 --- a/app/src/main/cpp/skyline/input/sections/TouchScreen.h +++ b/app/src/main/cpp/skyline/input/sections/TouchScreen.h @@ -18,10 +18,10 @@ namespace skyline::input { u32 positionX; //!< The X position of this touch u32 positionY; //!< The Y position of this touch - u32 diameterX; //!< The diameter of the touch on the X-axis - u32 diameterY; //!< The diameter of the touch on the Y-axis + u32 minorAxis; //!< The diameter of the touch cross-section on the minor-axis in pixels + u32 majorAxis; //!< The diameter of the touch cross-section on the major-axis in pixels - u32 angle; //!< The angle of the touch + i32 angle; //!< The angle of the touch in degrees (from -89 to 90 [-90 and 90 aren't distinguishable]. On the Switch, this has limited resolution with only 90, -67, -45, 0, 45, 67, 90 being values) u32 _pad1_; }; static_assert(sizeof(TouchScreenStateData) == 0x28); diff --git a/app/src/main/cpp/skyline/input/touch.cpp b/app/src/main/cpp/skyline/input/touch.cpp new file mode 100644 index 00000000..16e89424 --- /dev/null +++ b/app/src/main/cpp/skyline/input/touch.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include "touch.h" + +namespace skyline::input { + TouchManager::TouchManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), section(hid->touchScreen) { + Activate(); + } + + void TouchManager::Activate() { + activated = true; + SetState({}); + } + + void TouchManager::SetState(const std::span &points) { + if (!activated) + return; + + const auto& lastEntry = section.entries[section.header.currentEntry]; + auto entryIndex = (section.header.currentEntry != constant::HidEntryCount - 1) ? section.header.currentEntry + 1 : 0; + auto& entry = section.entries[entryIndex]; + entry.globalTimestamp = lastEntry.globalTimestamp + 1; + entry.localTimestamp = lastEntry.localTimestamp + 1; + entry.touchCount = points.size(); + + for (size_t i{}; i < points.size(); i++) { + const auto& host = points[i]; + auto& guest = entry.data[i]; + guest.index = i; + guest.positionX = host.x; + guest.positionY = host.y; + guest.minorAxis = host.minor; + guest.majorAxis = host.major; + guest.angle = host.angle; + } + + section.header.timestamp = util::GetTimeTicks(); + section.header.entryCount = std::min(static_cast(section.header.entryCount + 1), constant::HidEntryCount); + section.header.currentEntry = entryIndex; + } +} diff --git a/app/src/main/cpp/skyline/input/touch.h b/app/src/main/cpp/skyline/input/touch.h new file mode 100644 index 00000000..b9964373 --- /dev/null +++ b/app/src/main/cpp/skyline/input/touch.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#pragma once + +#include +#include "shared_mem.h" + +namespace skyline::input { + /* + * @brief A description of a point being touched on the screen + * @note All members are jint as it is treated as a jint array in Kotlin + * @note This structure corresponds to TouchScreenStateData, see that for details + */ + struct TouchScreenPoint { + jint x; + jint y; + jint minor; + jint major; + jint angle; + }; + + /** + * @brief This class is used to manage the shared memory responsible for touch-screen data + */ + class TouchManager { + private: + const DeviceState &state; + bool activated{}; + TouchScreenSection §ion; + + public: + /** + * @param hid A pointer to HID Shared Memory on the host + */ + TouchManager(const DeviceState &state, input::HidSharedMemory *hid); + + void Activate(); + + void SetState(const std::span &points); + }; +} diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index aeb5f3f0..f3903ace 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -20,7 +20,7 @@ import kotlinx.android.synthetic.main.emu_activity.* import java.io.File import kotlin.math.abs -class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { +class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTouchListener { init { System.loadLibrary("skyline") // libskyline.so } @@ -142,6 +142,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { */ private external fun setAxisValue(index : Int, axis : Int, value : Int) + /** + * This sets the values of the points on the guest touch-screen + * + * @param points An array of skyline::input::TouchScreenPoint in C++ represented as integers + */ + private external fun setTouchState(points : IntArray) + /** * This initializes all of the controllers from [input] on the guest */ @@ -235,6 +242,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { @Suppress("DEPRECATION") val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display!! else windowManager.defaultDisplay display?.supportedModes?.maxBy { it.refreshRate + (it.physicalHeight * it.physicalWidth) }?.let { window.attributes.preferredDisplayModeId = it.modeId } + game_view.setOnTouchListener(this) + executeApplication(intent.data!!) } @@ -390,6 +399,30 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { return super.onGenericMotionEvent(event) } + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view : View, event : MotionEvent) : Boolean { + val count = if(event.action != MotionEvent.ACTION_UP && event.action != MotionEvent.ACTION_CANCEL) event.pointerCount else 0 + val points = IntArray(count * 5) // This is an array of skyline::input::TouchScreenPoint in C++ as that allows for efficient transfer of values to it + var offset = 0 + for (index in 0 until count) { + val pointer = MotionEvent.PointerCoords() + event.getPointerCoords(index, pointer) + + val x = 0f.coerceAtLeast(pointer.x * 1280 / view.width).toInt() + val y = 0f.coerceAtLeast(pointer.y * 720 / view.height).toInt() + + points[offset++] = x + points[offset++] = y + points[offset++] = pointer.touchMinor.toInt() + points[offset++] = pointer.touchMajor.toInt() + points[offset++] = (pointer.orientation * 180 / Math.PI).toInt() + } + + setTouchState(points) + + return true + } + @SuppressLint("WrongConstant") fun vibrateDevice(index : Int, timing : LongArray, amplitude : IntArray) { val vibrator = if (vibrators[index] != null) {