Move to Callback for Input Initialization + ConditionalVariable for Surface

This commit is contained in:
◱ PixelyIon 2020-08-21 16:44:27 +05:30 committed by ◱ PixelyIon
parent 07c2f2d891
commit 7290a80c3e
10 changed files with 65 additions and 61 deletions

View File

@ -53,6 +53,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(
try { try {
skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath)); skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath));
inputWeak = os.state.input; inputWeak = os.state.input;
jvmManager->InitializeControllers();
env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath); env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath);
auto romUri = env->GetStringUTFChars(romUriJstring, nullptr); auto romUri = env->GetStringUTFChars(romUriJstring, nullptr);
@ -100,27 +101,22 @@ extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIE
} }
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setController(JNIEnv *, jobject, jint index, jint type, jint partnerIndex) { extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setController(JNIEnv *, jobject, jint index, jint type, jint partnerIndex) {
while (inputWeak.expired()); // If this isn't called then the guest won't know that the following host controller exists
auto input = inputWeak.lock(); auto input = inputWeak.lock();
std::lock_guard guard(input->npad.mutex); std::lock_guard guard(input->npad.mutex);
input->npad.controllers[index] = skyline::input::GuestController{static_cast<skyline::input::NpadControllerType>(type), static_cast<skyline::i8>(partnerIndex)}; 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) { extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_updateControllers(JNIEnv *, jobject) {
while (inputWeak.expired()); // If this isn't called then the mappings will not update unless the guest initiates an update itself inputWeak.lock()->npad.Update();
auto input = inputWeak.lock();
std::unique_lock lock(input->npad.mutex);
input->npad.Update(lock, true);
} }
extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jint state) { extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jboolean pressed) {
try { try {
auto input = inputWeak.lock(); auto input = inputWeak.lock();
auto device = input->npad.controllers[index].device; auto device = input->npad.controllers[index].device;
skyline::input::NpadButton button{.raw = static_cast<skyline::u64>(mask)};
if (device) if (device)
device->SetButtonState(button, state); device->SetButtonState(skyline::input::NpadButton{.raw = static_cast<skyline::u64>(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 // We don't mind if we miss button updates while input hasn't been initialized
} }
} }
@ -131,7 +127,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValu
auto device = input->npad.controllers[index].device; auto device = input->npad.controllers[index].device;
if (device) if (device)
device->SetAxisValue(static_cast<skyline::input::NpadAxisId>(axis), value); device->SetAxisValue(static_cast<skyline::input::NpadAxisId>(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 // We don't mind if we miss axis updates while input hasn't been initialized
} }
} }

View File

