1
0
forked from MeloNX/MeloNX

android - update ui handler to call back into java

This commit is contained in:
Emmanuel Hansen 2024-09-09 19:09:39 +00:00
parent b7deaefff7
commit e2f584a7ff
15 changed files with 316 additions and 531 deletions

View File

@ -2,6 +2,7 @@ using LibHac.Tools.Fs;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.HOS.Applets; using Ryujinx.HLE.HOS.Applets;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types; using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using System; using System;
@ -15,20 +16,13 @@ namespace LibRyujinx.Android
{ {
internal class AndroidUIHandler : IHostUIHandler, IDisposable internal class AndroidUIHandler : IHostUIHandler, IDisposable
{ {
public ManualResetEvent _waitEvent;
public ManualResetEvent _responseEvent;
private bool _isDisposed; private bool _isDisposed;
private bool _isOkPressed; private bool _isOkPressed;
private long _input; private string? _input;
private ManualResetEvent _resetEvent = new ManualResetEvent(false);
public IHostUITheme HostUITheme => throw new NotImplementedException(); public IHostUITheme HostUITheme => throw new NotImplementedException();
public AndroidUIHandler()
{
_waitEvent = new ManualResetEvent(false);
_responseEvent = new ManualResetEvent(false);
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler() public IDynamicTextInputHandler CreateDynamicTextInputHandler()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@ -36,47 +30,51 @@ namespace LibRyujinx.Android
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText) public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
{ {
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? "")); Interop.UpdateUiHandler(title ?? "",
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? "")); message ?? "",
LibRyujinx.setUiHandlerType(1); "",
1,
_responseEvent.Reset(); 0,
Set(); 0,
_responseEvent.WaitOne(); KeyboardMode.Default,
"",
"");
return _isOkPressed; return _isOkPressed;
} }
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText) public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
{ {
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString("Software Keyboard")); _input = null;
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(args.HeaderText ?? "")); _resetEvent.Reset();
LibRyujinx.setUiHandlerWatermark(LibRyujinx.storeString(args.GuideText ?? "")); Interop.UpdateUiHandler("Software Keyboard",
LibRyujinx.setUiHandlerSubtitle(LibRyujinx.storeString(args.SubtitleText ?? "")); args.HeaderText ?? "",
LibRyujinx.setUiHandlerInitialText(LibRyujinx.storeString(args.InitialText ?? "")); args.GuideText ?? "",
LibRyujinx.setUiHandlerMinLength(args.StringLengthMin); 2,
LibRyujinx.setUiHandlerMaxLength(args.StringLengthMax); args.StringLengthMin,
LibRyujinx.setUiHandlerType(2); args.StringLengthMax,
LibRyujinx.setUiHandlerKeyboardMode((int)args.KeyboardMode); args.KeyboardMode,
args.SubtitleText ?? "",
args.InitialText ?? "");
_responseEvent.Reset(); _resetEvent.WaitOne();
Set();
_responseEvent.WaitOne();
userText = _input != -1 ? LibRyujinx.GetStoredString(_input) : ""; userText = _input ?? "";
return _isOkPressed; return _isOkPressed;
} }
public bool DisplayMessageDialog(string title, string message) public bool DisplayMessageDialog(string title, string message)
{ {
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? "")); Interop.UpdateUiHandler(title ?? "",
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? "")); message ?? "",
LibRyujinx.setUiHandlerType(1); "",
1,
_responseEvent.Reset(); 0,
Set(); 0,
_responseEvent.WaitOne(); KeyboardMode.Default,
"",
"");
return _isOkPressed; return _isOkPressed;
} }
@ -99,38 +97,18 @@ namespace LibRyujinx.Android
// throw new NotImplementedException(); // throw new NotImplementedException();
} }
internal void Wait() internal void SetResponse(bool isOkPressed, string input)
{
if (_isDisposed)
return;
_waitEvent.Reset();
_waitEvent.WaitOne();
_waitEvent.Reset();
}
internal void Set()
{
if (_isDisposed)
return;
_waitEvent.Set();
}
internal void SetResponse(bool isOkPressed, long input)
{ {
if (_isDisposed) if (_isDisposed)
return; return;
_isOkPressed = isOkPressed; _isOkPressed = isOkPressed;
_input = input; _input = input;
_responseEvent.Set(); _resetEvent.Set();
} }
public void Dispose() public void Dispose()
{ {
_isDisposed = true; _isDisposed = true;
_waitEvent.Set();
_waitEvent.Set();
_responseEvent.Dispose();
_waitEvent.Dispose();
} }
} }
} }

