forked from MeloNX/MeloNX
android - add basic software keyboard support
This commit is contained in:
parent
316c0ffca2
commit
b5d48e8324
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Logging.Formatters;
|
||||
using Ryujinx.Common.Logging.Targets;
|
||||
using System;
|
||||
@ -12,7 +12,7 @@ namespace LibRyujinx
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public AndroidLogTarget( string name)
|
||||
public AndroidLogTarget(string name)
|
||||
{
|
||||
_name = name;
|
||||
_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);
|
||||
|
||||
[DllImport("libryujinxjni")]
|
||||
private extern static long storeString(string ch);
|
||||
[DllImport("libryujinxjni")]
|
||||
private extern static IntPtr getString(long id);
|
||||
internal extern static long storeString(string ch);
|
||||
|
||||
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);
|
||||
if (pointer != IntPtr.Zero)
|
||||
@ -91,7 +116,7 @@ namespace LibRyujinx
|
||||
|
||||
Logger.AddTarget(
|
||||
new AsyncLogTargetWrapper(
|
||||
new AndroidLogTarget("Ryujinx"),
|
||||
new AndroidLogTarget("RyujinxLog"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Block
|
||||
));
|
||||
@ -709,6 +734,30 @@ namespace LibRyujinx
|
||||
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")]
|
||||
public static void JniOpenUser(JEnvRef jEnv, JObjectLocalRef jObj, JLong userIdPtr)
|
||||
{
|
||||
|
@ -37,6 +37,8 @@ using System.Collections.Generic;
|
||||
using LibHac.Bcat;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Text;
|
||||
using Ryujinx.HLE.Ui;
|
||||
using LibRyujinx.Android;
|
||||
|
||||
namespace LibRyujinx
|
||||
{
|
||||
@ -89,7 +91,7 @@ namespace LibRyujinx
|
||||
Console.WriteLine(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
OpenALLibraryNameContainer.OverridePath = "libopenal.so";
|
||||
|
||||
Logger.Notice.Print(LogClass.Application, "RyujinxAndroid is ready!");
|
||||
@ -142,7 +144,9 @@ namespace LibRyujinx
|
||||
|
||||
var gameInfo = new GameInfo
|
||||
{
|
||||
FileSize = gameStream.Length * 0.000000000931, TitleName = "Unknown", TitleId = "0000000000000000",
|
||||
FileSize = gameStream.Length * 0.000000000931,
|
||||
TitleName = "Unknown",
|
||||
TitleId = "0000000000000000",
|
||||
Developer = "Unknown",
|
||||
Version = "0",
|
||||
Icon = null
|
||||
@ -629,11 +633,11 @@ namespace LibRyujinx
|
||||
|
||||
public static List<string> GetDlcContentList(string path, ulong titleId)
|
||||
{
|
||||
if(!File.Exists(path))
|
||||
if (!File.Exists(path))
|
||||
return new List<string>();
|
||||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
@ -665,6 +669,38 @@ namespace LibRyujinx
|
||||
|
||||
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
|
||||
@ -677,6 +713,7 @@ namespace LibRyujinx
|
||||
public UserChannelPersistence UserChannelPersistence { get; set; }
|
||||
public InputManager? InputManager { get; set; }
|
||||
public Switch? EmulationContext { get; set; }
|
||||
public IHostUiHandler? HostUiHandler { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -741,7 +778,7 @@ namespace LibRyujinx
|
||||
renderer,
|
||||
LibRyujinx.AudioDriver, //Audio
|
||||
MemoryConfiguration.MemoryConfiguration4GiB,
|
||||
null,
|
||||
HostUiHandler,
|
||||
systemLanguage,
|
||||
regionCode,
|
||||
enableVsync,
|
||||
|
@ -38,6 +38,36 @@
|
||||
|
||||
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;
|
||||
|
||||
@ -46,7 +76,7 @@ long _currentRenderingThreadId = 0;
|
||||
JavaVM* _vm = nullptr;
|
||||
jobject _mainActivity = nullptr;
|
||||
jclass _mainActivityClass = nullptr;
|
||||
std::string _currentString = "";
|
||||
string_helper str_helper = string_helper();
|
||||
UiHandler ui_handler = UiHandler();
|
||||
|
||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||
|
@ -327,6 +327,37 @@ const char* getString(long id){
|
||||
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) {
|
||||
@ -337,7 +368,7 @@ Java_org_ryujinx_android_NativeHelpers_storeStringJava(JNIEnv *env, jobject thiz
|
||||
extern "C"
|
||||
JNIEXPORT jstring JNICALL
|
||||
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"
|
||||
@ -346,3 +377,145 @@ 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;
|
||||
}
|
||||
|
@ -75,6 +75,8 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
_isInit = false
|
||||
_isStarted = false
|
||||
|
||||
mainViewModel.activity.uiHandler.stop()
|
||||
|
||||
_updateThread?.join()
|
||||
_renderingThreadWatcher?.join()
|
||||
}
|
||||
@ -161,6 +163,10 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
||||
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
||||
}
|
||||
}
|
||||
|
||||
thread {
|
||||
mainViewModel.activity.uiHandler.listen()
|
||||
}
|
||||
_nativeRyujinx.graphicsRendererRunLoop()
|
||||
|
||||
game?.close()
|
||||
|
@ -18,6 +18,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import com.anggrayudi.storage.SimpleStorageHelper
|
||||
import com.halilibo.richtext.ui.RichTextThemeIntegration
|
||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
||||
import org.ryujinx.android.viewmodels.MainViewModel
|
||||
import org.ryujinx.android.viewmodels.QuickSettings
|
||||
@ -32,6 +33,7 @@ class MainActivity : BaseActivity() {
|
||||
private var _isInit: Boolean = false
|
||||
var isGameRunning = false
|
||||
var storageHelper: SimpleStorageHelper? = null
|
||||
lateinit var uiHandler: UiHandler
|
||||
companion object {
|
||||
var mainViewModel: MainViewModel? = null
|
||||
var AppPath : String = ""
|
||||
@ -77,6 +79,8 @@ class MainActivity : BaseActivity() {
|
||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Guest.ordinal, quickSettings.enableGuestLogs)
|
||||
RyujinxNative.instance.loggingSetEnabled(LogLevel.Trace.ordinal, quickSettings.enableTraceLogs)
|
||||
val success = RyujinxNative.instance.initialize(NativeHelpers.instance.storeStringJava(appPath))
|
||||
|
||||
uiHandler = UiHandler()
|
||||
_isInit = success
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -107,12 +111,14 @@ class MainActivity : BaseActivity() {
|
||||
mainViewModel?.apply {
|
||||
setContent {
|
||||
RyujinxAndroidTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
MainView.Main(mainViewModel = this)
|
||||
RichTextThemeIntegration(contentColor = { MaterialTheme.colorScheme.onSurface }) {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
MainView.Main(mainViewModel = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,4 +32,13 @@ class NativeHelpers {
|
||||
external fun storeStringJava(string: String) : Long
|
||||
external fun getStringJava(id: 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
|
||||
}
|
||||
|
@ -74,4 +74,8 @@ class RyujinxNative {
|
||||
external fun deviceVerifyFirmware(fileDescriptor: Int, isXci: Boolean): Long
|
||||
external fun deviceInstallFirmware(fileDescriptor: Int, isXci: Boolean)
|
||||
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