@ -13,15 +13,8 @@ namespace skyline::input {
{*this, hid->npad[8], NpadId::Unknown}, {*this, hid->npad[9], NpadId::Handheld}, {*this, hid->npad[8], NpadId::Unknown}, {*this, hid->npad[9], NpadId::Handheld},
} {} } {}
void NpadManager::Update(std::unique_lock<std::mutex> &lock, bool host) { void NpadManager::Update() {
if (host) { std::lock_guard guard(mutex);
updated = true;
} else if (!updated) {
lock.unlock();
while (!updated);
lock.lock();
return;
}
if (!activated) if (!activated)
return; return;
@ -85,17 +78,17 @@ namespace skyline::input {
} }
void NpadManager::Activate() { void NpadManager::Activate() {
std::unique_lock lock(mutex); std::lock_guard guard(mutex);
supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8}; 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}; styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true};
activated = true; activated = true;
Update(lock); Update();
} }
void NpadManager::Deactivate() { void NpadManager::Deactivate() {
std::unique_lock lock(mutex); std::lock_guard guard(mutex);
supportedIds = {}; supportedIds = {};
styles = {}; styles = {};

View File

@ -22,7 +22,6 @@ namespace skyline::input {
private: private:
const DeviceState &state; const DeviceState &state;
bool activated{false}; //!< If this NpadManager is activated or not bool activated{false}; //!< If this NpadManager is activated or not
std::atomic<bool> updated{false}; //!< If this NpadManager has been updated by the guest
friend NpadDevice; friend NpadDevice;
@ -43,7 +42,7 @@ namespace skyline::input {
} }
public: public:
std::mutex mutex; //!< This mutex must be locked before any modifications to class members std::recursive_mutex mutex; //!< This mutex must be locked before any modifications to class members
std::array<NpadDevice, constant::NpadCount> npads; std::array<NpadDevice, constant::NpadCount> npads;
std::array<GuestController, constant::ControllerCount> controllers; std::array<GuestController, constant::ControllerCount> controllers;
std::vector<NpadId> supportedIds; //!< The NpadId(s) that are supported by the application std::vector<NpadId> supportedIds; //!< The NpadId(s) that are supported by the application
@ -71,10 +70,9 @@ namespace skyline::input {
/** /**
* @brief This deduces all the mappings from guest controllers -> players based on the configuration supplied by HID services and available controllers * @brief This deduces all the mappings from guest controllers -> players based on the configuration supplied by HID services and available controllers
* @param lock A unique_lock which locks the mutex in the class, it should be locked before modifications to any members and must not be passed in an unlocked state * @note If any class members were edited, the mutex shouldn't be released till this is called
* @param host If the update is host-initiated rather than the guest
*/ */
void Update(std::unique_lock<std::mutex> &lock, bool host = false); void Update();
/** /**
* @brief This activates the mapping between guest controllers -> players, a call to this is required for function * @brief This activates the mapping between guest controllers -> players, a call to this is required for function

View File

@ -6,7 +6,7 @@
thread_local JNIEnv *env; thread_local JNIEnv *env;
namespace skyline { namespace skyline {
JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))) { JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast<jclass>(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")) {
env = environ; env = environ;
if (env->GetJavaVM(&vm) < 0) if (env->GetJavaVM(&vm) < 0)
throw exception("Cannot get JavaVM from environment"); throw exception("Cannot get JavaVM from environment");
@ -37,4 +37,8 @@ namespace skyline {
bool JvmManager::CheckNull(jobject &object) { bool JvmManager::CheckNull(jobject &object) {
return env->IsSameObject(object, nullptr); return env->IsSameObject(object, nullptr);
} }
void JvmManager::InitializeControllers() {
env->CallVoidMethod(instance, initializeControllersId);
}
} }

View File

@ -35,7 +35,7 @@ namespace skyline {
/** /**
* @brief Returns a pointer to the JNI environment for the current thread * @brief Returns a pointer to the JNI environment for the current thread
*/ */
JNIEnv *GetEnv(); static JNIEnv *GetEnv();
/** /**
* @brief Retrieves a specific field of the given type from the activity * @brief Retrieves a specific field of the given type from the activity
@ -85,6 +85,14 @@ namespace skyline {
* @param object The jobject to check * @param object The jobject to check
* @return If the object is null or not * @return If the object is null or not
*/ */
bool CheckNull(jobject &object); static bool CheckNull(jobject &object);
/**
* @brief A call to EmulationActivity.initializeControllers in Kotlin
*/
void InitializeControllers();
private:
jmethodID initializeControllersId;
}; };
} }

View File

@ -29,9 +29,9 @@ namespace skyline::service::hid {
void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto styleSet = request.Pop<NpadStyleSet>(); auto styleSet = request.Pop<NpadStyleSet>();
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
state.input->npad.styles = styleSet; state.input->npad.styles = styleSet;
state.input->npad.Update(lock); state.input->npad.Update();
state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast<bool>(styleSet.proController), static_cast<bool>(styleSet.joyconHandheld), static_cast<bool>(styleSet.joyconDual), static_cast<bool>(styleSet.joyconLeft), static_cast<bool> state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast<bool>(styleSet.proController), static_cast<bool>(styleSet.joyconHandheld), static_cast<bool>(styleSet.joyconDual), static_cast<bool>(styleSet.joyconLeft), static_cast<bool>
(styleSet.joyconRight), static_cast<bool>(styleSet.gamecube), static_cast<bool>(styleSet.palma), static_cast<bool>(styleSet.nes), static_cast<bool>(styleSet.nesHandheld), static_cast<bool>(styleSet.snes)); (styleSet.joyconRight), static_cast<bool>(styleSet.gamecube), static_cast<bool>(styleSet.palma), static_cast<bool>(styleSet.nes), static_cast<bool>(styleSet.nesHandheld), static_cast<bool>(styleSet.snes));
@ -45,16 +45,16 @@ namespace skyline::service::hid {
const auto &buffer = request.inputBuf.at(0); const auto &buffer = request.inputBuf.at(0);
u64 address = buffer.address; u64 address = buffer.address;
size_t size = buffer.size / sizeof(NpadId); size_t size = buffer.size / sizeof(NpadId);
std::vector<NpadId> supportedIds;
std::vector<NpadId> supportedIds(size);
for (size_t i = 0; i < size; i++) { for (size_t i = 0; i < size; i++) {
supportedIds.push_back(state.process->GetObject<NpadId>(address)); supportedIds[i] = state.process->GetObject<NpadId>(address);
address += sizeof(NpadId); address += sizeof(NpadId);
} }
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
state.input->npad.supportedIds = supportedIds; state.input->npad.supportedIds = supportedIds;
state.input->npad.Update(lock); state.input->npad.Update();
} }
void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -75,10 +75,10 @@ namespace skyline::service::hid {
} }
void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
request.Skip<u64>(); request.Skip<u64>();
state.input->npad.orientation = request.Pop<NpadJoyOrientation>(); state.input->npad.orientation = request.Pop<NpadJoyOrientation>();
state.input->npad.Update(lock); state.input->npad.Update();
} }
void IHidServer::GetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::GetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
@ -87,22 +87,22 @@ namespace skyline::service::hid {
void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<NpadId>(); auto id = request.Pop<NpadId>();
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
state.input->npad.Update(lock); state.input->npad.Update();
} }
void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<NpadId>(); auto id = request.Pop<NpadId>();
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single);
state.input->npad.Update(lock); state.input->npad.Update();
} }
void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto id = request.Pop<NpadId>(); auto id = request.Pop<NpadId>();
std::unique_lock lock(state.input->npad.mutex); std::lock_guard lock(state.input->npad.mutex);
state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual);
state.input->npad.Update(lock); state.input->npad.Update();
} }
} }