View File

@ -0,0 +1,190 @@
using LibRyujinx.Jni;
using LibRyujinx.Jni.Identifiers;
using LibRyujinx.Jni.Pointers;
using LibRyujinx.Jni.Primitives;
using LibRyujinx.Jni.References;
using LibRyujinx.Jni.Values;
using Rxmxnx.PInvoke;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace LibRyujinx.Android
{
internal unsafe static class Interop
{
internal const string BaseClassName = "org/ryujinx/android/RyujinxNative";
private static JGlobalRef? _classId;
private static ConcurrentDictionary<(string method, string descriptor), JMethodId> _methodCache = new ConcurrentDictionary<(string method, string descriptor), JMethodId>();
private static (string name, string descriptor)[] _methods = new[]
{
("test", "()V"),
("updateUiHandler", "(JJJIIIIJJ)V")
};
internal static void Initialize(JEnvRef jniEnv)
{
var vm = JniHelper.GetVirtualMachine(jniEnv);
if (_classId == null)
{
var className = new ReadOnlySpan<Byte>(Encoding.UTF8.GetBytes(BaseClassName));
using (IReadOnlyFixedMemory<Byte>.IDisposable cName = className.GetUnsafeValPtr()
.GetUnsafeFixedContext(className.Length))
{
_classId = JniHelper.GetGlobalClass(jniEnv, cName);
if (_classId == null)
{
Logger.Info?.Print(LogClass.Application, $"Class Id {BaseClassName} not found");
return;
}
}
}
foreach (var x in _methods)
{
CacheMethod(jniEnv, x.name, x.descriptor);
}
JniEnv._jvm = vm;
}
private static void CacheMethod(JEnvRef jEnv, string name, string descriptor)
{
if (!_methodCache.TryGetValue((name, descriptor), out var method))
{
var methodName = new ReadOnlySpan<Byte>(Encoding.UTF8.GetBytes(name));
var descriptorId = new ReadOnlySpan<Byte>(Encoding.UTF8.GetBytes(descriptor));
using (IReadOnlyFixedMemory<Byte>.IDisposable mName = methodName.GetUnsafeValPtr()
.GetUnsafeFixedContext(methodName.Length))
using (IReadOnlyFixedMemory<Byte>.IDisposable dName = descriptorId.GetUnsafeValPtr()
.GetUnsafeFixedContext(descriptorId.Length))
{
var methodId = JniHelper.GetStaticMethodId(jEnv, (JClassLocalRef)(_classId.Value.Value), mName, dName);
if (methodId == null)
{
Logger.Warning?.Print(LogClass.Application, $"Java Method Id {name} not found");
return;
}
method = methodId.Value;
_methodCache[(name, descriptor)] = method;
}
}
}
private static void CallMethod(string name, string descriptor, params JValue[] values)
{
using var env = JniEnv.Create();
if (_methodCache.TryGetValue((name, descriptor), out var method))
{
if (descriptor.EndsWith("V"))
{
JniHelper.CallStaticVoidMethod(env.Env, (JClassLocalRef)(_classId.Value.Value), method, values);
}
}
}
public static void Test()
{
CallMethod("test", "()V");
}
public static void UpdateUiHandler(string newTitle,
string newMessage,
string newWatermark,
int newType,
int min,
int max,
KeyboardMode nMode,
string newSubtitle,
string newInitialText)
{
using var titlePointer = new TempNativeString(newTitle);
using var messagePointer = new TempNativeString(newMessage);
using var watermarkPointer = new TempNativeString(newWatermark);
using var subtitlePointer = new TempNativeString(newSubtitle);
using var newInitialPointer = new TempNativeString(newInitialText);
CallMethod("updateUiHandler", "(JJJIIIIJJ)V", new JValue[]
{
JValue.Create(titlePointer.AsBytes()),
JValue.Create(messagePointer.AsBytes()),
JValue.Create(watermarkPointer.AsBytes()),
JValue.Create(newType.AsBytes()),
JValue.Create(min.AsBytes()),
JValue.Create(max.AsBytes()),
JValue.Create(nMode.AsBytes()),
JValue.Create(subtitlePointer.AsBytes()),
JValue.Create(newInitialPointer.AsBytes())
});
}
private class TempNativeString : IDisposable
{
private JLong _jPointer;
public TempNativeString(string value)
{
Pointer = Marshal.StringToHGlobalAuto(value);
JPointer = (JLong)Pointer;
}
public nint Pointer { get; private set; }
public JLong JPointer { get => _jPointer; private set => _jPointer = value; }
public Span<byte> AsBytes()
{
return _jPointer.AsBytes();
}
public void Dispose()
{
if (Pointer != IntPtr.Zero)
{
Marshal.FreeHGlobal(Pointer);
}
Pointer = IntPtr.Zero;
}
}
private class JniEnv : IDisposable
{
internal static JavaVMRef? _jvm;
private readonly JEnvRef _env;
private readonly bool _newAttach;
public JEnvRef Env => _env;
private JniEnv(JEnvRef env, bool newAttach)
{
_env = env;
_newAttach = newAttach;
}
public void Dispose()
{
if(_newAttach)
{
JniHelper.Detach(_jvm!.Value);
}
}
public static JniEnv? Create()
{
bool newAttach = false;
ReadOnlySpan<Byte> threadName = "JvmCall"u8;
var env = _jvm == null ? default : JniHelper.Attach(_jvm.Value, threadName.GetUnsafeValPtr().GetUnsafeFixedContext(threadName.Length),
out newAttach);
return env != null ? new JniEnv(env.Value, newAttach) : null;
}
}
}
}

