Implement Guest Touch-Screen Support

This commit is contained in:
◱ PixelyIon 2020-09-07 22:09:05 +05:30 committed by ◱ PixelyIon
parent 89718804d0
commit 65019375ca
9 changed files with 145 additions and 8 deletions

View File

@ -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

View File

@ -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<Point> points(reinterpret_cast<Point *>(env->GetIntArrayElements(pointsJni, &isCopy)), env->GetArrayLength(pointsJni) / (sizeof(Point) / sizeof(jint)));
input->touch.SetState(points);
env->ReleaseIntArrayElements(pointsJni, reinterpret_cast<jint *>(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
}
}

View File

@ -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;

View File

@ -4,5 +4,5 @@
#include "input.h"
namespace skyline::input {
Input::Input(const DeviceState &state) : state(state), kHid(std::make_shared<kernel::type::KSharedMemory>(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))), hid(reinterpret_cast<HidSharedMemory *>(kHid->kernel.address)), npad(state, hid) {}
Input::Input(const DeviceState &state) : state(state), kHid(std::make_shared<kernel::type::KSharedMemory>(state, NULL, sizeof(HidSharedMemory), memory::Permission(true, false, false))), hid(reinterpret_cast<HidSharedMemory *>(kHid->kernel.address)), npad(state, hid), touch(state, hid) {}
}

View File

@ -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<kernel::type::KSharedMemory> 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);
};

View File

@ -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);

View File

@ -0,0 +1,43 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <input.h>
#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<TouchScreenPoint> &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<u8>(section.header.entryCount + 1), constant::HidEntryCount);
section.header.currentEntry = entryIndex;
}
}

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#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 &section;
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<TouchScreenPoint> &points);
};
}

View File

@ -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) {