forked from MeloNX/MeloNX
android - add basic software keyboard support
This commit is contained in:
parent
c12da6d650
commit
b81643352e
@ -1,4 +1,4 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.Logging.Formatters;
|
using Ryujinx.Common.Logging.Formatters;
|
||||||
using Ryujinx.Common.Logging.Targets;
|
using Ryujinx.Common.Logging.Targets;
|
||||||
using System;
|
using System;
|
||||||
@ -12,7 +12,7 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
string ILogTarget.Name { get => _name; }
|
string ILogTarget.Name { get => _name; }
|
||||||
|
|
||||||
public AndroidLogTarget( string name)
|
public AndroidLogTarget(string name)
|
||||||
{
|
{
|
||||||
_name = name;
|
_name = name;
|
||||||
_formatter = new DefaultLogFormatter();
|
_formatter = new DefaultLogFormatter();
|
||||||
|
136
src/LibRyujinx/Android/AndroidUiHandler.cs
Normal file
136
src/LibRyujinx/Android/AndroidUiHandler.cs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE;
|
||||||
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationProxy.Types;
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LibRyujinx.Android
|
||||||
|
{
|
||||||
|
internal class AndroidUiHandler : IHostUiHandler, IDisposable
|
||||||
|
{
|
||||||
|
public IHostUiTheme HostUiTheme => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public ManualResetEvent _waitEvent;
|
||||||
|
public ManualResetEvent _responseEvent;
|
||||||
|
private bool _isDisposed;
|
||||||
|
private bool _isOkPressed;
|
||||||
|
private long _input;
|
||||||
|
|
||||||
|
public AndroidUiHandler()
|
||||||
|
{
|
||||||
|
_waitEvent = new ManualResetEvent(false);
|
||||||
|
_responseEvent = new ManualResetEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDynamicTextInputHandler CreateDynamicTextInputHandler()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
_responseEvent.Reset();
|
||||||
|
Set();
|
||||||
|
_responseEvent.WaitOne();
|
||||||
|
|
||||||
|
userText = _input != -1 ? LibRyujinx.GetStoredString(_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();
|
||||||
|
|
||||||
|
return _isOkPressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DisplayMessageDialog(ControllerAppletUiArgs args)
|
||||||
|
{
|
||||||
|
string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
|
||||||
|
|
||||||
|
string message = $"Application requests **{playerCount}** player(s) with:\n\n"
|
||||||
|
+ $"**TYPES:** {args.SupportedStyles}\n\n"
|
||||||
|
+ $"**PLAYERS:** {string.Join(", ", args.SupportedPlayers)}\n\n"
|
||||||
|
+ (args.IsDocked ? "Docked mode set. `Handheld` is also invalid.\n\n" : "")
|
||||||
|
+ "_Please reconfigure Input now and then press OK._";
|
||||||
|
|
||||||
|
return DisplayMessageDialog("Controller Applet", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value)
|
||||||
|
{
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
return;
|
||||||
|
_isOkPressed = isOkPressed;
|
||||||
|
_input = input;
|
||||||
|
_responseEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_isDisposed = true;
|
||||||
|
_waitEvent.Set();
|
||||||
|
_waitEvent.Set();
|
||||||
|
_responseEvent.Dispose();
|
||||||
|
_waitEvent.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,11 +42,36 @@ namespace LibRyujinx
|
|||||||
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
||||||
|
|
||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
private extern static long storeString(string ch);
|
internal extern static long storeString(string ch);
|
||||||
[DllImport("libryujinxjni")]
|
|
||||||
private extern static IntPtr getString(long id);
|
|
||||||
|
|
||||||
private static string GetStoredString(long id)
|
[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);
|
var pointer = getString(id);
|
||||||
if (pointer != IntPtr.Zero)
|
if (pointer != IntPtr.Zero)
|
||||||
@ -91,7 +116,7 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
Logger.AddTarget(
|
Logger.AddTarget(
|
||||||
new AsyncLogTargetWrapper(
|
new AsyncLogTargetWrapper(
|
||||||
new AndroidLogTarget("Ryujinx"),
|
new AndroidLogTarget("RyujinxLog"),
|
||||||
1000,
|
1000,
|
||||||
AsyncLogTargetOverflowAction.Block
|
AsyncLogTargetOverflowAction.Block
|
||||||
));
|
));
|
||||||
@ -709,6 +734,30 @@ namespace LibRyujinx
|
|||||||
DeleteUser(userId);
|
DeleteUser(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetup")]
|
||||||
|
public static void JniSetupUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
|
{
|
||||||
|
SetupUiHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerWait")]
|
||||||
|
public static void JniWaitUiHandler(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
|
{
|
||||||
|
WaitUiHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerStopWait")]
|
||||||
|
public static void JniStopUiHandlerWait(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
|
{
|
||||||
|
StopUiHandlerWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_uiHandlerSetResponse")]
|
||||||
|
public static void JniSetUiHandlerResponse(JEnvRef jEnv, JObjectLocalRef jObj, JBoolean isOkPressed, JLong input)
|
||||||
|
{
|
||||||
|
SetUiHandlerResponse(isOkPressed, input);
|
||||||
|
}
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_userOpenUser")]
|
||||||
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
||||||
{
|
{
|
||||||
|
@ -37,6 +37,8 @@ using System.Collections.Generic;
|
|||||||
using LibHac.Bcat;
|
using LibHac.Bcat;
|
||||||
using Ryujinx.Ui.App.Common;
|
using Ryujinx.Ui.App.Common;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Ryujinx.HLE.Ui;
|
||||||
|
using LibRyujinx.Android;
|
||||||
|
|
||||||
namespace LibRyujinx
|
namespace LibRyujinx
|
||||||
{
|
{
|
||||||
@ -89,7 +91,7 @@ namespace LibRyujinx
|
|||||||
Console.WriteLine(ex);
|
Console.WriteLine(ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenALLibraryNameContainer.OverridePath = "libopenal.so";
|
OpenALLibraryNameContainer.OverridePath = "libopenal.so";
|
||||||
|
|
||||||
Logger.Notice.Print(LogClass.Application, "RyujinxAndroid is ready!");
|
Logger.Notice.Print(LogClass.Application, "RyujinxAndroid is ready!");
|
||||||
@ -142,7 +144,9 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
var gameInfo = new GameInfo
|
var gameInfo = new GameInfo
|
||||||
{
|
{
|
||||||
FileSize = gameStream.Length * 0.000000000931, TitleName = "Unknown", TitleId = "0000000000000000",
|
FileSize = gameStream.Length * 0.000000000931,
|
||||||
|
TitleName = "Unknown",
|
||||||
|
TitleId = "0000000000000000",
|
||||||
Developer = "Unknown",
|
Developer = "Unknown",
|
||||||
Version = "0",
|
Version = "0",
|
||||||
Icon = null
|
Icon = null
|
||||||
@ -629,11 +633,11 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
public static List<string> GetDlcContentList(string path, ulong titleId)
|
public static List<string> GetDlcContentList(string path, ulong titleId)
|
||||||
{
|
{
|
||||||
if(!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(path);
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new();
|
PartitionFileSystem partitionFileSystem = new();
|
||||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||||
|
|
||||||
@ -665,6 +669,38 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetupUiHandler()
|
||||||
|
{
|
||||||
|
if (SwitchDevice is { } switchDevice)
|
||||||
|
{
|
||||||
|
switchDevice.HostUiHandler = new AndroidUiHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (SwitchDevice?.HostUiHandler is AndroidUiHandler uiHandler)
|
||||||
|
{
|
||||||
|
uiHandler.SetResponse(isOkPressed, input);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SwitchDevice : IDisposable
|
public class SwitchDevice : IDisposable
|
||||||
@ -677,6 +713,7 @@ namespace LibRyujinx
|
|||||||
public UserChannelPersistence UserChannelPersistence { get; set; }
|
public UserChannelPersistence UserChannelPersistence { get; set; }
|
||||||
public InputManager? InputManager { get; set; }
|
public InputManager? InputManager { get; set; }
|
||||||
public Switch? EmulationContext { get; set; }
|
public Switch? EmulationContext { get; set; }
|
||||||
|
public IHostUiHandler? HostUiHandler { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@ -741,7 +778,7 @@ namespace LibRyujinx
|
|||||||
renderer,
|
renderer,
|
||||||
LibRyujinx.AudioDriver, //Audio
|
LibRyujinx.AudioDriver, //Audio
|
||||||
MemoryConfiguration.MemoryConfiguration4GiB,
|
MemoryConfiguration.MemoryConfiguration4GiB,
|
||||||
null,
|
HostUiHandler,
|
||||||
systemLanguage,
|
systemLanguage,
|
||||||
regionCode,
|
regionCode,
|
||||||
enableVsync,
|
enableVsync,
|
||||||
|
@ -38,6 +38,36 @@
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@ -46,7 +76,7 @@ long _currentRenderingThreadId = 0;
|
|||||||
JavaVM* _vm = nullptr;
|
JavaVM* _vm = nullptr;
|
||||||
jobject _mainActivity = nullptr;
|
jobject _mainActivity = nullptr;
|
||||||
jclass _mainActivityClass = nullptr;
|
jclass _mainActivityClass = nullptr;
|
||||||
std::string _currentString = "";
|
|
||||||
string_helper str_helper = string_helper();
|
string_helper str_helper = string_helper();
|
||||||
|
UiHandler ui_handler = UiHandler();
|
||||||
|
|
||||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||||
|
@ -327,6 +327,37 @@ const char* getString(long id){
|
|||||||
return cstr;
|
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"
|
extern "C"
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz, jstring string) {
|
Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz, jstring string) {
|
||||||
@ -337,7 +368,7 @@ Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz
|
|||||||
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 id) {
|
||||||
return createStringFromStdString(env, str_helper.get_stored(id));
|
return createStringFromStdString(env, id > -1 ? str_helper.get_stored(id) : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
@ -346,3 +377,145 @@ 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;
|
||||||
|
}
|
||||||
|
@ -75,6 +75,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
_isInit = false
|
_isInit = false
|
||||||
_isStarted = false
|
_isStarted = false
|
||||||
|
|
||||||
|
mainViewModel.activity.uiHandler.stop()
|
||||||
|
|
||||||
_updateThread?.join()
|
_updateThread?.join()
|
||||||
_renderingThreadWatcher?.join()
|
_renderingThreadWatcher?.join()
|
||||||
}
|
}
|
||||||
@ -161,6 +163,10 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thread {
|
||||||
|
mainViewModel.activity.uiHandler.listen()
|
||||||
|
}
|
||||||
_nativeRyujinx.graphicsRendererRunLoop()
|
_nativeRyujinx.graphicsRendererRunLoop()
|
||||||
|
|
||||||
game?.close()
|
game?.close()
|
||||||
|
@ -18,6 +18,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.halilibo.richtext.ui.RichTextThemeIntegration
|
||||||
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
|
||||||
@ -32,6 +33,7 @@ class MainActivity : BaseActivity() {
|
|||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
var isGameRunning = false
|
var isGameRunning = false
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
|
lateinit var uiHandler: UiHandler
|
||||||
companion object {
|
companion object {
|
||||||
var mainViewModel: MainViewModel? = null
|
var mainViewModel: MainViewModel? = null
|
||||||
var AppPath : String = ""
|
var AppPath : String = ""
|
||||||
@ -77,6 +79,8 @@ class MainActivity : BaseActivity() {
|
|||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, quickSettings.enableGuestLogs)
|
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, quickSettings.enableGuestLogs)
|
||||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, quickSettings.enableTraceLogs)
|
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, quickSettings.enableTraceLogs)
|
||||||
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
|
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
|
||||||
|
|
||||||
|
uiHandler = UiHandler()
|
||||||
_isInit = success
|
_isInit = success
|
||||||
}
|
}
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -107,12 +111,14 @@ class MainActivity : BaseActivity() {
|
|||||||
mainViewModel?.apply {
|
mainViewModel?.apply {
|
||||||
setContent {
|
setContent {
|
||||||
RyujinxAndroidTheme {
|
RyujinxAndroidTheme {
|
||||||
// A surface container using the 'background' color from the theme
|
RichTextThemeIntegration(contentColor = { MaterialTheme.colorScheme.onSurface }) {
|
||||||
Surface(
|
// A surface container using the 'background' color from the theme
|
||||||
modifier = Modifier.fillMaxSize(),
|
Surface(
|
||||||
color = MaterialTheme.colorScheme.background
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
color = MaterialTheme.colorScheme.background
|
||||||
MainView.Main(mainViewModel = this)
|
) {
|
||||||
|
MainView.Main(mainViewModel = this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,4 +32,13 @@ class NativeHelpers {
|
|||||||
external fun storeStringJava(string: String) : Long
|
external fun storeStringJava(string: String) : Long
|
||||||
external fun getStringJava(id: 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
|
||||||
}
|
}
|
||||||
|
@ -74,4 +74,8 @@ class RyujinxNative {
|
|||||||
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
|
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
|
||||||
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
||||||
external fun deviceGetInstalledFirmwareVersion() : Long
|
external fun deviceGetInstalledFirmwareVersion() : Long
|
||||||
|
external fun uiHandlerSetup()
|
||||||
|
external fun uiHandlerWait()
|
||||||
|
external fun uiHandlerStopWait()
|
||||||
|
external fun uiHandlerSetResponse(isOkPressed: Boolean, input: Long)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
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.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.halilibo.richtext.markdown.Markdown
|
||||||
|
import com.halilibo.richtext.ui.RichText
|
||||||
|
|
||||||
|
internal enum class KeyboardMode {
|
||||||
|
Default, Numeric, ASCII, FullLatin, Alphabet, SimplifiedChinese, TraditionalChinese, Korean, LanguageSet2, LanguageSet2Latin
|
||||||
|
}
|
||||||
|
|
||||||
|
class UiHandler {
|
||||||
|
private var initialText: String = ""
|
||||||
|
private var subtitle: String = ""
|
||||||
|
private var maxLength: Int = 0
|
||||||
|
private var minLength: Int = 0
|
||||||
|
private var watermark: String = ""
|
||||||
|
private var type: Int = -1
|
||||||
|
private var mode: KeyboardMode = KeyboardMode.Default
|
||||||
|
val showMessage = mutableStateOf(false)
|
||||||
|
val inputText = mutableStateOf("")
|
||||||
|
var title: String = ""
|
||||||
|
var message: String = ""
|
||||||
|
var shouldListen = true
|
||||||
|
|
||||||
|
init {
|
||||||
|
RyujinxNative.instance.uiHandlerSetup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun listen() {
|
||||||
|
showMessage.value = false
|
||||||
|
while (shouldListen) {
|
||||||
|
RyujinxNative.instance.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.instance.uiHandlerStopWait()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun Compose() {
|
||||||
|
val showMessageListener = remember {
|
||||||
|
showMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
val inputListener = remember {
|
||||||
|
inputText
|
||||||
|
}
|
||||||
|
val validation = remember {
|
||||||
|
mutableStateOf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validate() : Boolean{
|
||||||
|
if(inputText.value.isEmpty()){
|
||||||
|
validation.value = "Must be between ${minLength} and ${maxLength} characters"
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return inputText.value.length < minLength || inputText.value.length > maxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInputType(): KeyboardType {
|
||||||
|
return when(mode){
|
||||||
|
KeyboardMode.Default -> KeyboardType.Text
|
||||||
|
KeyboardMode.Numeric -> KeyboardType.Decimal
|
||||||
|
KeyboardMode.ASCII -> KeyboardType.Ascii
|
||||||
|
else -> { KeyboardType.Text}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
showMessageListener.value = false
|
||||||
|
RyujinxNative.instance.uiHandlerSetResponse(true, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showMessageListener.value) {
|
||||||
|
AlertDialog(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
onDismissRequest = { },
|
||||||
|
properties = DialogProperties(dismissOnBackPress = false, false)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = title)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(128.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
RichText {
|
||||||
|
Markdown(content = message)
|
||||||
|
}
|
||||||
|
if (type == 2) {
|
||||||
|
validate()
|
||||||
|
if (watermark.isNotEmpty())
|
||||||
|
TextField(
|
||||||
|
value = inputListener.value,
|
||||||
|
onValueChange = { inputListener.value = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp),
|
||||||
|
label = {
|
||||||
|
Text(text = watermark)
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = getInputType()),
|
||||||
|
isError = validate()
|
||||||
|
)
|
||||||
|
else
|
||||||
|
TextField(
|
||||||
|
value = inputListener.value,
|
||||||
|
onValueChange = { inputListener.value = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(4.dp),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = getInputType(),
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
),
|
||||||
|
isError = validate(),
|
||||||
|
singleLine = true,
|
||||||
|
keyboardActions = KeyboardActions(onDone = { submit() })
|
||||||
|
)
|
||||||
|
if (subtitle.isNotEmpty())
|
||||||
|
Text(text = subtitle)
|
||||||
|
Text(text = validation.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
submit()
|
||||||
|
}) {
|
||||||
|
Text(text = "OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -285,6 +285,8 @@ class GameViews {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mainViewModel.activity.uiHandler.Compose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user