forked from MeloNX/MeloNX
android - update ui handler to call back into java
This commit is contained in:
parent
b7deaefff7
commit
e2f584a7ff
@ -2,6 +2,7 @@ using LibHac.Tools.Fs;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||
using Ryujinx.HLE.UI;
|
||||
using System;
|
||||
@ -15,20 +16,13 @@ namespace LibRyujinx.Android
|
||||
{
|
||||
internal class AndroidUIHandler : IHostUIHandler, IDisposable
|
||||
{
|
||||
public ManualResetEvent _waitEvent;
|
||||
public ManualResetEvent _responseEvent;
|
||||
private bool _isDisposed;
|
||||
private bool _isOkPressed;
|
||||
private long _input;
|
||||
private string? _input;
|
||||
private ManualResetEvent _resetEvent = new ManualResetEvent(false);
|
||||
|
||||
public IHostUITheme HostUITheme => throw new NotImplementedException();
|
||||
|
||||
public AndroidUIHandler()
|
||||
{
|
||||
_waitEvent = new ManualResetEvent(false);
|
||||
_responseEvent = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
@ -36,47 +30,51 @@ namespace LibRyujinx.Android
|
||||
|
||||
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
|
||||
{
|
||||
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? ""));
|
||||
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? ""));
|
||||
LibRyujinx.setUiHandlerType(1);
|
||||
|
||||
_responseEvent.Reset();
|
||||
Set();
|
||||
_responseEvent.WaitOne();
|
||||
Interop.UpdateUiHandler(title ?? "",
|
||||
message ?? "",
|
||||
"",
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
KeyboardMode.Default,
|
||||
"",
|
||||
"");
|
||||
|
||||
return _isOkPressed;
|
||||
}
|
||||
|
||||
public bool DisplayInputDialog(SoftwareKeyboardUIArgs args, out string userText)
|
||||
{
|
||||
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString("Software Keyboard"));
|
||||
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(args.HeaderText ?? ""));
|
||||
LibRyujinx.setUiHandlerWatermark(LibRyujinx.storeString(args.GuideText ?? ""));
|
||||
LibRyujinx.setUiHandlerSubtitle(LibRyujinx.storeString(args.SubtitleText ?? ""));
|
||||
LibRyujinx.setUiHandlerInitialText(LibRyujinx.storeString(args.InitialText ?? ""));
|
||||
LibRyujinx.setUiHandlerMinLength(args.StringLengthMin);
|
||||
LibRyujinx.setUiHandlerMaxLength(args.StringLengthMax);
|
||||
LibRyujinx.setUiHandlerType(2);
|
||||
LibRyujinx.setUiHandlerKeyboardMode((int)args.KeyboardMode);
|
||||
_input = null;
|
||||
_resetEvent.Reset();
|
||||
Interop.UpdateUiHandler("Software Keyboard",
|
||||
args.HeaderText ?? "",
|
||||
args.GuideText ?? "",
|
||||
2,
|
||||
args.StringLengthMin,
|
||||
args.StringLengthMax,
|
||||
args.KeyboardMode,
|
||||
args.SubtitleText ?? "",
|
||||
args.InitialText ?? "");
|
||||
|
||||
_responseEvent.Reset();
|
||||
Set();
|
||||
_responseEvent.WaitOne();
|
||||
_resetEvent.WaitOne();
|
||||
|
||||
userText = _input != -1 ? LibRyujinx.GetStoredString(_input) : "";
|
||||
userText = _input ?? "";
|
||||
|
||||
return _isOkPressed;
|
||||
}
|
||||
|
||||
public bool DisplayMessageDialog(string title, string message)
|
||||
{
|
||||
LibRyujinx.setUiHandlerTitle(LibRyujinx.storeString(title ?? ""));
|
||||
LibRyujinx.setUiHandlerMessage(LibRyujinx.storeString(message ?? ""));
|
||||
LibRyujinx.setUiHandlerType(1);
|
||||
|
||||
_responseEvent.Reset();
|
||||
Set();
|
||||
_responseEvent.WaitOne();
|
||||
Interop.UpdateUiHandler(title ?? "",
|
||||
message ?? "",
|
||||
"",
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
KeyboardMode.Default,
|
||||
"",
|
||||
"");
|
||||
|
||||
return _isOkPressed;
|
||||
}
|
||||
@ -99,38 +97,18 @@ namespace LibRyujinx.Android
|
||||
// throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal void Wait()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_waitEvent.Reset();
|
||||
_waitEvent.WaitOne();
|
||||
_waitEvent.Reset();
|
||||
}
|
||||
|
||||
internal void Set()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_waitEvent.Set();
|
||||
}
|
||||
|
||||
internal void SetResponse(bool isOkPressed, long input)
|
||||
internal void SetResponse(bool isOkPressed, string input)
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
_isOkPressed = isOkPressed;
|
||||
_input = input;
|
||||
_responseEvent.Set();
|
||||
_resetEvent.Set();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_isDisposed = true;
|
||||
_waitEvent.Set();
|
||||
_waitEvent.Set();
|
||||
_responseEvent.Dispose();
|
||||
_waitEvent.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
190
src/LibRyujinx/Android/Interop.cs
Normal file
190
src/LibRyujinx/Android/Interop.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using LibRyujinx.Android;
|
||||
using LibRyujinx.Jni.Pointers;
|
||||
using LibRyujinx.Jni.References;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@ -14,9 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace LibRyujinx
|
||||
@ -29,56 +27,6 @@ namespace LibRyujinx
|
||||
|
||||
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")]
|
||||
internal extern static void setRenderingThread();
|
||||
|
||||
@ -97,7 +45,7 @@ namespace LibRyujinx
|
||||
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
|
||||
|
||||
[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");
|
||||
PlatformInfo.IsBionic = true;
|
||||
@ -113,6 +61,10 @@ namespace LibRyujinx
|
||||
|
||||
var init = Initialize(path);
|
||||
|
||||
Interop.Initialize(new JEnvRef(jniEnv));
|
||||
|
||||
Interop.Test();
|
||||
|
||||
_surfaceEvent?.Set();
|
||||
|
||||
_surfaceEvent = new ManualResetEvent(false);
|
||||
@ -120,15 +72,6 @@ namespace LibRyujinx
|
||||
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")]
|
||||
public static void JnaReloadFileSystem()
|
||||
{
|
||||
@ -192,20 +135,6 @@ namespace LibRyujinx
|
||||
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")]
|
||||
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")]
|
||||
public static void JniSetSurface(long surfacePtr, long window)
|
||||
{
|
||||
@ -455,13 +379,6 @@ namespace LibRyujinx
|
||||
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")]
|
||||
public static void JnaInitializeInput(int width, int height)
|
||||
{
|
||||
@ -621,22 +538,10 @@ namespace LibRyujinx
|
||||
SetupUiHandler();
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "uiHandlerWait")]
|
||||
public static void JniWaitUiHandler()
|
||||
{
|
||||
WaitUiHandler();
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(EntryPoint = "uiHandlerStopWait")]
|
||||
public static void JniStopUiHandlerWait()
|
||||
{
|
||||
StopUiHandlerWait();
|
||||
}
|
||||
|
||||
[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")]
|
||||
|
@ -667,23 +667,7 @@ namespace LibRyujinx
|
||||
}
|
||||
}
|
||||
|
||||
public static void WaitUiHandler()
|
||||
{
|
||||
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)
|
||||
public static void SetUiHandlerResponse(bool isOkPressed, string input)
|
||||
{
|
||||
if (SwitchDevice?.HostUiHandler is AndroidUIHandler uiHandler)
|
||||
{
|
||||
|
@ -35,8 +35,7 @@ add_library( # Sets the name of the library.
|
||||
|
||||
# Provides a relative path to your source file(s).
|
||||
vulkan_wrapper.cpp
|
||||
string_helper.cpp
|
||||
ryujinx.cpp)
|
||||
ryujinx.cpp)
|
||||
|
||||
# Searches for a specified prebuilt library and stores the path as a
|
||||
# variable. Because CMake includes system libraries in the search path by
|
||||
|
@ -20,7 +20,6 @@
|
||||
#include <fcntl.h>
|
||||
#include "adrenotools/driver.h"
|
||||
#include "native_window.h"
|
||||
#include "string_helper.h"
|
||||
|
||||
// A macro to pass call to Vulkan and check for return value for success
|
||||
#define CALL_VK(func) \
|
||||
@ -39,49 +38,6 @@
|
||||
|
||||
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
|
||||
bool (*initialize)(char *) = NULL;
|
||||
|
||||
@ -89,7 +45,5 @@ long _renderingThreadId = 0;
|
||||
JavaVM *_vm = nullptr;
|
||||
jobject _mainActivity = nullptr;
|
||||
jclass _mainActivityClass = nullptr;
|
||||
string_helper str_helper = string_helper();
|
||||
UiHandler ui_handler = UiHandler();
|
||||
|
||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||
|
@ -311,61 +311,10 @@ Java_org_ryujinx_android_NativeHelpers_getProgressInfo(JNIEnv *env, jobject thiz
|
||||
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"
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong id) {
|
||||
return createStringFromStdString(env, id > -1 ? str_helper.get_stored(id) : "");
|
||||
Java_org_ryujinx_android_NativeHelpers_getStringJava(JNIEnv *env, jobject thiz, jlong ptr) {
|
||||
return createString(env, (char*)ptr);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
@ -374,145 +323,3 @@ Java_org_ryujinx_android_NativeHelpers_setIsInitialOrientationFlipped(JNIEnv *en
|
||||
jboolean 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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -72,7 +72,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
_isInit = false
|
||||
_isStarted = false
|
||||
|
||||
mainViewModel.activity.uiHandler.stop()
|
||||
RyujinxNative.jnaInstance.uiHandlerSetResponse(false, "")
|
||||
|
||||
_updateThread?.join()
|
||||
_renderingThreadWatcher?.join()
|
||||
@ -142,10 +142,6 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
}
|
||||
|
||||
private fun runGame() {
|
||||
|
||||
thread {
|
||||
mainViewModel.activity.uiHandler.listen()
|
||||
}
|
||||
RyujinxNative.jnaInstance.graphicsRendererRunLoop()
|
||||
|
||||
game?.close()
|
||||
|
@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.sun.jna.JNIEnv
|
||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
||||
import org.ryujinx.android.viewmodels.MainViewModel
|
||||
import org.ryujinx.android.viewmodels.QuickSettings
|
||||
@ -101,7 +102,7 @@ class MainActivity : BaseActivity() {
|
||||
quickSettings.enableTraceLogs
|
||||
)
|
||||
val success =
|
||||
RyujinxNative.jnaInstance.javaInitialize(appPath)
|
||||
RyujinxNative.jnaInstance.javaInitialize(appPath, JNIEnv.CURRENT)
|
||||
|
||||
uiHandler = UiHandler()
|
||||
_isInit = success
|
||||
|
@ -30,16 +30,6 @@ class NativeHelpers {
|
||||
external fun setSwapInterval(nativeWindow: Long, swapInterval: Int): Int
|
||||
external fun getProgressInfo(): String
|
||||
external fun getProgressValue(): Float
|
||||
external fun storeStringJava(string: String): Long
|
||||
external fun getStringJava(id: Long): String
|
||||
external fun getStringJava(ptr: Long): String
|
||||
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
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package org.ryujinx.android
|
||||
|
||||
import com.sun.jna.JNIEnv
|
||||
import com.sun.jna.Library
|
||||
import com.sun.jna.Native
|
||||
import org.ryujinx.android.viewmodels.GameInfo
|
||||
import java.util.Collections
|
||||
|
||||
interface RyujinxNativeJna : Library {
|
||||
fun deviceInitialize(
|
||||
@ -35,7 +37,7 @@ interface RyujinxNativeJna : Library {
|
||||
driver: Long
|
||||
): Boolean
|
||||
|
||||
fun javaInitialize(appPath: String): Boolean
|
||||
fun javaInitialize(appPath: String, env: JNIEnv): Boolean
|
||||
fun deviceLaunchMiiEditor(): Boolean
|
||||
fun deviceGetGameFrameRate(): Double
|
||||
fun deviceGetGameFrameTime(): Double
|
||||
@ -73,9 +75,7 @@ interface RyujinxNativeJna : Library {
|
||||
fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
||||
fun deviceGetInstalledFirmwareVersion(): String
|
||||
fun uiHandlerSetup()
|
||||
fun uiHandlerWait()
|
||||
fun uiHandlerStopWait()
|
||||
fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
|
||||
fun uiHandlerSetResponse(isOkPressed: Boolean, input: String)
|
||||
fun deviceGetDlcTitleId(path: String, ncaPath: String): String
|
||||
fun deviceGetGameInfo(fileDescriptor: Int, extension: String, info: GameInfo)
|
||||
fun userGetAllUsers(): Array<String>
|
||||
@ -88,7 +88,47 @@ class RyujinxNative {
|
||||
companion object {
|
||||
val jnaInstance: RyujinxNativeJna = Native.load(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.BasicAlertDialog
|
||||
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.ui.material3.RichText
|
||||
|
||||
internal enum class KeyboardMode {
|
||||
enum class KeyboardMode {
|
||||
Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin
|
||||
}
|
||||
|
||||
@ -48,39 +47,34 @@ class UiHandler {
|
||||
val inputText = mutableStateOf("")
|
||||
var title: String = ""
|
||||
var message: String = ""
|
||||
var shouldListen = true
|
||||
|
||||
init {
|
||||
RyujinxNative.jnaInstance.uiHandlerSetup()
|
||||
}
|
||||
|
||||
fun listen() {
|
||||
showMessage.value = false
|
||||
while (shouldListen) {
|
||||
RyujinxNative.jnaInstance.uiHandlerWait()
|
||||
|
||||
title =
|
||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestTitle())
|
||||
message =
|
||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestMessage())
|
||||
watermark =
|
||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestWatermark())
|
||||
type = NativeHelpers.instance.getUiHandlerRequestType()
|
||||
minLength = NativeHelpers.instance.getUiHandlerMinLength()
|
||||
maxLength = NativeHelpers.instance.getUiHandlerMaxLength()
|
||||
mode = KeyboardMode.values()[NativeHelpers.instance.getUiHandlerKeyboardMode()]
|
||||
subtitle =
|
||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestSubtitle())
|
||||
initialText =
|
||||
NativeHelpers.instance.getStringJava(NativeHelpers.instance.getUiHandlerRequestInitialText())
|
||||
inputText.value = initialText
|
||||
showMessage.value = type > 0
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
shouldListen = false
|
||||
RyujinxNative.jnaInstance.uiHandlerStopWait()
|
||||
fun update(
|
||||
newTitle: String,
|
||||
newMessage: String,
|
||||
newWatermark: String,
|
||||
newType: Int,
|
||||
min: Int,
|
||||
max: Int,
|
||||
newMode: KeyboardMode,
|
||||
newSubtitle: String,
|
||||
newInitialText: String
|
||||
)
|
||||
{
|
||||
title = newTitle
|
||||
message = newMessage
|
||||
watermark = newWatermark
|
||||
type = newType
|
||||
minLength = min
|
||||
maxLength = max
|
||||
mode = newMode
|
||||
subtitle = newSubtitle
|
||||
initialText = newInitialText
|
||||
inputText.value = initialText
|
||||
showMessage.value = type > 0
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -119,15 +113,15 @@ class UiHandler {
|
||||
}
|
||||
|
||||
fun submit() {
|
||||
var input: Long = -1
|
||||
if (type == 2) {
|
||||
if (inputListener.value.length < minLength || inputListener.value.length > maxLength)
|
||||
return
|
||||
input =
|
||||
NativeHelpers.instance.storeStringJava(inputListener.value)
|
||||
}
|
||||
RyujinxNative.jnaInstance.uiHandlerSetResponse(
|
||||
true,
|
||||
if (type == 2) inputListener.value else ""
|
||||
)
|
||||
showMessageListener.value = false
|
||||
RyujinxNative.jnaInstance.uiHandlerSetResponse(true, input)
|
||||
}
|
||||
|
||||
if (showMessageListener.value) {
|
||||
|
@ -24,7 +24,7 @@ android.nonTransitiveRClass=true
|
||||
# Build configuration
|
||||
# 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.
|
||||
org.ryujinx.config=release
|
||||
org.ryujinx.config=debug
|
||||
# Controls stripping of symbols from libryujinx
|
||||
# Setting this property to auto causes symbols to be stripped for release builds,
|
||||
# but not for debug builds.
|
||||
|
Reference in New Issue
Block a user