View File

@ -9,7 +9,7 @@ namespace skyline::service::settings {
{0x3, SFUNC(ISystemSettingsServer::GetFirmwareVersion)}}) {} {0x3, SFUNC(ISystemSettingsServer::GetFirmwareVersion)}}) {}
void ISystemSettingsServer::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { void ISystemSettingsServer::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
SysVerTitle title{.major=5, .minor=0, .micro=0, .revMajor=4, .revMinor=0, .platform="NX", .verHash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .dispVer="9.0.0", .dispTitle="NintendoSDK Firmware for NX 9.0.0-4.0"}; SysVerTitle title{.major=9, .minor=0, .micro=0, .revMajor=4, .revMinor=0, .platform="NX", .verHash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .dispVer="9.0.0", .dispTitle="NintendoSDK Firmware for NX 9.0.0-4.0"};
state.process->WriteMemory(title, request.outputBuf.at(0).address); state.process->WriteMemory(title, request.outputBuf.at(0).address);
} }
} }

View File

@ -10,6 +10,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.ConditionVariable
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
import android.view.* import android.view.*
@ -52,6 +53,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
@Volatile @Volatile
private var surface : Surface? = null private var surface : Surface? = null
/**
* A condition variable keeping track of if the surface is ready or not
*/
private var surfaceReady = ConditionVariable()
/** /**
* A boolean flag denoting if the emulation thread should call finish() or not * A boolean flag denoting if the emulation thread should call finish() or not
*/ */
@ -120,9 +126,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
* *
* @param index The index of the controller this is directed to * @param index The index of the controller this is directed to
* @param mask The mask of the button that are being set * @param mask The mask of the button that are being set
* @param state The state to set the button to * @param pressed If the buttons are being pressed or released
*/ */
private external fun setButtonState(index : Int, mask : Long, state : Int) private external fun setButtonState(index : Int, mask : Long, pressed : Boolean)
/** /**
* This sets the value of a specific axis on a specific controller * This sets the value of a specific axis on a specific controller
@ -141,13 +147,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
val controller = entry.value val controller = entry.value
if (controller.type != ControllerType.None) { if (controller.type != ControllerType.None) {
val type : Int = when (controller.type) { val type = when (controller.type) {
ControllerType.None -> throw IllegalArgumentException() ControllerType.None -> throw IllegalArgumentException()
ControllerType.HandheldProController -> if (operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id ControllerType.HandheldProController -> if (operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id
ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id
} }
val partnerIndex : Int? = when (controller) { val partnerIndex = when (controller) {
is JoyConLeftController -> controller.partnerId is JoyConLeftController -> controller.partnerId
is JoyConRightController -> controller.partnerId is JoyConRightController -> controller.partnerId
else -> null else -> null
@ -170,10 +176,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
romFd = contentResolver.openFileDescriptor(rom, "r")!! romFd = contentResolver.openFileDescriptor(rom, "r")!!
emulationThread = Thread { emulationThread = Thread {
while (surface == null) surfaceReady.block()
Thread.yield()
runOnUiThread { initializeControllers() }
executeApplication(rom.toString(), romType, romFd.fd, preferenceFd.fd, applicationContext.filesDir.canonicalPath + "/") executeApplication(rom.toString(), romType, romFd.fd, preferenceFd.fd, applicationContext.filesDir.canonicalPath + "/")
@ -216,14 +219,12 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (sharedPreferences.getBoolean("perf_stats", false)) { if (sharedPreferences.getBoolean("perf_stats", false)) {
val perfRunnable = object : Runnable { perf_stats.postDelayed(object : Runnable {
override fun run() { override fun run() {
perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms" perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms"
perf_stats.postDelayed(this, 250) perf_stats.postDelayed(this, 250)
} }
} }, 250)
perf_stats.postDelayed(perfRunnable, 250)
} }
operationMode = sharedPreferences.getBoolean("operation_mode", operationMode) operationMode = sharedPreferences.getBoolean("operation_mode", operationMode)
@ -274,6 +275,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
Log.d("surfaceCreated", "Holder: $holder") Log.d("surfaceCreated", "Holder: $holder")
surface = holder.surface surface = holder.surface
setSurface(surface) setSurface(surface)
surfaceReady.open()
} }
/** /**
@ -288,6 +290,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
*/ */
override fun surfaceDestroyed(holder : SurfaceHolder) { override fun surfaceDestroyed(holder : SurfaceHolder) {
Log.d("surfaceDestroyed", "Holder: $holder") Log.d("surfaceDestroyed", "Holder: $holder")
surfaceReady.close()
surface = null surface = null
setSurface(surface) setSurface(surface)
} }
@ -299,7 +302,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
if (event.repeatCount != 0) if (event.repeatCount != 0)
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
val action : ButtonState = when (event.action) { val action = when (event.action) {
KeyEvent.ACTION_DOWN -> ButtonState.Pressed KeyEvent.ACTION_DOWN -> ButtonState.Pressed
KeyEvent.ACTION_UP -> ButtonState.Released KeyEvent.ACTION_UP -> ButtonState.Released
else -> return super.dispatchKeyEvent(event) else -> return super.dispatchKeyEvent(event)
@ -308,7 +311,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
return when (val guestEvent = input.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) { return when (val guestEvent = input.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) {
is ButtonGuestEvent -> { is ButtonGuestEvent -> {
if (guestEvent.button != ButtonId.Menu) if (guestEvent.button != ButtonId.Menu)
setButtonState(guestEvent.id, guestEvent.button.value(), action.ordinal) setButtonState(guestEvent.id, guestEvent.button.value(), action.state)
true true
} }
@ -356,7 +359,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback {
when (guestEvent) { when (guestEvent) {
is ButtonGuestEvent -> { is ButtonGuestEvent -> {
if (guestEvent.button != ButtonId.Menu) if (guestEvent.button != ButtonId.Menu)
setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.ordinal else ButtonState.Released.ordinal) setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.state else ButtonState.Released.state)
} }
is AxisGuestEvent -> { is AxisGuestEvent -> {

View File

@ -179,7 +179,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
setupAppList() setupAppList()
app_list.addOnScrollListener(object : RecyclerView.OnScrollListener() { app_list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var y : Int = 0 var y = 0
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) { override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
y += dy y += dy

View File

@ -65,12 +65,14 @@
android:key="category_input" android:key="category_input"
android:title="@string/input" android:title="@string/input"
app:initialExpandedChildrenCount="4"> app:initialExpandedChildrenCount="4">
<!--
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:summaryOff="@string/osc_not_shown" android:summaryOff="@string/osc_not_shown"
android:summaryOn="@string/osc_shown" android:summaryOn="@string/osc_shown"
app:key="show_osc" app:key="show_osc"
app:title="@string/show_osc" /> app:title="@string/show_osc" />
-->
<emu.skyline.preference.ControllerPreference index="0" /> <emu.skyline.preference.ControllerPreference index="0" />
<emu.skyline.preference.ControllerPreference index="1" /> <emu.skyline.preference.ControllerPreference index="1" />
<emu.skyline.preference.ControllerPreference index="2" /> <emu.skyline.preference.ControllerPreference index="2" />