commit 470b70cf1d7fd61983c6a3912ac7c3e7956e04b1
parent 14d516975857dae79a784c4662f67d403c57e256
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Tue,  3 Oct 2023 00:15:23 +0200

feat(scripting): interface builder

Diffstat:
Dapp/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteManagerIPC.kt | 54------------------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteScriptManager.kt | 16+++++++++++++++-
Aapp/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/RemoteManagerIPC.kt | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/InterfaceManager.kt | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/Node.kt | 42++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/NodeType.kt | 11+++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/impl/RowColumnNode.kt | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/scripting/ScriptsSection.kt | 181++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mcore/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/scripting/IPCInterface.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/scripting/JSModule.kt | 21++++++++++++++++++---
Mcore/src/main/kotlin/me/rhunk/snapenhance/scripting/ScriptRuntime.kt | 3++-
Mcore/src/main/kotlin/me/rhunk/snapenhance/scripting/core/CoreScriptRuntime.kt | 7++++---
13 files changed, 443 insertions(+), 80 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteManagerIPC.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteManagerIPC.kt @@ -1,53 +0,0 @@ -package me.rhunk.snapenhance.scripting - -import android.os.DeadObjectException -import me.rhunk.snapenhance.bridge.scripting.IPCListener -import me.rhunk.snapenhance.core.logger.AbstractLogger -import me.rhunk.snapenhance.scripting.type.ModuleInfo -import java.util.concurrent.ConcurrentHashMap - -typealias IPCListeners = ConcurrentHashMap<String, MutableMap<String, MutableSet<IPCListener>>> // channel, eventName -> listeners - -class RemoteManagerIPC( - private val moduleInfo: ModuleInfo, - private val logger: AbstractLogger, - private val ipcListeners: IPCListeners = ConcurrentHashMap(), -) : IPCInterface() { - companion object { - private const val TAG = "RemoteManagerIPC" - } - - override fun on(eventName: String, listener: Listener) { - onBroadcast(moduleInfo.name, eventName, listener) - } - - override fun emit(eventName: String, vararg args: String?) { - emit(moduleInfo.name, eventName, *args) - } - - override fun onBroadcast(channel: String, eventName: String, listener: Listener) { - ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(object: IPCListener.Stub() { - override fun onMessage(args: Array<out String?>) { - try { - listener(args) - } catch (doe: DeadObjectException) { - ipcListeners[channel]?.get(eventName)?.remove(this) - } catch (t: Throwable) { - logger.error("Failed to receive message for channel: $channel, event: $eventName", t, TAG) - } - } - }) - } - - override fun broadcast(channel: String, eventName: String, vararg args: String?) { - ipcListeners[channel]?.get(eventName)?.toList()?.forEach { - try { - it.onMessage(args) - } catch (doe: DeadObjectException) { - ipcListeners[channel]?.get(eventName)?.remove(it) - } catch (t: Throwable) { - logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG) - } - } - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteScriptManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/RemoteScriptManager.kt @@ -5,13 +5,18 @@ import androidx.documentfile.provider.DocumentFile import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.bridge.scripting.IPCListener import me.rhunk.snapenhance.bridge.scripting.IScripting +import me.rhunk.snapenhance.scripting.impl.IPCListeners +import me.rhunk.snapenhance.scripting.impl.ui.InterfaceBuilder +import me.rhunk.snapenhance.scripting.impl.ui.InterfaceManager +import me.rhunk.snapenhance.scripting.impl.RemoteManagerIPC import me.rhunk.snapenhance.scripting.type.ModuleInfo import java.io.InputStream class RemoteScriptManager( private val context: RemoteSideContext, ) : IScripting.Stub() { - val runtime = ScriptRuntime(context.log, context.androidContext.classLoader) + val runtime = ScriptRuntime(context.androidContext, context.log) + private val userInterfaces = mutableMapOf<String, MutableMap<String, InterfaceBuilder>>() private val cachedModuleInfo = mutableMapOf<String, ModuleInfo>() private val ipcListeners = IPCListeners() @@ -35,6 +40,11 @@ class RemoteScriptManager( fun init() { runtime.buildModuleObject = { module -> putConst("ipc", this, RemoteManagerIPC(module.moduleInfo, context.log, ipcListeners)) + putConst("im", this, InterfaceManager(module.moduleInfo, context.log) { name, interfaceBuilder -> + userInterfaces.getOrPut(module.moduleInfo.name) { + mutableMapOf() + }[name] = interfaceBuilder + }) } sync() @@ -47,6 +57,10 @@ class RemoteScriptManager( } } + fun getScriptInterface(scriptName: String, interfaceName: String) + = userInterfaces[scriptName]?.get(interfaceName) + + private fun <R> getScriptInputStream(name: String, callback: (InputStream?) -> R): R { val file = getScriptsFolder()?.findFile(name) ?: return callback(null) return context.androidContext.contentResolver.openInputStream(file.uri)?.use(callback) ?: callback(null) diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/RemoteManagerIPC.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/RemoteManagerIPC.kt @@ -0,0 +1,55 @@ +package me.rhunk.snapenhance.scripting.impl + +import android.os.DeadObjectException +import me.rhunk.snapenhance.bridge.scripting.IPCListener +import me.rhunk.snapenhance.core.logger.AbstractLogger +import me.rhunk.snapenhance.scripting.IPCInterface +import me.rhunk.snapenhance.scripting.Listener +import me.rhunk.snapenhance.scripting.type.ModuleInfo +import java.util.concurrent.ConcurrentHashMap + +typealias IPCListeners = ConcurrentHashMap<String, MutableMap<String, MutableSet<IPCListener>>> // channel, eventName -> listeners + +class RemoteManagerIPC( + private val moduleInfo: ModuleInfo, + private val logger: AbstractLogger, + private val ipcListeners: IPCListeners = ConcurrentHashMap(), +) : IPCInterface() { + companion object { + private const val TAG = "RemoteManagerIPC" + } + + override fun on(eventName: String, listener: Listener) { + onBroadcast(moduleInfo.name, eventName, listener) + } + + override fun emit(eventName: String, vararg args: String?) { + emit(moduleInfo.name, eventName, *args) + } + + override fun onBroadcast(channel: String, eventName: String, listener: Listener) { + ipcListeners.getOrPut(channel) { mutableMapOf() }.getOrPut(eventName) { mutableSetOf() }.add(object: IPCListener.Stub() { + override fun onMessage(args: Array<out String?>) { + try { + listener(args) + } catch (doe: DeadObjectException) { + ipcListeners[channel]?.get(eventName)?.remove(this) + } catch (t: Throwable) { + logger.error("Failed to receive message for channel: $channel, event: $eventName", t, TAG) + } + } + }) + } + + override fun broadcast(channel: String, eventName: String, vararg args: String?) { + ipcListeners[channel]?.get(eventName)?.toList()?.forEach { + try { + it.onMessage(args) + } catch (doe: DeadObjectException) { + ipcListeners[channel]?.get(eventName)?.remove(it) + } catch (t: Throwable) { + logger.error("Failed to send message for channel: $channel, event: $eventName", t, TAG) + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/InterfaceManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/InterfaceManager.kt @@ -0,0 +1,79 @@ +package me.rhunk.snapenhance.scripting.impl.ui + +import me.rhunk.snapenhance.core.logger.AbstractLogger +import me.rhunk.snapenhance.scripting.impl.ui.components.Node +import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType +import me.rhunk.snapenhance.scripting.impl.ui.components.impl.RowColumnNode +import me.rhunk.snapenhance.scripting.type.ModuleInfo +import org.mozilla.javascript.Context +import org.mozilla.javascript.Function +import org.mozilla.javascript.annotations.JSFunction + + +class InterfaceBuilder { + val nodes = mutableListOf<Node>() + var onLaunchedCallback: (() -> Unit)? = null + + + private fun createNode(type: NodeType, block: Node.() -> Unit): Node { + return Node(type).apply(block).also { nodes.add(it) } + } + + fun onLaunched(block: () -> Unit) { + onLaunchedCallback = block + } + + fun row(block: (InterfaceBuilder) -> Unit) = RowColumnNode(NodeType.ROW).apply { + children.addAll(InterfaceBuilder().apply(block).nodes) + }.also { nodes.add(it) } + + fun column(block: (InterfaceBuilder) -> Unit) = RowColumnNode(NodeType.COLUMN).apply { + children.addAll(InterfaceBuilder().apply(block).nodes) + }.also { nodes.add(it) } + + fun text(text: String) = createNode(NodeType.TEXT) { + label(text) + } + + fun switch(state: Boolean?, callback: (Boolean) -> Unit) = createNode(NodeType.SWITCH) { + attributes["state"] = state + attributes["callback"] = callback + } + + fun button(label: String, callback: () -> Unit) = createNode(NodeType.BUTTON) { + label(label) + attributes["callback"] = callback + } + + fun slider(min: Int, max: Int, step: Int, value: Int, callback: (Int) -> Unit) = createNode( + NodeType.SLIDER + ) { + attributes["value"] = value + attributes["min"] = min + attributes["max"] = max + attributes["step"] = step + attributes["callback"] = callback + } + + fun list(label: String, items: List<String>, callback: (String) -> Unit) = createNode(NodeType.LIST) { + label(label) + attributes["items"] = items + attributes["callback"] = callback + } +} + + + +class InterfaceManager( + private val moduleInfo: ModuleInfo, + private val logger: AbstractLogger, + private val registerInterface: (String, InterfaceBuilder) -> Unit, +) { + @JSFunction + fun create(name: String, callback: Function) { + logger.info("Creating interface for ${moduleInfo.name}") + val interfaceBuilder = InterfaceBuilder() + callback.call(Context.getCurrentContext(), callback, callback, arrayOf(interfaceBuilder)) + registerInterface(name, interfaceBuilder) + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/Node.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/Node.kt @@ -0,0 +1,42 @@ +package me.rhunk.snapenhance.scripting.impl.ui.components + +open class Node( + val type: NodeType, +) { + lateinit var uiChangeDetection: (key: String, value: Any?) -> Unit + + val children = mutableListOf<Node>() + val attributes = object: HashMap<String, Any?>() { + override fun put(key: String, value: Any?): Any? { + return super.put(key, value).also { + if (::uiChangeDetection.isInitialized) { + uiChangeDetection(key, value) + } + } + } + } + + fun setAttribute(key: String, value: Any?) { + attributes[key] = value + } + + fun label(text: String): Node { + attributes["label"] = text + return this + } + + fun padding(padding: Int): Node { + attributes["padding"] = padding + return this + } + + fun fontSize(size: Int): Node { + attributes["fontSize"] = size + return this + } + + fun color(color: Long): Node { + attributes["color"] = color + return this + } +} diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/NodeType.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/NodeType.kt @@ -0,0 +1,10 @@ +package me.rhunk.snapenhance.scripting.impl.ui.components +enum class NodeType { + ROW, + COLUMN, + TEXT, + SWITCH, + BUTTON, + SLIDER, + LIST +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/impl/RowColumnNode.kt b/app/src/main/kotlin/me/rhunk/snapenhance/scripting/impl/ui/components/impl/RowColumnNode.kt @@ -0,0 +1,47 @@ +package me.rhunk.snapenhance.scripting.impl.ui.components.impl + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.ui.Alignment +import me.rhunk.snapenhance.scripting.impl.ui.components.Node +import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType + + +class RowColumnNode( + type: NodeType, +) : Node(type) { + companion object { + private val arrangements = mapOf( + "start" to Arrangement.Start, + "end" to Arrangement.End, + "top" to Arrangement.Top, + "bottom" to Arrangement.Bottom, + "center" to Arrangement.Center, + "spaceBetween" to Arrangement.SpaceBetween, + "spaceAround" to Arrangement.SpaceAround, + "spaceEvenly" to Arrangement.SpaceEvenly, + ) + private val alignments = mapOf( + "start" to Alignment.Start, + "end" to Alignment.End, + "top" to Alignment.Top, + "bottom" to Alignment.Bottom, + "centerVertically" to Alignment.CenterVertically, + "centerHorizontally" to Alignment.CenterHorizontally, + ) + } + + fun arrangement(arrangement: String): RowColumnNode { + attributes["arrangement"] = arrangements[arrangement] ?: throw IllegalArgumentException("Invalid arrangement") + return this + } + + fun alignment(alignment: String): RowColumnNode { + attributes["alignment"] = alignments[alignment] ?: throw IllegalArgumentException("Invalid alignment") + return this + } + + fun spacedBy(spacing: Int): RowColumnNode { + attributes["spacing"] = spacing + return this + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/scripting/ScriptsSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/scripting/ScriptsSection.kt @@ -1,27 +1,23 @@ package me.rhunk.snapenhance.ui.manager.sections.scripting -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import kotlinx.coroutines.launch +import me.rhunk.snapenhance.scripting.impl.ui.components.Node +import me.rhunk.snapenhance.scripting.impl.ui.components.NodeType import me.rhunk.snapenhance.scripting.type.ModuleInfo import me.rhunk.snapenhance.ui.manager.Section +import kotlin.math.abs class ScriptsSection : Section() { @Composable @@ -29,16 +25,22 @@ class ScriptsSection : Section() { var enabled by remember { mutableStateOf(context.modDatabase.isScriptEnabled(script.name)) } + var openSettings by remember { + mutableStateOf(false) + } Card( modifier = Modifier .fillMaxWidth() - .padding(8.dp), + .padding(12.dp), elevation = CardDefaults.cardElevation() ) { Row( modifier = Modifier .fillMaxWidth() + .clickable { + openSettings = !openSettings + } .padding(8.dp), verticalAlignment = Alignment.CenterVertically ) { @@ -56,6 +58,14 @@ class ScriptsSection : Section() { fontSize = 14.sp, ) } + IconButton(onClick = { + openSettings = !openSettings + }) { + Icon( + imageVector = Icons.Default.Settings, + contentDescription = "Settings", + ) + } Switch( checked = enabled, onCheckedChange = { @@ -64,6 +74,145 @@ class ScriptsSection : Section() { } ) } + + + if (openSettings) { + ScriptSettings(script) + } + } + } + + @Composable + private fun DrawNode(node: Node) { + val coroutineScope = rememberCoroutineScope() + val cachedAttributes = remember { mutableStateMapOf(*node.attributes.toList().toTypedArray()) } + + node.uiChangeDetection = { key, value -> + coroutineScope.launch { + cachedAttributes[key] = value + } + } + + val padding = cachedAttributes["padding"]?.toString()?.toInt()?.let { abs(it) } ?: 2 + val arrangement = cachedAttributes["arrangement"] + val alignment = cachedAttributes["alignment"] + val spacing = cachedAttributes["spacing"]?.toString()?.toInt()?.let { abs(it) } + + fun runCallbackSafe(callback: () -> Unit) { + runCatching { + callback() + }.onFailure { + context.log.error("Error running callback", it) + } + } + + @Composable + fun NodeLabel() { + Text( + text = cachedAttributes["label"] as String, + fontSize = (cachedAttributes["fontSize"]?.toString()?.toInt() ?: 14).sp, + color = (cachedAttributes["color"] as? Long)?.let { Color(it) } ?: Color.Unspecified + ) + } + + when (node.type) { + NodeType.COLUMN -> { + Column( + verticalArrangement = arrangement as? Arrangement.Vertical ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.Top, + horizontalAlignment = alignment as? Alignment.Horizontal ?: Alignment.Start, + modifier = Modifier + .fillMaxWidth() + .padding(padding.dp) + ) { + node.children.forEach { child -> + DrawNode(child) + } + } + } + NodeType.ROW -> { + Row( + horizontalArrangement = arrangement as? Arrangement.Horizontal ?: spacing?.let { Arrangement.spacedBy(it.dp) } ?: Arrangement.SpaceBetween, + verticalAlignment = alignment as? Alignment.Vertical ?: Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(padding.dp) + ) { + node.children.forEach { child -> + DrawNode(child) + } + } + } + NodeType.TEXT -> NodeLabel() + NodeType.SWITCH -> { + var switchState by remember { + mutableStateOf(cachedAttributes["state"] as Boolean) + } + Switch( + checked = switchState, + onCheckedChange = { state -> + runCallbackSafe { + switchState = state + node.setAttribute("state", state) + (cachedAttributes["callback"] as? (Boolean) -> Unit)?.let { it(state) } + } + } + ) + } + NodeType.SLIDER -> { + var sliderValue by remember { + mutableFloatStateOf((cachedAttributes["value"] as Int).toFloat()) + } + Slider( + value = sliderValue, + onValueChange = { value -> + runCallbackSafe { + sliderValue = value + node.setAttribute("value", value.toInt()) + (cachedAttributes["callback"] as? (Int) -> Unit)?.let { it(value.toInt()) } + } + }, + valueRange = (cachedAttributes["min"] as Int).toFloat()..(cachedAttributes["max"] as Int).toFloat(), + steps = cachedAttributes["step"] as Int, + ) + } + NodeType.BUTTON -> { + OutlinedButton(onClick = { + runCallbackSafe { + (cachedAttributes["callback"] as? () -> Unit)?.let { it() } + } + }) { + NodeLabel() + } + } + else -> {} + } + } + + @Composable + fun ScriptSettings(script: ModuleInfo) { + val settingsInterface = remember { + context.scriptManager.getScriptInterface(script.name, "settings") + } ?: run { + Text( + text = "This module does not have any settings", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(8.dp) + ) + return + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + settingsInterface.nodes.forEach { node -> + DrawNode(node) + } + + LaunchedEffect(settingsInterface) { + settingsInterface.onLaunchedCallback?.invoke() + } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt b/core/src/main/kotlin/me/rhunk/snapenhance/ModContext.kt @@ -58,7 +58,7 @@ class ModContext { val event = EventBus(this) val eventDispatcher = EventDispatcher(this) val native = NativeLib() - val scriptRuntime by lazy { CoreScriptRuntime(log, androidContext.classLoader) } + val scriptRuntime by lazy { CoreScriptRuntime(androidContext, log) } val isDeveloper by lazy { config.scripting.developerMode.get() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/scripting/IPCInterface.kt b/core/src/main/kotlin/me/rhunk/snapenhance/scripting/IPCInterface.kt @@ -13,5 +13,5 @@ abstract class IPCInterface { @Suppress("unused") fun emit(eventName: String) = emit(eventName, *emptyArray()) @Suppress("unused") - fun emit(channel: String, eventName: String) = broadcast(channel, eventName, *emptyArray()) + fun emit(channel: String, eventName: String) = broadcast(channel, eventName) } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/scripting/JSModule.kt b/core/src/main/kotlin/me/rhunk/snapenhance/scripting/JSModule.kt @@ -1,6 +1,7 @@ package me.rhunk.snapenhance.scripting -import me.rhunk.snapenhance.core.logger.AbstractLogger +import android.os.Handler +import android.widget.Toast import me.rhunk.snapenhance.scripting.ktx.contextScope import me.rhunk.snapenhance.scripting.ktx.putFunction import me.rhunk.snapenhance.scripting.ktx.scriptableObject @@ -20,6 +21,7 @@ class JSModule( fun load(block: ScriptableObject.() -> Unit) { contextScope { + val classLoader = scriptRuntime.androidContext.classLoader moduleObject = initSafeStandardObjects() moduleObject.putConst("module", moduleObject, scriptableObject { putConst("info", this, scriptableObject { @@ -53,12 +55,12 @@ class JSModule( moduleObject.putFunction("findClass") { val className = it?.get(0).toString() - scriptRuntime.classLoader.loadClass(className) + classLoader.loadClass(className) } moduleObject.putFunction("type") { args -> val className = args?.get(0).toString() - val clazz = scriptRuntime.classLoader.loadClass(className) + val clazz = classLoader.loadClass(className) scriptableObject("JavaClassWrapper") { putFunction("newInstance") newInstance@{ args -> @@ -90,6 +92,19 @@ class JSModule( Undefined.instance } + for (toastFunc in listOf("longToast", "shortToast")) { + moduleObject.putFunction(toastFunc) { args -> + Handler(scriptRuntime.androidContext.mainLooper).post { + Toast.makeText( + scriptRuntime.androidContext, + args?.joinToString(" ") ?: "", + if (toastFunc == "longToast") Toast.LENGTH_LONG else Toast.LENGTH_SHORT + ).show() + } + Undefined.instance + } + } + block(moduleObject) evaluateString(moduleObject, content, moduleInfo.name, 1, null) } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/scripting/ScriptRuntime.kt b/core/src/main/kotlin/me/rhunk/snapenhance/scripting/ScriptRuntime.kt @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.scripting +import android.content.Context import me.rhunk.snapenhance.core.logger.AbstractLogger import me.rhunk.snapenhance.scripting.type.ModuleInfo import org.mozilla.javascript.ScriptableObject @@ -8,8 +9,8 @@ import java.io.ByteArrayInputStream import java.io.InputStream open class ScriptRuntime( + val androidContext: Context, val logger: AbstractLogger, - val classLoader: ClassLoader, ) { var buildModuleObject: ScriptableObject.(JSModule) -> Unit = {} private val modules = mutableMapOf<String, JSModule>() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/scripting/core/CoreScriptRuntime.kt b/core/src/main/kotlin/me/rhunk/snapenhance/scripting/core/CoreScriptRuntime.kt @@ -1,5 +1,6 @@ package me.rhunk.snapenhance.scripting.core +import android.content.Context import me.rhunk.snapenhance.bridge.scripting.IPCListener import me.rhunk.snapenhance.bridge.scripting.IScripting import me.rhunk.snapenhance.core.logger.AbstractLogger @@ -9,9 +10,9 @@ import me.rhunk.snapenhance.scripting.ScriptRuntime import me.rhunk.snapenhance.scripting.core.impl.ScriptHooker class CoreScriptRuntime( + androidContext: Context, logger: AbstractLogger, - classLoader: ClassLoader, -): ScriptRuntime(logger, classLoader) { +): ScriptRuntime(androidContext, logger) { private val scriptHookers = mutableListOf<ScriptHooker>() fun connect(scriptingInterface: IScripting) { @@ -38,7 +39,7 @@ class CoreScriptRuntime( sendIPCMessage(channel, eventName, args) } }) - putConst("hooker", this, ScriptHooker(module.moduleInfo, logger, classLoader).also { + putConst("hooker", this, ScriptHooker(module.moduleInfo, logger, androidContext.classLoader).also { scriptHookers.add(it) }) }