View File

@ -1,5 +1,5 @@
using LibRyujinx.Android;
using LibRyujinx.Jni.Pointers; using LibRyujinx.Jni.Pointers;
using LibRyujinx.Jni.References;
using Ryujinx.Audio.Backends.OpenAL; using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
@ -14,9 +14,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading; using System.Threading;
namespace LibRyujinx namespace LibRyujinx
@ -29,56 +27,6 @@ namespace LibRyujinx
public static VulkanLoader? VulkanLoader { get; private set; } public static VulkanLoader? VulkanLoader { get; private set; }
[DllImport("libryujinxjni")]
private extern static IntPtr getStringPointer(JEnvRef jEnv, JStringLocalRef s);
[DllImport("libryujinxjni")]
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
[DllImport("libryujinxjni")]
internal extern static long storeString(string ch);
[DllImport("libryujinxjni")]
internal extern static IntPtr getString(long id);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerTitle(long title);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMessage(long message);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerWatermark(long watermark);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerInitialText(long text);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerSubtitle(long text);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerType(int type);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerKeyboardMode(int mode);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMinLength(int lenght);
[DllImport("libryujinxjni")]
internal extern static long setUiHandlerMaxLength(int lenght);
internal static string GetStoredString(long id)
{
var pointer = getString(id);
if (pointer != IntPtr.Zero)
{
var str = Marshal.PtrToStringAnsi(pointer) ?? "";
Marshal.FreeHGlobal(pointer);
return str;
}
return "";
}
[DllImport("libryujinxjni")] [DllImport("libryujinxjni")]
internal extern static void setRenderingThread(); internal extern static void setRenderingThread();
@ -97,7 +45,7 @@ namespace LibRyujinx
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance); public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
[UnmanagedCallersOnly(EntryPoint = "javaInitialize")] [UnmanagedCallersOnly(EntryPoint = "javaInitialize")]
public static bool JniInitialize(IntPtr jpathId) public unsafe static bool JniInitialize(IntPtr jpathId, IntPtr jniEnv)
{ {
Logger.Trace?.Print(LogClass.Application, "Jni Function Call"); Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
PlatformInfo.IsBionic = true; PlatformInfo.IsBionic = true;
@ -113,6 +61,10 @@ namespace LibRyujinx
var init = Initialize(path); var init = Initialize(path);
Interop.Initialize(new JEnvRef(jniEnv));
Interop.Test();
_surfaceEvent?.Set(); _surfaceEvent?.Set();
_surfaceEvent = new ManualResetEvent(false); _surfaceEvent = new ManualResetEvent(false);
@ -120,15 +72,6 @@ namespace LibRyujinx
return init; return init;
} }
private static string? GetString(JEnvRef jEnv, JStringLocalRef jString)
{
var stringPtr = getStringPointer(jEnv, jString);
var s = Marshal.PtrToStringAnsi(stringPtr);
Marshal.FreeHGlobal(stringPtr);
return s;
}
[UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")] [UnmanagedCallersOnly(EntryPoint = "deviceReloadFilesystem")]
public static void JnaReloadFileSystem() public static void JnaReloadFileSystem()
{ {
@ -192,20 +135,6 @@ namespace LibRyujinx
return stats; return stats;
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_deviceLoad")]
public static bool JniLoadApplicationNative(JEnvRef jEnv, JObjectLocalRef jObj, JStringLocalRef pathPtr)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
if (SwitchDevice?.EmulationContext == null)
{
return false;
}
var path = GetString(jEnv, pathPtr);
return LoadApplication(path);
}
[UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")] [UnmanagedCallersOnly(EntryPoint = "deviceLaunchMiiEditor")]
public static bool JNALaunchMiiEditApplet() public static bool JNALaunchMiiEditApplet()
{ {
@ -336,11 +265,6 @@ namespace LibRyujinx
}); });
} }
private static CCharSequence GetCCharSequence(string s)
{
return Encoding.UTF8.GetBytes(s).AsSpan();
}
[UnmanagedCallersOnly(EntryPoint = "graphicsSetSurface")] [UnmanagedCallersOnly(EntryPoint = "graphicsSetSurface")]
public static void JniSetSurface(long surfacePtr, long window) public static void JniSetSurface(long surfacePtr, long window)
{ {
@ -455,13 +379,6 @@ namespace LibRyujinx
SetVsyncState(enabled); SetVsyncState(enabled);
} }
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererSetSwapBufferCallback")]
public static void JniSetSwapBuffersCallbackNative(JEnvRef jEnv, JObjectLocalRef jObj, IntPtr swapBuffersCallback)
{
Logger.Trace?.Print(LogClass.Application, "Jni Function Call");
_swapBuffersCallback = Marshal.GetDelegateForFunctionPointer<SwapBuffersCallback>(swapBuffersCallback);
}
[UnmanagedCallersOnly(EntryPoint = "inputInitialize")] [UnmanagedCallersOnly(EntryPoint = "inputInitialize")]
public static void JnaInitializeInput(int width, int height) public static void JnaInitializeInput(int width, int height)
{ {
@ -621,22 +538,10 @@ namespace LibRyujinx
SetupUiHandler(); SetupUiHandler();
} }
[UnmanagedCallersOnly(EntryPoint = "uiHandlerWait")]
public static void JniWaitUiHandler()
{
WaitUiHandler();
}
[UnmanagedCallersOnly(EntryPoint = "uiHandlerStopWait")]
public static void JniStopUiHandlerWait()
{
StopUiHandlerWait();
}
[UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")] [UnmanagedCallersOnly(EntryPoint = "uiHandlerSetResponse")]
public static void JniSetUiHandlerResponse(bool isOkPressed, long input) public static void JniSetUiHandlerResponse(bool isOkPressed, IntPtr input)
{ {
SetUiHandlerResponse(isOkPressed, input); SetUiHandlerResponse(isOkPressed, Marshal.PtrToStringAnsi(input) ?? "");
} }
[UnmanagedCallersOnly(EntryPoint = "userOpenUser")] [UnmanagedCallersOnly(EntryPoint = "userOpenUser")]

View File

@ -667,23 +667,7 @@ namespace LibRyujinx
} }
} }
public static void WaitUiHandler() public static void SetUiHandlerResponse(bool isOkPressed, string input)
{
if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler)
{
uiHandler.Wait();
}
}
public static void StopUiHandlerWait()
{
if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler)
{
uiHandler.Set();
}
}
public static void SetUiHandlerResponse(bool isOkPressed, long input)
{ {
if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler) if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler)
{ {

View File

@ -35,8 +35,7 @@ add_library( # Sets the name of the library.
# Provides a relative path to your source file(s). # Provides a relative path to your source file(s).
vulkan_wrapper.cpp vulkan_wrapper.cpp
string_helper.cpp ryujinx.cpp)
ryujinx.cpp)
# Searches for a specified prebuilt library and stores the path as a # Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by # variable. Because CMake includes system libraries in the search path by

View File

@ -20,7 +20,6 @@
#include <fcntl.h> #include <fcntl.h>
#include "adrenotools/driver.h" #include "adrenotools/driver.h"
#include "native_window.h" #include "native_window.h"
#include "string_helper.h"
// A macro to pass call to Vulkan and check for return value for success // A macro to pass call to Vulkan and check for return value for success
#define CALL_VK(func) \ #define CALL_VK(func) \
@ -39,49 +38,6 @@
void *_ryujinxNative = NULL; void *_ryujinxNative = NULL;
class UiHandler {
public:
void setTitle(long storedTitle);
void setMessage(long storedMessage);
void setWatermark(long wm);
void setType(int t);
void setMode(int t);
void setMinLength(int t);
void setMaxLength(int t);
void setInitialText(long text);
void setSubtitle(long text);
long getTitle();
long getMessage();
long getWatermark();
long getInitialText();
long getSubtitle();
int type = 0;
int keyboardMode = 0;
int min_length = -1;
int max_length = -1;
private:
long title = -1;
long message = -1;
long watermark = -1;
long initialText = -1;
long subtitle = -1;
};
// Ryujinx imported functions // Ryujinx imported functions
bool (*initialize)(char *) = NULL; bool (*initialize)(char *) = NULL;
@ -89,7 +45,5 @@ long _renderingThreadId = 0;
JavaVM *_vm = nullptr; JavaVM *_vm = nullptr;
jobject _mainActivity = nullptr; jobject _mainActivity = nullptr;
jclass _mainActivityClass = nullptr; jclass _mainActivityClass = nullptr;
string_helper str_helper = string_helper();
UiHandler ui_handler = UiHandler();
#endif //RYUJINXNATIVE_RYUIJNX_H #endif //RYUJINXNATIVE_RYUIJNX_H

View File

@ -311,61 +311,10 @@ Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz
return createStringFromStdString(env, progressInfo); return createStringFromStdString(env, progressInfo);
} }
extern "C"
long storeString(char *str) {
return str_helper.store_cstring(str);
}
extern "C"
const char *getString(long id) {
auto str = str_helper.get_stored(id);
auto cstr = (char *) ::malloc(str.length() + 1);
::strcpy(cstr, str.c_str());
return cstr;
}
extern "C"
{
void setUiHandlerTitle(long title) {
ui_handler.setTitle(title);
}
void setUiHandlerMessage(long message) {
ui_handler.setMessage(message);
}
void setUiHandlerWatermark(long wm) {
ui_handler.setWatermark(wm);
}
void setUiHandlerType(int type) {
ui_handler.setType(type);
}
void setUiHandlerKeyboardMode(int mode) {
ui_handler.setMode(mode);
}
void setUiHandlerMinLength(int length) {
ui_handler.setMinLength(length);
}
void setUiHandlerMaxLength(int length) {
ui_handler.setMaxLength(length);
}
void setUiHandlerInitialText(long text) {
ui_handler.setInitialText(text);
}
void setUiHandlerSubtitle(long text) {
ui_handler.setSubtitle(text);
}
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz, jstring string) {
auto str = getStringPointer(env, string);
return str_helper.store_cstring(str);
}
extern "C" extern "C"
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong id) { Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong ptr) {
return createStringFromStdString(env, id > -1 ? str_helper.get_stored(id) : ""); return createString(env, (char*)ptr);
} }
extern "C" extern "C"
@ -374,145 +323,3 @@ Java_org_ryujinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *en
jboolean is_flipped) { jboolean is_flipped) {
isInitialOrientationFlipped = is_flipped; isInitialOrientationFlipped = is_flipped;
} }
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestType(JNIEnv *env, jobject thiz) {
return ui_handler.type;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestTitle(JNIEnv *env, jobject thiz) {
return ui_handler.getTitle();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestMessage(JNIEnv *env, jobject thiz) {
return ui_handler.getMessage();
}
void UiHandler::setTitle(long storedTitle) {
if (title != -1) {
str_helper.get_stored(title);
title = -1;
}
title = storedTitle;
}
void UiHandler::setMessage(long storedMessage) {
if (message != -1) {
str_helper.get_stored(message);
message = -1;
}
message = storedMessage;
}
void UiHandler::setType(int t) {
this->type = t;
}
long UiHandler::getTitle() {
auto v = title;
title = -1;
return v;
}
long UiHandler::getMessage() {
auto v = message;
message = -1;
return v;
}
void UiHandler::setWatermark(long wm) {
if (watermark != -1) {
str_helper.get_stored(watermark);
watermark = -1;
}
watermark = wm;
}
void UiHandler::setMinLength(int t) {
this->min_length = t;
}
void UiHandler::setMaxLength(int t) {
this->max_length = t;
}
long UiHandler::getWatermark() {
auto v = watermark;
watermark = -1;
return v;
}
void UiHandler::setInitialText(long text) {
if (initialText != -1) {
str_helper.get_stored(watermark);
initialText = -1;
}
initialText = text;
}
void UiHandler::setSubtitle(long text) {
if (subtitle != -1) {
str_helper.get_stored(subtitle);
subtitle = -1;
}
subtitle = text;
}
long UiHandler::getInitialText() {
auto v = initialText;
initialText = -1;
return v;
}
long UiHandler::getSubtitle() {
auto v = subtitle;
subtitle = -1;
return v;
}
void UiHandler::setMode(int t) {
keyboardMode = t;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMinLength(JNIEnv *env, jobject thiz) {
return ui_handler.min_length;
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerMaxLength(JNIEnv *env, jobject thiz) {
return ui_handler.max_length;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestWatermark(JNIEnv *env, jobject thiz) {
return ui_handler.getWatermark();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestInitialText(JNIEnv *env, jobject thiz) {
return ui_handler.getInitialText();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerRequestSubtitle(JNIEnv *env, jobject thiz) {
return ui_handler.getSubtitle();
}
extern "C"
JNIEXPORT jint JNICALL
Java_org_ryujinx_android_NativeHelpers_getUiHandlerKeyboardMode(JNIEnv *env, jobject thiz) {
return ui_handler.keyboardMode;
}

View File

@ -1,24 +0,0 @@
//
// Created by Emmanuel Hansen on 10/30/2023.
//
#include "string_helper.h"
long string_helper::store_cstring(const char *cstr) {
auto id = ++current_id;
_map.insert({id, cstr});
return id;
}
long string_helper::store_string(const string& str) {
auto id = ++current_id;
_map.insert({id, str});
return id;
}
string string_helper::get_stored(long id) {
auto str = _map[id];
_map.erase(id);
return str;
}

View File

@ -1,29 +0,0 @@
//
// Created by Emmanuel Hansen on 10/30/2023.
//
#ifndef RYUJINXANDROID_STRING_HELPER_H
#define RYUJINXANDROID_STRING_HELPER_H
#include <string>
#include <unordered_map>
using namespace std;
class string_helper {
public:
long store_cstring(const char * cstr);
long store_string(const string& str);
string get_stored(long id);
string_helper(){
_map = unordered_map<long,string>();
current_id = 0;
}
private:
unordered_map<long, string> _map;
long current_id;
};
#endif //RYUJINXANDROID_STRING_HELPER_H

View File

@ -72,7 +72,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
_isInit = false _isInit = false
_isStarted = false _isStarted = false
mainViewModel.activity.uiHandler.stop() RyujinxNative.jnaInstance.uiHandlerSetResponse(false, "")
_updateThread?.join() _updateThread?.join()
_renderingThreadWatcher?.join() _renderingThreadWatcher?.join()
@ -142,10 +142,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
} }
private fun runGame() { private fun runGame() {
thread {
mainViewModel.activity.uiHandler.listen()
}
RyujinxNative.jnaInstance.graphicsRendererRunLoop() RyujinxNative.jnaInstance.graphicsRendererRunLoop()
game?.close() game?.close()

View File

@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import com.anggrayudi.storage.SimpleStorageHelper import com.anggrayudi.storage.SimpleStorageHelper
import com.sun.jna.JNIEnv
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
import org.ryujinx.android.viewmodels.MainViewModel import org.ryujinx.android.viewmodels.MainViewModel
import org.ryujinx.android.viewmodels.QuickSettings import org.ryujinx.android.viewmodels.QuickSettings
@ -101,7 +102,7 @@ class MainActivity : BaseActivity() {
quickSettings.enableTraceLogs quickSettings.enableTraceLogs
) )
val success = val success =
RyujinxNative.jnaInstance.javaInitialize(appPath) RyujinxNative.jnaInstance.javaInitialize(appPath, JNIEnv.CURRENT)
uiHandler = UiHandler() uiHandler = UiHandler()
_isInit = success _isInit = success

View File

@ -30,16 +30,6 @@ class NativeHelpers {
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
external fun getProgressInfo(): String external fun getProgressInfo(): String
external fun getProgressValue(): Float external fun getProgressValue(): Float
external fun storeStringJava(string: String): Long external fun getStringJava(ptr: Long): String
external fun getStringJava(id: Long): String
external fun setIsInitialOrientationFlipped(isFlipped: Boolean) external fun setIsInitialOrientationFlipped(isFlipped: Boolean)
external fun getUiHandlerRequestType(): Int
external fun getUiHandlerRequestTitle(): Long
external fun getUiHandlerRequestMessage(): Long
external fun getUiHandlerMinLength(): Int
external fun getUiHandlerMaxLength(): Int
external fun getUiHandlerKeyboardMode(): Int
external fun getUiHandlerRequestWatermark(): Long
external fun getUiHandlerRequestInitialText(): Long
external fun getUiHandlerRequestSubtitle(): Long
} }

View File

@ -1,8 +1,10 @@
package org.ryujinx.android package org.ryujinx.android
import com.sun.jna.JNIEnv
import com.sun.jna.Library import com.sun.jna.Library
import com.sun.jna.Native import com.sun.jna.Native
import org.ryujinx.android.viewmodels.GameInfo import org.ryujinx.android.viewmodels.GameInfo
import java.util.Collections
interface RyujinxNativeJna : Library { interface RyujinxNativeJna : Library {
fun deviceInitialize( fun deviceInitialize(
@ -35,7 +37,7 @@ interface RyujinxNativeJna : Library {
driver: Long driver: Long
): Boolean ): Boolean
fun javaInitialize(appPath: String): Boolean fun javaInitialize(appPath: String, env: JNIEnv): Boolean
fun deviceLaunchMiiEditor(): Boolean fun deviceLaunchMiiEditor(): Boolean
fun deviceGetGameFrameRate(): Double fun deviceGetGameFrameRate(): Double
fun deviceGetGameFrameTime(): Double fun deviceGetGameFrameTime(): Double
@ -73,9 +75,7 @@ interface RyujinxNativeJna : Library {
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean) fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
fun deviceGetInstalledFirmwareVersion(): String fun deviceGetInstalledFirmwareVersion(): String
fun uiHandlerSetup() fun uiHandlerSetup()
fun uiHandlerWait() fun uiHandlerSetResponse(isOkPressed: Boolean, input: String)
fun uiHandlerStopWait()
fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
fun deviceGetDlcTitleId(path: String, ncaPath: String): String fun deviceGetDlcTitleId(path: String, ncaPath: String): String
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo) fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
fun userGetAllUsers(): Array<String> fun userGetAllUsers(): Array<String>
@ -88,7 +88,47 @@ class RyujinxNative {
companion object { companion object {
val jnaInstance: RyujinxNativeJna = Native.load( val jnaInstance: RyujinxNativeJna = Native.load(
"ryujinx", "ryujinx",
RyujinxNativeJna::class.java RyujinxNativeJna::class.java,
Collections.singletonMap(Library.OPTION_ALLOW_OBJECTS, true)
) )
@JvmStatic
fun test()
{
val i = 0
}
@JvmStatic
fun updateUiHandler(
newTitlePointer: Long,
newMessagePointer: Long,
newWatermarkPointer: Long,
newType: Int,
min: Int,
max: Int,
nMode: Int,
newSubtitlePointer: Long,
newInitialTextPointer: Long
)
{
var uiHandler = MainActivity.mainViewModel?.activity?.uiHandler
uiHandler?.apply {
val newTitle = NativeHelpers.instance.getStringJava(newTitlePointer)
val newMessage = NativeHelpers.instance.getStringJava(newMessagePointer)
val newWatermark = NativeHelpers.instance.getStringJava(newWatermarkPointer)
val newSubtitle = NativeHelpers.instance.getStringJava(newSubtitlePointer)
val newInitialText = NativeHelpers.instance.getStringJava(newInitialTextPointer)
val newMode = KeyboardMode.entries[nMode]
update(newTitle,
newMessage,
newWatermark,
newType,
min,
max,
newMode,
newSubtitle,
newInitialText);
}
}
} }
} }

View File

@ -12,7 +12,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.BasicAlertDialog import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -32,7 +31,7 @@ import androidx.compose.ui.window.DialogProperties
import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.material3.RichText
internal enum class KeyboardMode { enum class KeyboardMode {
Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin
} }
@ -48,39 +47,34 @@ class UiHandler {
val inputText = mutableStateOf("") val inputText = mutableStateOf("")
var title: String = "" var title: String = ""
var message: String = "" var message: String = ""
var shouldListen = true
init { init {
RyujinxNative.jnaInstance.uiHandlerSetup() RyujinxNative.jnaInstance.uiHandlerSetup()
} }
fun listen() { fun update(
showMessage.value = false newTitle: String,
while (shouldListen) { newMessage: String,
RyujinxNative.jnaInstance.uiHandlerWait() newWatermark: String,
newType: Int,
title = min: Int,
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle()) max: Int,
message = newMode: KeyboardMode,
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestMessage()) newSubtitle: String,
watermark = newInitialText: String
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestWatermark()) )
type = NativeHelpers.instance.getUiHandlerRequestType() {
minLength = NativeHelpers.instance.getUiHandlerMinLength() title = newTitle
maxLength = NativeHelpers.instance.getUiHandlerMaxLength() message = newMessage
mode = KeyboardMode.values()[NativeHelpers.instance.getUiHandlerKeyboardMode()] watermark = newWatermark
subtitle = type = newType
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestSubtitle()) minLength = min
initialText = maxLength = max
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestInitialText()) mode = newMode
inputText.value = initialText subtitle = newSubtitle
showMessage.value = type > 0 initialText = newInitialText
} inputText.value = initialText
} showMessage.value = type > 0
fun stop() {
shouldListen = false
RyujinxNative.jnaInstance.uiHandlerStopWait()
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -119,15 +113,15 @@ class UiHandler {
} }
fun submit() { fun submit() {
var input: Long = -1
if (type == 2) { if (type == 2) {
if (inputListener.value.length < minLength || inputListener.value.length > maxLength) if (inputListener.value.length < minLength || inputListener.value.length > maxLength)
return return
input =
NativeHelpers.instance.storeStringJava(inputListener.value)
} }
RyujinxNative.jnaInstance.uiHandlerSetResponse(
true,
if (type == 2) inputListener.value else ""
)
showMessageListener.value = false showMessageListener.value = false
RyujinxNative.jnaInstance.uiHandlerSetResponse(true, input)
} }
if (showMessageListener.value) { if (showMessageListener.value) {

View File

@ -24,7 +24,7 @@ android.nonTransitiveRClass=true
# Build configuration # Build configuration
# It needs to be set to either "debug" or "release" and can also be overriden on a per build basis # It needs to be set to either "debug" or "release" and can also be overriden on a per build basis
# by adding -Dorg.ryujinx.config=NAME to the command line. # by adding -Dorg.ryujinx.config=NAME to the command line.
org.ryujinx.config=release org.ryujinx.config=debug
# Controls stripping of symbols from libryujinx # Controls stripping of symbols from libryujinx
# Setting this property to auto causes symbols to be stripped for release builds, # Setting this property to auto causes symbols to be stripped for release builds,
# but not for debug builds. # but not for debug builds.