diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 27637415d..bc876e0d9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -202,8 +202,6 @@
android:name="org.autojs.autojs.ui.error.IssueReporterActivity"
android:theme="@style/IssueReporterTheme" />
-
-
+
+
+ <${factory.Dialog/**对话框的挂载点 */} />
+
+ `
+ }
+})
+
+startActivity(app)
\ No newline at end of file
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/AppBar.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/AppBar.mjs"
index b65c2697b..a3d836894 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/AppBar.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/AppBar.mjs"
@@ -12,7 +12,7 @@ const TopAppBar = defineComponent({
-
+
@@ -31,17 +31,17 @@ const BottomAppBar = defineComponent({
-
+
-
+
-
+
-
+
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/BottomSheet.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/BottomSheet.mjs"
index 1177b765d..b3932b299 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/BottomSheet.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/BottomSheet.mjs"
@@ -31,7 +31,7 @@ const BottomSheetScaffold = defineComponent({
-
+
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Chip.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Chip.mjs"
index 441f39d92..b8c085829 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Chip.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Chip.mjs"
@@ -13,17 +13,17 @@ const app = createApp({
render() {
return xml`
-
+
+ leadingIcon=${selected.value ? Done : null} label="Filter" />
+ avatar=${Person} label="Input" />
-
@@ -31,13 +31,13 @@ const app = createApp({
Input
-
+
-
+
-
+
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Tabs.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Tabs.mjs"
index b6889dfa9..1ac0a1f62 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Tabs.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/Tabs.mjs"
@@ -8,15 +8,15 @@ const { Person, Star, Home, Close, Settings } = Icons.Default
const modifier = [padding(0, 5)]
const tabs = [{
text: "Settings",
- icon: Settings(),
+ icon: Settings,
enabled: true,
}, {
text: "Home",
- icon: Home(),
+ icon: Home,
enabled: false
}, {
text: "Star",
- icon: Star(),
+ icon: Star,
enabled: true,
}
]
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\233\276\347\211\207\344\270\216Icon.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\233\276\347\211\207\344\270\216Icon.mjs"
index 13e109a2b..966141276 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\233\276\347\211\207\344\270\216Icon.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\233\276\347\211\207\344\270\216Icon.mjs"
@@ -14,9 +14,9 @@ const app = createApp({
return xml`
Icon
-
+
带颜色的Icon
-
+
支持@drawer/ 中的资源
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\274\271\345\207\272\350\217\234\345\215\225.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\274\271\345\207\272\350\217\234\345\215\225.mjs"
index 3b5b27191..ef756e25c 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\274\271\345\207\272\350\217\234\345\215\225.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\345\274\271\345\207\272\350\217\234\345\215\225.mjs"
@@ -71,7 +71,7 @@ const M2 = defineComponent({
readOnly=${true}
singleLine=${true} >
-
+
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\214\211\351\222\256.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\214\211\351\222\256.mjs"
index 64b987af4..dbd1c2272 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\214\211\351\222\256.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\214\211\351\222\256.mjs"
@@ -63,7 +63,7 @@ const app = createApp({
-
+
切换按钮
<${G}/>
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\265\256\345\212\250\346\214\211\351\222\256.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\265\256\345\212\250\346\214\211\351\222\256.mjs"
index ef56f5981..73ae1d906 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\265\256\345\212\250\346\214\211\351\222\256.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\346\265\256\345\212\250\346\214\211\351\222\256.mjs"
@@ -12,17 +12,17 @@ const app = createApp({
return xml`
-
+
-
+
-
+
-
-
+
+
-
+
Extended Fab2
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\347\225\214\351\235\242\346\250\241\346\235\277.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\347\225\214\351\235\242\346\250\241\346\235\277.mjs"
index f81559e8b..4e6370df3 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\347\225\214\351\235\242\346\250\241\346\235\277.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\347\225\214\351\235\242\346\250\241\346\235\277.mjs"
@@ -61,7 +61,7 @@ const TopAppBar = defineComponent({
{ drawer.open() }}>
-
+
@@ -106,9 +106,9 @@ let app = createApp({
- - cu.value = 0} icon=${Home()}>主页
- - cu.value = 1} icon=${Home()}>管理
- - 文档
+ - cu.value = 0} icon=${Home}>主页
+ - cu.value = 1} icon=${Home}>管理
+ - 文档
diff --git "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\350\276\223\345\205\245\346\241\206.mjs" "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\350\276\223\345\205\245\346\241\206.mjs"
index d75c91587..6a3ebf90e 100644
--- "a/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\350\276\223\345\205\245\346\241\206.mjs"
+++ "b/app/src/main/assets/sample/v7/\347\224\250\346\210\267\347\225\214\351\235\242ui/\350\276\223\345\205\245\346\241\206.mjs"
@@ -36,8 +36,8 @@ const app = createApp({
只读和图标
-
-
+
+
`
diff --git "a/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-nodejs.mjs" "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-nodejs.mjs"
new file mode 100644
index 000000000..261828df5
--- /dev/null
+++ "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-nodejs.mjs"
@@ -0,0 +1,5 @@
+import { getRunningEngines } from 'engines'
+
+getRunningEngines().forEach(engine => {
+ engine.emit('test', 789012)
+})
\ No newline at end of file
diff --git "a/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-rhino.js" "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-rhino.js"
new file mode 100644
index 000000000..ebe545b01
--- /dev/null
+++ "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\345\217\221\351\200\201\344\272\213\344\273\266-rhino.js"
@@ -0,0 +1,4 @@
+
+engines.all().forEach(e => {
+ e.emit('test', 123456)
+});
diff --git "a/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266- rhino.js" "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266- rhino.js"
new file mode 100644
index 000000000..84028d879
--- /dev/null
+++ "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266- rhino.js"
@@ -0,0 +1,6 @@
+events.on('test', (data) => {
+ toast(`收到test事件, data:${data}`)
+})
+
+//保持脚本运行
+setInterval(() => { }, 1000)
\ No newline at end of file
diff --git "a/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266-nodejs.mjs" "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266-nodejs.mjs"
new file mode 100644
index 000000000..57e617c15
--- /dev/null
+++ "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\347\233\221\345\220\254\345\274\225\346\223\216\344\272\213\344\273\266-nodejs.mjs"
@@ -0,0 +1,11 @@
+import { selfEngine } from 'engines'
+import { showToast } from 'toast'
+
+// console.log(myEngine());
+
+selfEngine.on('test', (data) => {
+ showToast(`收到test事件, data:${data}`)
+})
+
+//保持脚本运行
+setInterval(() => { }, 1000)
\ No newline at end of file
diff --git "a/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\350\277\220\350\241\214\350\204\232\346\234\254.mjs" "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\350\277\220\350\241\214\350\204\232\346\234\254.mjs"
new file mode 100644
index 000000000..bebeddc17
--- /dev/null
+++ "b/app/src/main/assets/sample/v7/\350\204\232\346\234\254\345\274\225\346\223\216/\350\277\220\350\241\214\350\204\232\346\234\254.mjs"
@@ -0,0 +1,26 @@
+import { execScriptFile, stopAll } from 'engines'
+import { writeFile, rm } from 'fs/promises'
+import { showToast } from 'toast'
+
+
+const file = process.cwd() + '/test.js'
+await writeFile(file, `
+ setTimeout(() => { }, 2000)
+ `, "utf8")
+console.log(process.cwd());
+
+execScriptFile(file, {
+ onStart() {
+ showToast('开始执行')
+ },
+ onSuccess() {
+ showToast('执行成功')
+ rm(file, { force: true })
+ },
+ onException() {
+ showToast('执行出错')
+ rm(file, { force: true })
+ }
+})
+
+setTimeout(() => { }, 5000)
\ No newline at end of file
diff --git a/app/src/main/java/org/autojs/autojs/external/foreground/ForegroundService.java b/app/src/main/java/org/autojs/autojs/external/foreground/ForegroundService.java
deleted file mode 100644
index db2ad7496..000000000
--- a/app/src/main/java/org/autojs/autojs/external/foreground/ForegroundService.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package org.autojs.autojs.external.foreground;
-
-import static android.app.PendingIntent.FLAG_IMMUTABLE;
-import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.IBinder;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.app.NotificationCompat;
-
-import org.autojs.autoxjs.R;
-import org.autojs.autojs.ui.main.MainActivity;
-
-public class ForegroundService extends Service {
-
-
- private static final int NOTIFICATION_ID = 1;
- private static final String CHANEL_ID = ForegroundService.class.getName() + ".foreground";
-
- public static void start(Context context) {
- context.startForegroundService(new Intent(context, ForegroundService.class));
- }
-
- public static void stop(Context context) {
- context.stopService(new Intent(context, ForegroundService.class));
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- startForeground();
- }
-
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- private void startForeground() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- startForeground(FOREGROUND_SERVICE_TYPE_SPECIAL_USE, buildNotification());
- } else {
- startForeground(NOTIFICATION_ID, buildNotification());
- }
- }
-
- private Notification buildNotification() {
- createNotificationChannel();
- // PendingIntent contentIntent = PendingIntent.getActivity(this, 0, MainActivity_.intent(this).get(), 0);
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), FLAG_IMMUTABLE);
- return new NotificationCompat.Builder(this, CHANEL_ID)
- .setContentTitle(getString(R.string.foreground_notification_title))
- .setContentText(getString(R.string.foreground_notification_text))
- .setSmallIcon(R.drawable.autojs_logo)
- .setWhen(System.currentTimeMillis())
- .setContentIntent(contentIntent)
- .setChannelId(CHANEL_ID)
- .setVibrate(new long[0])
- .build();
- }
-
- private void createNotificationChannel() {
- NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- assert manager != null;
- CharSequence name = getString(R.string.foreground_notification_channel_name);
- String description = getString(R.string.foreground_notification_channel_name);
- NotificationChannel channel = new NotificationChannel(CHANEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT);
- channel.setDescription(description);
- channel.enableLights(false);
- manager.createNotificationChannel(channel);
- }
-
- @Override
- public void onDestroy() {
- stopForeground(true);
- super.onDestroy();
- }
-}
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt
index eb70e9f45..0e4a38a7a 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt
+++ b/app/src/main/java/org/autojs/autojs/ui/main/MainActivity.kt
@@ -77,7 +77,6 @@ import com.stardust.autojs.servicecomponents.EngineController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.autojs.autojs.Pref
-import org.autojs.autojs.external.foreground.ForegroundService
import org.autojs.autojs.timing.TimedTaskScheduler
import org.autojs.autojs.ui.build.ProjectConfigActivity
import org.autojs.autojs.ui.common.ScriptOperations
@@ -117,8 +116,6 @@ class MainActivity : FragmentActivity() {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
Log.i("MainActivity", "Pid: ${Process.myPid()}")
- if (Pref.isForegroundServiceEnabled()) ForegroundService.start(this)
- else ForegroundService.stop(this)
if (Pref.isFloatingMenuShown()) {
if (DrawOverlaysPermission.isCanDrawOverlays(this)) FloatyWindowManger.showCircularMenu()
diff --git a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerPage.kt b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerPage.kt
index ea8ff10ec..652e8fb4c 100644
--- a/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerPage.kt
+++ b/app/src/main/java/org/autojs/autojs/ui/main/drawer/DrawerPage.kt
@@ -60,6 +60,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
+import androidx.core.content.edit
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.preference.PreferenceManager
import coil.compose.rememberAsyncImagePainter
@@ -68,6 +69,8 @@ import com.stardust.app.isOpPermissionGranted
import com.stardust.app.permission.DrawOverlaysPermission
import com.stardust.app.permission.DrawOverlaysPermission.launchCanDrawOverlaysSettings
import com.stardust.app.permission.PermissionsSettingsUtil
+import com.stardust.autojs.IndependentScriptService
+import com.stardust.autojs.core.pref.PrefKey
import com.stardust.enhancedfloaty.FloatyService
import com.stardust.notification.NotificationListenerService
import com.stardust.toast
@@ -84,7 +87,6 @@ import kotlinx.coroutines.withContext
import org.autojs.autojs.Pref
import org.autojs.autojs.autojs.AutoJs
import org.autojs.autojs.devplugin.DevPlugin
-import org.autojs.autojs.external.foreground.ForegroundService
import org.autojs.autojs.tool.AccessibilityServiceTool
import org.autojs.autojs.tool.WifiTool
import org.autojs.autojs.ui.build.MyTextField
@@ -341,7 +343,6 @@ private fun BottomButtons() {
fun exitCompletely(context: Context) {
if (context is Activity) context.finish()
FloatyWindowManger.hideCircularMenu()
- ForegroundService.stop(context)
context.stopService(Intent(context, FloatyService::class.java))
AutoJs.getInstance().scriptEngineService.stopAll()
}
@@ -418,6 +419,7 @@ private fun ConnectComputerSwitch() {
).show()
}
}
+
QRResult.QRUserCanceled -> {}
QRResult.QRMissingPermission -> {}
is QRResult.QRError -> {}
@@ -602,10 +604,10 @@ private fun FloatingWindowSwitch() {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
- if (DrawOverlaysPermission.isCanDrawOverlays(context)){
+ if (DrawOverlaysPermission.isCanDrawOverlays(context)) {
FloatyWindowManger.showCircularMenu()
isFloatingWindowShowing = true
- }else isFloatingWindowShowing = false
+ } else isFloatingWindowShowing = false
}
)
SwitchItem(
@@ -693,8 +695,8 @@ private fun UsageStatsPermissionSwitch() {
private fun ForegroundServiceSwitch() {
val context = LocalContext.current
var isOpenForegroundServices by remember {
- val default = PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(context.getString(R.string.key_foreground_servie), false)
+ val default = com.stardust.autojs.core.pref.Pref.getDefault(context)
+ .getBoolean(PrefKey.KEY_FOREGROUND_SERVIE, false)
mutableStateOf(default)
}
SwitchItem(
@@ -707,15 +709,12 @@ private fun ForegroundServiceSwitch() {
text = { Text(text = stringResource(id = R.string.text_foreground_service)) },
checked = isOpenForegroundServices,
onCheckedChange = {
- if (it) {
- ForegroundService.start(context)
- } else {
- ForegroundService.stop(context)
+ com.stardust.autojs.core.pref.Pref.getDefault(context).edit(true) {
+ putBoolean(PrefKey.KEY_FOREGROUND_SERVIE, it)
}
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putBoolean(context.getString(R.string.key_foreground_servie), it)
- .apply()
+ if (it) {
+ IndependentScriptService.startForeground(context)
+ } else IndependentScriptService.stopForeground(context)
isOpenForegroundServices = it
}
)
diff --git a/autojs/src/js-api/gulpfile.mjs b/autojs/src/js-api/gulpfile.mjs
index cf2a2612d..de584ba88 100644
--- a/autojs/src/js-api/gulpfile.mjs
+++ b/autojs/src/js-api/gulpfile.mjs
@@ -26,6 +26,17 @@ export async function createPackageFile(cb) {
)
cb()
}
+async function createRootPackageFile(cb) {
+ const n = JSON.parse(await fs.readFile('./package.json', 'utf8'))
+ const packageFile = {
+ name: n.name,
+ version: n.version,
+ description: n.description,
+ type: "module",
+ license: n.license,
+ }
+ await fs.writeFile('./dist/package.json', JSON.stringify(packageFile, undefined, 2))
+}
export const build = series(
clear,
@@ -37,5 +48,6 @@ export const build = series(
await Promise.all(option.output.map(bundle.write))
}
},
- createPackageFile
+ createPackageFile,
+ createRootPackageFile,
)
\ No newline at end of file
diff --git a/autojs/src/js-api/package-lock.json b/autojs/src/js-api/package-lock.json
index 1776f9841..6d2e393fe 100644
--- a/autojs/src/js-api/package-lock.json
+++ b/autojs/src/js-api/package-lock.json
@@ -14,6 +14,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-typescript": "^11.1.6",
+ "@types/lodash": "^4.17.7",
"@vue/runtime-core": "^3.4.31",
"axios": "^1.7.2",
"gulp": "^5.0.0",
@@ -496,6 +497,13 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.7",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
+ "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "20.14.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz",
diff --git a/autojs/src/js-api/package.json b/autojs/src/js-api/package.json
index b8a20df44..77f1e80f1 100644
--- a/autojs/src/js-api/package.json
+++ b/autojs/src/js-api/package.json
@@ -16,6 +16,7 @@
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.7",
"@rollup/plugin-typescript": "^11.1.6",
+ "@types/lodash": "^4.17.7",
"@vue/runtime-core": "^3.4.31",
"axios": "^1.7.2",
"gulp": "^5.0.0",
diff --git a/autojs/src/js-api/src/axios/index.ts b/autojs/src/js-api/src/axios/index.ts
index 8cd674d98..0e126d536 100644
--- a/autojs/src/js-api/src/axios/index.ts
+++ b/autojs/src/js-api/src/axios/index.ts
@@ -3,7 +3,7 @@
* 作用于node.js和浏览器中。
* 它提供了http请求的方便封装。
*
- * (官方中文文档)[https://axios-http.com/zh/docs/intro]
+ * [官方中文文档](https://axios-http.com/zh/docs/intro)
* @packageDocumentation
*/
export { default } from 'axios'
diff --git a/autojs/src/js-api/src/clip_manager/index.ts b/autojs/src/js-api/src/clip_manager/index.ts
index 3ddf3f724..08e8218fe 100644
--- a/autojs/src/js-api/src/clip_manager/index.ts
+++ b/autojs/src/js-api/src/clip_manager/index.ts
@@ -8,9 +8,11 @@ const clipManager = Autox.clipManager
/**
* 此对象是一个EventEmitter,用于监听剪贴板变化
* @template
+ * ```js
* clipboardManager.on('clip_changed',()=>{
* getClip()
* })
+ * ```
*/
export const clipboardManager = new EventEmitter()
clipManager.registerListener({
diff --git a/autojs/src/js-api/src/dialogs/DialogFactory.ts b/autojs/src/js-api/src/dialogs/DialogFactory.ts
new file mode 100644
index 000000000..3338c5f7e
--- /dev/null
+++ b/autojs/src/js-api/src/dialogs/DialogFactory.ts
@@ -0,0 +1,111 @@
+import { xml } from "@/vue-ui"
+import { shallowReactive, Component, defineComponent, reactive, watch } from "@vue/runtime-core"
+import { DialogEvent, DialogInterface } from "."
+import * as main from '.'
+import { DialogBuilderOptions, IDialogs, InputDialogOptions } from "./options"
+import { EventEmitter } from 'node:events'
+
+type DialogStatus = DialogOps & {
+ isShow: boolean
+}
+
+class MutxDialog extends EventEmitter> implements DialogInterface {
+ state: DialogStatus
+ constructor(state: DialogStatus) {
+ super()
+ this.state = state
+ }
+ dismiss(): void {
+ this.state.isShow = false
+ }
+}
+
+export default class DialogFactory implements IDialogs {
+ showin = shallowReactive(new Set<[Component, DialogStatus]>())
+
+ get Dialog(): Component {
+ const showin = this.showin
+ return defineComponent({
+ render() {
+ const nodes = Array.from(showin).map((t) => {
+ const [comp, ops] = t
+ if (!ops.isShow) {
+ showin.delete(t)
+ return
+ }
+ return xml`
+
+ `
+ })
+ return xml`
+ ${nodes}
+ `
+ }
+ })
+ }
+
+ _mountUi(comp: Component, ops: DialogOps): DialogInterface {
+ const state: DialogStatus = reactive({
+ ...ops,
+ isShow: true
+ })
+ const p = watch(() => state.isShow, (isShow) => {
+ if (isShow === false) {
+ ops.onDismiss();
+ p()
+ }
+ })
+ this.showin.add([comp, state])
+ return new MutxDialog(state)
+ }
+ showDialog(options: DialogBuilderOptions): DialogInterface {
+ options.type = this
+ return main.showDialog({
+ ...options,
+ type: this,
+ })
+ }
+ showAlertDialog(title: string, options: DialogBuilderOptions): Promise {
+ return main.showAlertDialog(title, {
+ ...options,
+ type: this,
+ })
+ }
+ showConfirmDialog(title: string, options: DialogBuilderOptions): Promise {
+ return main.showConfirmDialog(title, {
+ ...options,
+ type: this,
+ })
+ }
+ showInputDialog(title: string, prefill?: string, options?: InputDialogOptions): Promise {
+ return main.showInputDialog(title, prefill, {
+ ...options,
+ type: this,
+ })
+ }
+ showSelectDialog(title: string, items: string[], options?: DialogBuilderOptions): Promise {
+ return main.showSelectDialog(title, items, {
+ ...options,
+ type: this,
+ })
+ }
+ showMultiChoiceDialog(title: string, items: string[], initialSelectedIndices?: number[], options?: DialogBuilderOptions): Promise {
+ return main.showMultiChoiceDialog(title, items, initialSelectedIndices, {
+ ...options,
+ type: this,
+ })
+ }
+ showSingleChoiceDialog(title: string, items: string[], initialSelectedIndex?: number, options?: DialogBuilderOptions): Promise {
+ return main.showSingleChoiceDialog(title, items, initialSelectedIndex, {
+ ...options,
+ type: this,
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/autojs/src/js-api/src/dialogs/dialogs.d.ts b/autojs/src/js-api/src/dialogs/dialogs.d.ts
new file mode 100644
index 000000000..c26b3114e
--- /dev/null
+++ b/autojs/src/js-api/src/dialogs/dialogs.d.ts
@@ -0,0 +1,14 @@
+declare type TSecureTpye = 'SecureOn' | 'SecureOff' | 'Inherit'
+
+declare type DialogOps = {
+ dismissOnBackPress: boolean
+ dismissOnClickOutside: boolean
+ securePolicy?: TSecureTpye
+ onDismiss: () => void
+}
+declare type AppDialogBuilder = {
+ dismiss(): void
+}
+declare namespace root.dialogs {
+ function showDialog(element: ComposeElement, ops: DialogOps?): AppDialogBuilder
+}
\ No newline at end of file
diff --git a/autojs/src/js-api/src/dialogs/index.ts b/autojs/src/js-api/src/dialogs/index.ts
new file mode 100644
index 000000000..ec0b6daa7
--- /dev/null
+++ b/autojs/src/js-api/src/dialogs/index.ts
@@ -0,0 +1,329 @@
+
+import { EventEmitter } from 'node:events'
+import { xml, Component, ModifierExtension, defineComponent, createApp, ref, reactive } from '@/vue-ui'
+import { nodeOps } from '@/vue-ui/nodeOps'
+import { createDialogContent, DialogBuilderOptions, DialogEventListener, InputDialogOptions } from './options'
+import { padding, fillMaxWidth, height, heightIn, clickable } from '@/vue-ui/modifierExtension'
+import DialogFactory from './DialogFactory'
+
+const dialogs = Autox.dialogs
+
+export type DialogType = 'app' | 'overlay' | DialogFactory
+export const defaultDialogType: DialogType = 'app'
+
+
+export function showAppDialog(comp: Component, ops?: DialogOps) {
+ const el = nodeOps.createElement('box')
+ const app = createApp(comp)
+ app.mount(el)
+ const s = setInterval(() => { }, 2000)
+ return dialogs.showDialog(el.__xel, Object.assign({
+ dismissOnBackPress: true
+ }, ops, {
+ onDismiss() {
+ app.unmount()
+ clearInterval(s)
+ ops?.onDismiss()
+ }
+ }))
+}
+
+export enum DialogEvent {
+ /**@event */
+ ON_DISMISS = 'dismiss',
+ /**@event */
+ ON_POSITIVE = 'positive',
+ /**@event */
+ ON_NEGATIVE = 'negative',
+ /**@event */
+ ON_NEUTRAL = 'neutral',
+ /**@event */
+ ON_INPUT_CHANGE = 'input_change',
+}
+export interface DialogInterface extends EventEmitter> {
+ dismiss(): void
+}
+class Dialog extends EventEmitter>
+ implements DialogInterface {
+ _nv?: AppDialogBuilder
+ destroyed: boolean = false
+ constructor() {
+ super()
+ this.once(DialogEvent.ON_DISMISS, () => { this.destroyed = true; })
+ }
+ dismiss() {
+ this._nv?.dismiss()
+ }
+}
+export function showDialog(options: DialogBuilderOptions): DialogInterface {
+ const { type = defaultDialogType, dismissOnBackPress, dismissOnClickOutside } = options
+ const Content = createDialogContent(options)
+ let dialog: DialogInterface
+ const dialogEventListener: DialogEventListener = {
+ onPositive() {
+ dialog.emit(DialogEvent.ON_POSITIVE, dialog)
+ },
+ onNegative() {
+ dialog.emit(DialogEvent.ON_NEGATIVE, dialog)
+ },
+ onNeutral() {
+ dialog.emit(DialogEvent.ON_NEUTRAL, dialog)
+ },
+ }
+ const comp = () => {
+ return xml`
+ <${Content} events=${dialogEventListener}/>
+ `
+ }
+ const ops: DialogOps = {
+ dismissOnBackPress: (typeof dismissOnBackPress === 'boolean') ? dismissOnBackPress : true,
+ dismissOnClickOutside: (typeof dismissOnClickOutside === 'boolean') ? dismissOnClickOutside : true,
+ onDismiss() {
+ dialog.emit(DialogEvent.ON_DISMISS)
+ },
+ }
+
+ if (type === 'app') {
+ dialog = new Dialog();
+ (dialog as Dialog)._nv = showAppDialog(comp, ops)
+ } else if (type instanceof DialogFactory) {
+ dialog = type._mountUi(comp, ops)
+ } else {
+ dialog = new Dialog();
+ console.warn('Unknown Dialog type: ' + type);
+ }
+ return dialog
+}
+/**
+ * 显示一个消息提示对话框,返回一个Promise
+ * @param title
+ * @param options
+ * @returns Promise将在对话框消失时完成
+ */
+export async function showAlertDialog(title: string, options?: DialogBuilderOptions) {
+ const f: DialogBuilderOptions = {
+ title: title,
+ positive: '确认',
+ }
+ const dialog = showDialog(Object.assign(f, options))
+
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, resolve)
+ dialog.once(DialogEvent.ON_POSITIVE, () => { dialog.dismiss() })
+ })
+}
+/**
+ * 显示一个确认对话框
+ * @param title
+ * @param options
+ * @returns 只在点击positive按钮时返回true,其他情况返回false
+ */
+export async function showConfirmDialog(title: string, options?: DialogBuilderOptions) {
+ const f: DialogBuilderOptions = {
+ title: title,
+ positive: '确认',
+ negative: '取消',
+ }
+ const dialog = showDialog(Object.assign(f, options))
+ let r = false
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, () => resolve(r))
+ dialog.once(DialogEvent.ON_POSITIVE, () => {
+ r = true
+ dialog.dismiss()
+ })
+ dialog.once(DialogEvent.ON_NEGATIVE, () => { dialog.dismiss() })
+ })
+}
+/**
+ * 显示一个输入框,提示用户输入信息
+ * @param title
+ * @param prefill 输入框的默认内容
+ * @param options
+ * @returns 点击positive时返回字符串,即使输入为空,被取消时返回null
+ */
+export async function showInputDialog(title: string, prefill?: string, options?: InputDialogOptions) {
+ let input = prefill || ""
+ const DialogContent = defineComponent(function () {
+ function updateInput(value: string) {
+ input = value
+ dialog.emit(DialogEvent.ON_INPUT_CHANGE, value, dialog)
+ }
+ return function render() {
+ return xml`
+
+ `
+ }
+ })
+
+ const f: InputDialogOptions = {
+ title: title,
+ inputPrefill: prefill,
+ positive: '确认',
+ negative: '取消',
+ }
+ const dialog = showDialog(Object.assign(f, options, { content: DialogContent, }))
+
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, () => resolve(null))
+ dialog.once(DialogEvent.ON_POSITIVE, () => {
+ resolve(input)
+ dialog.dismiss()
+ })
+ dialog.once(DialogEvent.ON_NEGATIVE, () => { dialog.dismiss() })
+ })
+}
+/**
+ * 显示一个选择对话框,选中任意项后消失
+ * @param title
+ * @param items 选项数组
+ * @param options
+ * @returns 返回选中的项目索引,被取消则返回-1
+ */
+export async function showSelectDialog(title: string, items: string[], options?: DialogBuilderOptions) {
+ let select = -1
+ const DialogContent = defineComponent(function () {
+ function click(i: number) {
+ select = i
+ dialog.dismiss()
+ }
+ const modifier = [fillMaxWidth(), heightIn(50)]
+ return function render() {
+ return items.map((item, i) => {
+ const onClick = click.bind(undefined, i)
+ return xml`
+
+ ${item}
+
+ `
+ })
+ }
+ })
+
+ const f: InputDialogOptions = {
+ title: title
+ }
+ const dialog = showDialog(Object.assign(f, options, { content: DialogContent, }))
+
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, () => resolve(select))
+ })
+}
+
+/**
+ * 显示一个多选对话框
+ * @param title
+ * @param items 可多选的项目
+ * @param initialSelectedIndices 初始选中的项目索引数组
+ * @param options
+ * @returns 返回选中的项目索引数组,被取消则返回`null`
+ */
+export async function showMultiChoiceDialog(title: string,
+ items: string[],
+ initialSelectedIndices?: number[],
+ options?: DialogBuilderOptions) {
+ let select = new Set()
+
+ const DialogContent = defineComponent(function () {
+ const state = reactive(items.map(() => false))
+ if (initialSelectedIndices) {
+ for (let i of initialSelectedIndices) {
+ if (i >= items.length) continue;
+ select.add(i)
+ state[i] = true;
+ }
+ }
+ function click(i: number) {
+ const r = state[i] = !state[i]
+ if (r) {
+ select.add(i)
+ } else select.delete(i);
+ }
+ const modifier = [fillMaxWidth(), heightIn(50)]
+ return function render() {
+ return items.map((item, i) => {
+ const onCheckedChange = click.bind(undefined, i)
+ return xml`
+
+
+ ${item}
+
+ `
+ })
+ }
+ })
+
+ const f: InputDialogOptions = {
+ title: title,
+ positive: '确认',
+ negative: '取消',
+ }
+ const dialog = showDialog(Object.assign(f, options, { content: DialogContent, }))
+
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, () => resolve(null))
+ dialog.once(DialogEvent.ON_POSITIVE, () => {
+ resolve(Array.from(select))
+ dialog.dismiss()
+ })
+ dialog.once(DialogEvent.ON_NEGATIVE, () => { dialog.dismiss() })
+ })
+}
+/**
+ * 显示一个单选对话框
+ * @param title
+ * @param items
+ * @param initialSelectedIndex
+ * @param options
+ * @returns 返回选中的项目索引,被取消则返回-1
+ */
+export async function showSingleChoiceDialog(title: string,
+ items: string[],
+ initialSelectedIndex?: number,
+ options?: DialogBuilderOptions) {
+ let select = ref(initialSelectedIndex || 0)
+
+ const DialogContent = defineComponent(function () {
+ function click(i: number) {
+ select.value = i
+ }
+ const modifier = [fillMaxWidth(), heightIn(50)]
+ return function render() {
+ return items.map((item, i) => {
+ const onCheckedChange = click.bind(undefined, i)
+ return xml`
+
+
+ ${item}
+
+ `
+ })
+ }
+ })
+
+ const f: InputDialogOptions = {
+ title: title,
+ positive: '确认',
+ negative: '取消',
+ }
+ const dialog = showDialog(Object.assign(f, options, { content: DialogContent, }))
+
+ return new Promise((resolve, reject) => {
+ dialog.once(DialogEvent.ON_DISMISS, () => resolve(-1))
+ dialog.once(DialogEvent.ON_POSITIVE, () => {
+ resolve(select.value)
+ dialog.dismiss()
+ })
+ dialog.once(DialogEvent.ON_NEGATIVE, () => { dialog.dismiss() })
+ })
+}
+
+export { DialogFactory }
\ No newline at end of file
diff --git a/autojs/src/js-api/src/dialogs/options.ts b/autojs/src/js-api/src/dialogs/options.ts
new file mode 100644
index 000000000..5531a529d
--- /dev/null
+++ b/autojs/src/js-api/src/dialogs/options.ts
@@ -0,0 +1,116 @@
+import { Color } from "@/vue-ui/theme"
+import { DialogType, DialogInterface } from "."
+import { Component, defineComponent, h, VNode, xml, PropType, shallowReactive } from "@/vue-ui"
+import { fillMaxWidth, heightIn, padding, verticalScroll, widthIn } from "@/vue-ui/modifierExtension"
+
+export type SecureTpye = TSecureTpye
+/**
+ * 创建对话框的一些通用选项
+ */
+export interface DialogBuilderOptions {
+ /**对话框标题 */
+ title?: string
+ /**对话框内容,可以是一个vue组件对象 */
+ content?: string | Component
+ /**仅在content是字符串时有效 */
+ contentColor?: Color
+ /**尚未支持 */
+ icon?: string
+ /**positive的文本,不设置将不显示positive按钮 */
+ positive?: string
+ positiveColor?: Color
+ /**negative的文本,不设置将不显示negative按钮 */
+ negative?: string
+ negativeColor?: Color
+ neutralColor?: Color
+ /**neutral的文本,不设置将不显示neutral按钮 */
+ neutral?: string
+ /**是否允许返回键取消对话框,默认为true */
+ dismissOnBackPress?: boolean
+ /**是否允许点击对话框外部取消对话框,默认为true */
+ dismissOnClickOutside?: boolean
+ /**设置对话框窗口的安全策略 */
+ securePolicy?: SecureTpye
+ type?: DialogType
+}
+export interface DialogEventListener {
+ onPositive?: () => void
+ onNegative?: () => void
+ onNeutral?: () => void
+}
+export interface InputDialogOptions extends DialogBuilderOptions {
+ /**输入框的提示 */
+ inputHint?: string,
+ /**输入框的默认文本 */
+ inputPrefill?: string,
+ /**输入框的lable */
+ inputLable?: string
+}
+
+function dialogButton(text?: string, color?: Color, onClick?: () => void) {
+ if (!text) return
+ return xml`
+
+ `
+}
+
+export function createDialogContent(options: DialogBuilderOptions) {
+ const { title, content, contentColor, positive, positiveColor, icon,
+ negative, negativeColor, neutralColor, neutral,
+ } = options
+ return defineComponent({
+ props: {
+ events: Object as PropType
+ },
+ render() {
+ let contentVnode: any;
+ if (typeof content === 'string') {
+ contentVnode = xml`${content}`
+ } else if (content) {
+ contentVnode = xml`<${content}/>`
+ }
+ const positiveVnode = dialogButton(positive, positiveColor, this.$props.events?.onPositive)
+ const negativeVnode = dialogButton(negative, negativeColor, this.$props.events?.onNegative)
+ const neutralVnode = dialogButton(neutral, neutralColor, this.$props.events?.onNeutral)
+
+ return xml`
+
+
+
+ ${title}
+
+
+ ${contentVnode}
+
+
+ ${neutralVnode}
+
+ ${negativeVnode}
+ ${positiveVnode}
+
+
+
+
+ `
+ }
+ })
+}
+
+export interface IDialogs {
+ showDialog(options: DialogBuilderOptions): DialogInterface
+ showAlertDialog(title: string, options?: DialogBuilderOptions): Promise
+ showConfirmDialog(title: string, options?: DialogBuilderOptions): Promise
+ showInputDialog(title: string, prefill?: string, options?: InputDialogOptions): Promise
+ showSelectDialog(title: string, items: string[], options?: DialogBuilderOptions): Promise
+ showMultiChoiceDialog(title: string,
+ items: string[],
+ initialSelectedIndices?: number[],
+ options?: DialogBuilderOptions): Promise
+ showSingleChoiceDialog(title: string,
+ items: string[],
+ initialSelectedIndex?: number,
+ options?: DialogBuilderOptions): Promise
+
+}
diff --git a/autojs/src/js-api/src/engines/engines.d.ts b/autojs/src/js-api/src/engines/engines.d.ts
new file mode 100644
index 000000000..b2484f659
--- /dev/null
+++ b/autojs/src/js-api/src/engines/engines.d.ts
@@ -0,0 +1,31 @@
+declare namespace root.engines {
+ let selfEngine: any | undefined
+ function myEngine(): NodeScriptEngine
+ function allEngine(): Set
+ function stopAll()
+ function stopAllAndToast()
+ function createExecutionConfig(): ExecutionConfig
+ function execScriptFile(path: String,
+ config: ExecutionConfig?,
+ listener: ((a: number, ...args) => void)?
+ ): ScriptExecution
+ function setupJs(ops: {
+ emitCallback: (name: string, ...args: any[]) => void
+ })
+}
+
+interface ScriptEngine {
+ id: number
+ isDestroyed: boolean
+ forceStop()
+ cwd(): string
+ emit(name: String, ...args: any)
+}
+interface NodeScriptEngine extends ScriptEngine {
+
+}
+type ExecutionConfig = {
+ workingDirectory: string
+ arguments: Map
+}
+type ScriptExecution = {}
\ No newline at end of file
diff --git a/autojs/src/js-api/src/engines/index.ts b/autojs/src/js-api/src/engines/index.ts
new file mode 100644
index 000000000..d877c24a3
--- /dev/null
+++ b/autojs/src/js-api/src/engines/index.ts
@@ -0,0 +1,120 @@
+import { EventEmitter } from 'node:events'
+const engines = Autox.engines
+
+export class ScriptEngineProxy extends EventEmitter {
+ private engine: ScriptEngine
+ get id(): number {
+ return this.engine.id
+ }
+ get isDestroyed(): boolean {
+ return this.engine.isDestroyed
+ }
+
+ constructor(engine: ScriptEngine) {
+ super()
+ this.engine = engine
+ }
+ emit(eventName: string | symbol, ...args: any[]): boolean {
+ super.emit(eventName, ...args)
+ if (typeof eventName === 'string' && this.id !== selfEngine.id) {
+ this.engine.emit(eventName, ...args)
+ }
+ return true
+ }
+ forceStop() {
+ if (engines.selfEngine === this) {
+ process.exit(1)
+ return
+ }
+ this.engine.forceStop()
+ }
+ cwd(): string {
+ return this.engine.cwd()
+ }
+
+}
+/**
+ * 当前运行的引擎
+ */
+export const selfEngine = new ScriptEngineProxy(engines.myEngine())
+engines.setupJs({
+ emitCallback(name, ...args) {
+ selfEngine.emit(name, ...args)
+ },
+})
+export interface ExecutionConfigOptions {
+ workingDirectory?: string
+ arguments?: Map
+ onStart?: (execution: ScriptExecution) => void
+ onSuccess?: (execution: ScriptExecution, result: any) => void
+ onException?: (execution: ScriptExecution, err: any) => void
+}
+/**
+ * 获取当前运行的引擎
+ * @returns
+ */
+export function myEngine(): ScriptEngineProxy {
+ return selfEngine
+}
+/**
+ * 运行一个脚本文件
+ * @param path 只能是绝对路径,不支持相对路径
+ * @param ops
+ * @returns
+ */
+export function execScriptFile(path: string, ops: ExecutionConfigOptions) {
+ if (ops) {
+ const executionConfig = engines.createExecutionConfig()
+ if (ops.workingDirectory) {
+ executionConfig.workingDirectory = ops.workingDirectory
+ }
+ if (ops.arguments) {
+ ops.arguments.forEach((value, key) => {
+ executionConfig.arguments.set(key, value)
+ })
+ }
+ return engines.execScriptFile(path, executionConfig, (a, ...args) => {
+ if (a == 0) {
+ ops.onStart?.(args[0] as ScriptExecution)
+ } else if (a == 1) {
+ ops.onSuccess?.(args[0] as ScriptExecution, args[1])
+ } else if (a == 2) {
+ ops.onException?.(args[0] as ScriptExecution, args[1])
+ }
+ })
+ }
+ return engines.execScriptFile(path, null, null)
+}
+/**
+ * 停止所有运行中的脚本,包括自身
+ * @returns
+ */
+export function stopAll() {
+ engines.stopAll()
+}
+/**
+ * 获取所有运行中的脚本
+ * @returns
+ */
+export function getRunningEngines() {
+ const r: ScriptEngineProxy[] = []
+ engines.allEngine().forEach((engine) => {
+ r.push(new ScriptEngineProxy(engine))
+ })
+ return r
+}
+/**
+ * 向所有运行中的脚本发送事件,相当于
+ * ```js
+ * getRunningEngines().forEach((engine) => {
+ engine.emit(event, ...args)
+ })
+ * ```
+ * @param event
+ * @param args
+ */
+export function broadcast(event: string, ...args: any) {
+ getRunningEngines().forEach((engine) => {
+ engine.emit(event, ...args)
+ })
+}
\ No newline at end of file
diff --git a/autojs/src/js-api/src/java/index.ts b/autojs/src/js-api/src/java/index.ts
index 1e8a058f3..a0c453a0d 100644
--- a/autojs/src/js-api/src/java/index.ts
+++ b/autojs/src/js-api/src/java/index.ts
@@ -1,5 +1,27 @@
-
+/**
+ * 该模块用于与java交互
+ * @packageDocumentation
+ */
+import { memoize } from 'lodash'
const java = Autox.java
+
+const createPackage = memoize(function (name?: string): any {
+ if (name) {
+ try {
+ return loadClass(name)
+ } catch (e: any) { }
+ }
+ return new Proxy({ name }, {
+ get(target, propKey) {
+ if (typeof propKey !== 'string') return undefined
+ if (target.name) {
+ return createPackage(target.name + '.' + propKey)
+ } else {
+ return createPackage(propKey)
+ }
+ }
+ })
+})
/**
* 采用默认的计算线程池异步调用java方法,返回Promise接受结果
* @alpha
@@ -48,4 +70,31 @@ export function invokeUi(javaobj: any,
methodName: string,
args?: any[]): Promise {
return java.invokeUi(javaobj, methodName, args || [])
+}
+/**
+ * 用于向rhino一样访问java类,如
+ * `Packages.java`或`Packages.javax`
+ * 此外该模块直接导出了常用的包
+ * ```js
+ * import { java, android, com } from 'java'
+ *
+ * new java.io.File(...)
+ * ```
+ */
+export const Packages = createPackage()
+
+const javaPackage = Packages.java
+const androidPackage = Packages.android
+const javaxPackage = Packages.javax
+const comPackage = Packages.com
+const netPackage = Packages.net
+const androidxPackage = Packages.androidx
+
+export {
+ javaPackage as java,
+ androidPackage as android,
+ javaxPackage as javax,
+ comPackage as com,
+ netPackage as net,
+ androidxPackage as androidx,
}
\ No newline at end of file
diff --git a/autojs/src/js-api/src/toast/index.ts b/autojs/src/js-api/src/toast/index.ts
index db7d6bf5a..e18ec391b 100644
--- a/autojs/src/js-api/src/toast/index.ts
+++ b/autojs/src/js-api/src/toast/index.ts
@@ -9,7 +9,7 @@ interface ToastOptions {
* import { showToast } from 'toast'
* showToast('hello world')
* @param message 要显示的消息
- * @param option 可以是"short" | "long",表示弹出时长
+ * @param option 可以是`"short" | "long"`,表示弹出时长
*/
export function showToast(message: any, option?: ToastOptions | string) {
let duration: number
diff --git a/autojs/src/js-api/src/vue-ui/README.md b/autojs/src/js-api/src/vue-ui/README.md
new file mode 100644
index 000000000..a6a2dc2fb
--- /dev/null
+++ b/autojs/src/js-api/src/vue-ui/README.md
@@ -0,0 +1,40 @@
+在第二代 api,ui 构建不再使用传统的 xml 和 e4x,
+也不在是基于 view 的系统,而是在 js 端使用[vue3](https://cn.vuejs.org/guide/introduction.html)和[htm](https://github.com/developit/htm)作为模板引擎,在 java 端使用 Jetpack Compose,
+要注意的是虽然是使用 vue 但并不代表 vue 所有特性都可用,也不代表就能够使用 web 中的各种 ui 框架,准确的来说是使用了 vue 的核心`@vue/runtime-core`,而实现了一套从 vue 渲染到安卓 Jetpack Compose 的渲染器。
+
+## 入门
+
+想要创建一个界面首先你需要从`vue-ui`模块导入各种函数,如
+
+```js
+import { createApp, xml, startActivity, ModifierExtension } from "vue-ui";
+```
+
+使用`createApp`创建一个 app 实例,与 vue 的方法相同,不过其中必须包含`render`函数,
+用于创建 Vnode。
+
+```js
+const app = createApp({
+ render() {
+ return xml`
+
+ UI内容
+
+ `;
+ },
+});
+```
+
+`render`渲染函数会在数据变化时调用许多次,你不能在这个函数中做任何与创建 Vnode 无关的事。
+创建好后使用`startActivity`函数将打开一个界面并显示。
+
+```js
+startActivity(app);
+```
+
+`startActivity`会返回一个`Promise`,你可以通过`await`等待 Activity 启动完成并得到
+Activity 实例。
+与第 1 代 api 不同,你无需在脚本最前面加上`'ui';`,因为脚本线程始终与 ui 线程分离,
+这样你还可以通过多次调用`startActivity`启动多个 Activity,但必须是不同的 app 实例。
+
+## 组件
diff --git a/autojs/src/js-api/src/vue-ui/icons.ts b/autojs/src/js-api/src/vue-ui/icons.ts
index 5855653da..199e75f18 100644
--- a/autojs/src/js-api/src/vue-ui/icons.ts
+++ b/autojs/src/js-api/src/vue-ui/icons.ts
@@ -1,4 +1,4 @@
-
+import { memoize } from 'lodash'
export interface Icons {
Call: ImageVector
Add: ImageVector
@@ -21,57 +21,25 @@ export interface Icons {
Person: ImageVector
}
-export type IconExprot = {
- [Property in keyof Icons]:
- () => Icons[Property]
-}
const ui = Autox.ui
-function createLoadFn(group: string, name: string) {
- return () => ui.loadIcon(group, name)
-}
-let group = "Filled"
-const Filled: IconExprot = {
- Call: createLoadFn(group, "Call"),
- Add: createLoadFn(group, "Add"),
- ArrowBack: createLoadFn(group, "ArrowBack"),
- Clear: createLoadFn(group, "Clear"),
- Edit: createLoadFn(group, "Edit"),
- Menu: createLoadFn(group, "Menu"),
- Search: createLoadFn(group, "Search"),
- Close: createLoadFn(group, "Close"),
- Star: createLoadFn(group, "Star"),
- Home: createLoadFn(group, "Home"),
- Notifications: createLoadFn(group, "Notifications"),
- Settings: createLoadFn(group, "Settings"),
- MoreVert: createLoadFn(group, "MoreVert"),
- MailOutline: createLoadFn(group, "MailOutline"),
- Refresh: createLoadFn(group, "Refresh"),
- AccountBox: createLoadFn(group, "AccountBox"),
- ArrowDropDown: createLoadFn(group, "ArrowDropDown"),
- Done: createLoadFn(group, "Done"),
- Person: createLoadFn(group, "Person"),
-}
-group = "Default"
-const Default: Icons = {
- Call: createLoadFn(group, "Call"),
- Add: createLoadFn(group, "Add"),
- ArrowBack: createLoadFn(group, "ArrowBack"),
- Clear: createLoadFn(group, "Clear"),
- Edit: createLoadFn(group, "Edit"),
- Menu: createLoadFn(group, "Menu"),
- Search: createLoadFn(group, "Search"),
- Close: createLoadFn(group, "Close"),
- Star: createLoadFn(group, "Star"),
- Home: createLoadFn(group, "Home"),
- Notifications: createLoadFn(group, "Notifications"),
- Settings: createLoadFn(group, "Settings"),
- MoreVert: createLoadFn(group, "MoreVert"),
- MailOutline: createLoadFn(group, "MailOutline"),
- Refresh: createLoadFn(group, "Refresh"),
- AccountBox: createLoadFn(group, "AccountBox"),
- ArrowDropDown: createLoadFn(group, "ArrowDropDown"),
- Done: createLoadFn(group, "Done"),
- Person: createLoadFn(group, "Person"),
+function createLoadProxy(group: string): unknown {
+ const e = memoize((name: string) => {
+ return ui.loadIcon(group, name)
+ })
+ return new Proxy({ e }, {
+ get(target, propKey) {
+ if (typeof propKey === 'string') {
+ return target.e(propKey)
+ } else {
+ throw new TypeError(`Filled Not exported property: ${String(propKey)} in IconGroup ${group} `)
+ }
+ }
+ })
}
+
+
+const Filled: Icons = createLoadProxy("Filled") as Icons
+
+const Default: Icons = createLoadProxy("Default") as Icons
export { Filled, Default }
\ No newline at end of file
diff --git a/autojs/src/js-api/src/vue-ui/index.ts b/autojs/src/js-api/src/vue-ui/index.ts
index 2e8376515..cf7890152 100644
--- a/autojs/src/js-api/src/vue-ui/index.ts
+++ b/autojs/src/js-api/src/vue-ui/index.ts
@@ -1,3 +1,8 @@
+/**
+ * 入门
+ * @document README.md
+ * @packageDocumentation
+ */
import {
App,
Component,
@@ -78,6 +83,9 @@ export const xml = htm.bind(h)
export { setDebug }
export * from '@vue/runtime-core'
export * as Icons from './icons'
+/**
+ * 这个对象导出用于设置`Modifier`的函数
+ */
export { ModifierExtension }
export { ActivityEventListener }
export * as Theme from './theme'
\ No newline at end of file
diff --git a/autojs/src/js-api/src/vue-ui/modifierExtension.ts b/autojs/src/js-api/src/vue-ui/modifierExtension.ts
index efc82ef40..dabba8998 100644
--- a/autojs/src/js-api/src/vue-ui/modifierExtension.ts
+++ b/autojs/src/js-api/src/vue-ui/modifierExtension.ts
@@ -98,7 +98,7 @@ export function padding(left: number, top?: number, right?: number, bottom?: num
* @param clickable
* @returns
*/
-export function clickable(clickable: () => {}) {
+export function clickable(clickable: () => void) {
return loadFactory('clickable').createModifierExt([clickable])
}
/**
diff --git a/autojs/src/js-api/src/vue-ui/nodeOps.ts b/autojs/src/js-api/src/vue-ui/nodeOps.ts
index fd8005325..17b241d9b 100644
--- a/autojs/src/js-api/src/vue-ui/nodeOps.ts
+++ b/autojs/src/js-api/src/vue-ui/nodeOps.ts
@@ -3,7 +3,7 @@ import { RendererOptions } from '@vue/runtime-core'
import { PxNode, PxNodeTypes } from './types'
import { createElement, insertElement, removeElement, setText } from './nativeRender';
-
+export type DomRendererOptions = RendererOptions
let nodeId: number = 0
export enum NodeOpTypes {
@@ -93,7 +93,7 @@ export function logNodeOp(op: NodeOp) {
}
}
-export const nodeOps: Omit, 'patchProp'> = {
+export const nodeOps: Omit = {
createElement(tag: string): PxElement {
const node: PxElement = new PxElement(tag)
logNodeOp({
diff --git a/autojs/src/js-api/src/vue-ui/patchProp.ts b/autojs/src/js-api/src/vue-ui/patchProp.ts
index abf1867c2..6bcd88434 100644
--- a/autojs/src/js-api/src/vue-ui/patchProp.ts
+++ b/autojs/src/js-api/src/vue-ui/patchProp.ts
@@ -1,14 +1,17 @@
-import { NodeOpTypes, logNodeOp } from './nodeOps'
+import { NodeOpTypes, logNodeOp, DomRendererOptions } from './nodeOps'
import { isOn } from '@vue/shared'
import { PxElement } from './types';
import { patchElementProp } from './nativeRender';
import { parseModifier } from './modifierParse';
-export function patchProp(
+export const patchProp: DomRendererOptions['patchProp'] = function (
el: PxElement,
key: string,
prevValue: any,
nextValue: any,
+ namespace,
+ prevChildren,
+ parentComponent
) {
logNodeOp({
type: NodeOpTypes.PATCH,
diff --git a/autojs/src/main/AndroidManifest.xml b/autojs/src/main/AndroidManifest.xml
index e85f46d04..cfe06cd66 100644
--- a/autojs/src/main/AndroidManifest.xml
+++ b/autojs/src/main/AndroidManifest.xml
@@ -12,6 +12,11 @@
+
+
+
+
+
+
()
+ fun showDialog(context: Context, builder: AppDialogBuilder, scope: CoroutineScope) =
+ scope.launch(Dispatchers.Main) {
+ val eid = id++
+ dialogs[eid] = builder
+ context.startActivity(
+ Intent(context, AppDialogActivity::class.java)
+ .putExtra(TAG, eid)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+ }
+
+ private const val TAG = "AppDialogActivity"
+ }
+}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/aiselp/autox/api/JsDialogs.kt b/autojs/src/main/java/com/aiselp/autox/api/JsDialogs.kt
new file mode 100644
index 000000000..ed43de7ef
--- /dev/null
+++ b/autojs/src/main/java/com/aiselp/autox/api/JsDialogs.kt
@@ -0,0 +1,60 @@
+package com.aiselp.autox.api
+
+import android.content.Context
+import androidx.compose.ui.window.SecureFlagPolicy
+import com.aiselp.autox.activity.AppDialogActivity
+import com.aiselp.autox.api.ui.ComposeElement
+import com.aiselp.autox.engine.EventLoopQueue
+import com.caoccao.javet.annotations.V8Function
+import com.caoccao.javet.interop.V8Runtime
+import com.caoccao.javet.values.reference.V8ValueFunction
+import com.caoccao.javet.values.reference.V8ValueObject
+import kotlinx.coroutines.CoroutineScope
+
+class JsDialogs(
+ val eventLoopQueue: EventLoopQueue,
+ val context: Context,
+ val scope: CoroutineScope
+) : NativeApi {
+ override val moduleId: String = ID
+ override fun install(v8Runtime: V8Runtime, global: V8ValueObject): NativeApi.BindingMode {
+ return NativeApi.BindingMode.PROXY
+ }
+
+ override fun recycle(v8Runtime: V8Runtime, global: V8ValueObject) {
+ }
+
+ @V8Function
+ fun showDialog(
+ element: ComposeElement,
+ listener: V8ValueObject?
+ ): AppDialogActivity.AppDialogBuilder {
+ val securePolicy = listener?.getString("securePolicy")
+ val builder = object : AppDialogActivity.AppDialogBuilder(element, scope) {
+ val dismissListener = listener?.get("onDismiss")?.let {
+ eventLoopQueue.createV8Callback(it)
+ }
+ override val dismissOnBackPress: Boolean =
+ listener?.getBoolean("dismissOnBackPress") ?: super.dismissOnBackPress
+ override val dismissOnClickOutside: Boolean =
+ listener?.getBoolean("dismissOnClickOutside") ?: super.dismissOnClickOutside
+ override val securePolicy: SecureFlagPolicy = when (securePolicy) {
+ "SecureOn" -> SecureFlagPolicy.SecureOn
+ "SecureOff" -> SecureFlagPolicy.SecureOff
+ else -> super.securePolicy
+ }
+
+ override fun onDismiss() {
+ dismissListener?.invoke()
+ dismissListener?.close()
+ }
+ }
+
+ AppDialogActivity.showDialog(context, builder, scope)
+ return builder
+ }
+
+ companion object {
+ const val ID = "dialogs"
+ }
+}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/aiselp/autox/api/JsEngines.kt b/autojs/src/main/java/com/aiselp/autox/api/JsEngines.kt
new file mode 100644
index 000000000..add0ce834
--- /dev/null
+++ b/autojs/src/main/java/com/aiselp/autox/api/JsEngines.kt
@@ -0,0 +1,94 @@
+package com.aiselp.autox.api
+
+import com.aiselp.autox.engine.NodeScriptEngine
+import com.caoccao.javet.annotations.V8Function
+import com.caoccao.javet.interop.V8Runtime
+import com.caoccao.javet.values.reference.V8ValueFunction
+import com.caoccao.javet.values.reference.V8ValueObject
+import com.stardust.autojs.ScriptEngineService
+import com.stardust.autojs.engine.ScriptEngine
+import com.stardust.autojs.execution.ExecutionConfig
+import com.stardust.autojs.execution.ScriptExecution
+import com.stardust.autojs.execution.ScriptExecutionListener
+import com.stardust.autojs.script.ScriptFile
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class JsEngines(private val engine: NodeScriptEngine) : NativeApi {
+ override val moduleId: String = ID
+ private val engineService by lazy { ScriptEngineService.instance!! }
+ private var emitCallback: V8ValueFunction? = null
+
+ override fun install(v8Runtime: V8Runtime, global: V8ValueObject): NativeApi.BindingMode {
+ return NativeApi.BindingMode.PROXY
+ }
+
+ override fun recycle(v8Runtime: V8Runtime, global: V8ValueObject) {
+ }
+
+ @V8Function
+ fun setupJs(ops: V8ValueObject) {
+ emitCallback = ops.get(EMIT_FUNCTION)
+ }
+
+ @V8Function
+ fun execScriptFile(
+ path: String,
+ config: ExecutionConfig?,
+ listener: V8ValueFunction?
+ ): ScriptExecution {
+ if (listener != null) {
+ val callback = engine.eventLoopQueue.createV8Callback(listener)
+ return engineService.execute(ScriptFile(path).toSource(), object :
+ ScriptExecutionListener {
+ override fun onStart(execution: ScriptExecution?) {
+ callback.invoke(0, execution)
+ }
+
+ override fun onSuccess(execution: ScriptExecution?, result: Any?) {
+ callback.invoke(1, execution, result)
+ callback.close()
+ }
+
+ override fun onException(execution: ScriptExecution?, e: Throwable?) {
+ callback.invoke(2, execution, e)
+ callback.close()
+ }
+
+ }, config)
+ } else
+ return engineService.execute(ScriptFile(path).toSource(), config)
+ }
+
+ fun emitEngineEvent(name: String, args: Array) {
+ emitCallback?.callVoid(null, name, *args)
+ }
+
+ @V8Function
+ fun allEngine(): Set> {
+ return engineService.engines
+ }
+
+ @V8Function
+ fun myEngine() = engine
+
+ @V8Function
+ fun createExecutionConfig() = ExecutionConfig()
+
+ @V8Function
+ fun stopAll() {
+ engine.scope.launch(Dispatchers.Default) {
+ engineService.stopAll()
+ }
+ }
+
+ @V8Function
+ fun stopAllAndToast() {
+ engineService.stopAllAndToast()
+ }
+
+ companion object {
+ const val ID = "engines"
+ private const val EMIT_FUNCTION = "emitCallback"
+ }
+}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/aiselp/autox/api/JsToast.kt b/autojs/src/main/java/com/aiselp/autox/api/JsToast.kt
index 6f39a3ee7..5be680db5 100644
--- a/autojs/src/main/java/com/aiselp/autox/api/JsToast.kt
+++ b/autojs/src/main/java/com/aiselp/autox/api/JsToast.kt
@@ -7,7 +7,9 @@ import com.caoccao.javet.annotations.V8Property
import com.caoccao.javet.interop.V8Runtime
import com.caoccao.javet.values.reference.V8ValueObject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class JsToast(private val context: Context, private val scope: CoroutineScope) : NativeApi {
@@ -15,12 +17,14 @@ class JsToast(private val context: Context, private val scope: CoroutineScope) :
@get:V8Property(name = "SHORT")
val SHORT = Toast.LENGTH_SHORT
+
@get:V8Property(name = "LONG")
val LONG = Toast.LENGTH_LONG
+
+ @OptIn(DelicateCoroutinesApi::class)
@V8Function
- @JvmOverloads
fun showToast(msg: String, duration: Int = Toast.LENGTH_SHORT) =
- scope.launch(Dispatchers.Main) {
+ GlobalScope.launch(Dispatchers.Main) {
Toast.makeText(context, msg, duration).show()
}
diff --git a/autojs/src/main/java/com/aiselp/autox/api/JsUi.kt b/autojs/src/main/java/com/aiselp/autox/api/JsUi.kt
index 6e9b6fac0..6cc33a2f7 100644
--- a/autojs/src/main/java/com/aiselp/autox/api/JsUi.kt
+++ b/autojs/src/main/java/com/aiselp/autox/api/JsUi.kt
@@ -96,11 +96,6 @@ class JsUi(nodeScriptEngine: NodeScriptEngine) : NativeApi {
@V8Function
fun patchProp(element: ComposeElement, key: String, value: V8Value?) {
val value1 = converterValue(value)
- element.props[key]?.let {
- if (it is EventLoopQueue.V8Callback) {
- it.close()
- }
- }
element.props[key] = value1
}
@@ -140,7 +135,7 @@ class JsUi(nodeScriptEngine: NodeScriptEngine) : NativeApi {
private fun converterValue(value: V8Value?): Any? {
return if (value is V8ValueFunction) {
- eventLoopQueue.createV8Callback(value)
+ eventLoopQueue.createWeakV8Callback(value)
} else {
converter.toObject(value)
}
diff --git a/autojs/src/main/java/com/aiselp/autox/engine/EventLoopQueue.kt b/autojs/src/main/java/com/aiselp/autox/engine/EventLoopQueue.kt
index 4e06e2d07..c878b49f9 100644
--- a/autojs/src/main/java/com/aiselp/autox/engine/EventLoopQueue.kt
+++ b/autojs/src/main/java/com/aiselp/autox/engine/EventLoopQueue.kt
@@ -31,7 +31,7 @@ class EventLoopQueue(val runtime: NodeRuntime) {
return id;
},
emit: function(id, ...args){
- callbacks.get(id)(...args);
+ return callbacks.get(id)?.(...args);
},
removeCallback: function(id){
callbacks.delete(callbacks.get(id));
@@ -68,26 +68,28 @@ class EventLoopQueue(val runtime: NodeRuntime) {
}
fun createV8Callback(fn: V8ValueFunction): V8Callback {
+
val id = util.invoke("addCallback", fn)
id.use {
- return V8Callback(id.asLong())
+ return LastingV8Callback(id.asLong())
}
}
- fun removeV8Callback(callback: V8Callback) {
- addTask {
- util.invokeVoid("removeCallback", callback.id)
- }
+ fun createWeakV8Callback(fn: V8ValueFunction): V8Callback {
+ return WeakV8Callback(fn)
}
- fun executeQueue(): Boolean = synchronized(this) {
- val executeQueue = currentQueue
- if (executeQueue.isEmpty()) {
- return false
+ fun executeQueue(): Boolean {
+ val executeQueue: ArrayDeque
+ synchronized(this) {
+ executeQueue = currentQueue
+ if (executeQueue.isEmpty()) {
+ return false
+ }
+ currentQueue = if (currentQueue === queue) {
+ queueX
+ } else queue
}
- currentQueue = if (currentQueue === queue) {
- queueX
- } else queue
executeQueue.forEach { it.run() }
executeQueue.clear()
return true
@@ -103,8 +105,11 @@ class EventLoopQueue(val runtime: NodeRuntime) {
util.close()
}
- inner class V8Callback(val id: Long):AutoCloseable {
- @Volatile
+ /**
+ * 此回调会在js上下文中创建一个持久引用,使用完毕应调用close,
+ * 此类实现了线程安全,可在任意线程中调用
+ */
+ inner class LastingV8Callback(val id: Long) : V8Callback {
private var removerd = false
private fun call(vararg args: Any?): Any? {
@@ -115,7 +120,7 @@ class EventLoopQueue(val runtime: NodeRuntime) {
} else return result
}
- fun invoke(vararg args: Any?): CompletableDeferred {
+ override fun invoke(vararg args: Any?): CompletableDeferred {
if (removerd) {
Log.w(TAG, "this callback[${id}] has been removed")
return CompletableDeferred().also { it.complete(null) }
@@ -125,22 +130,64 @@ class EventLoopQueue(val runtime: NodeRuntime) {
return deferred
}
- fun invokeSync(vararg args: Any?): Any? = runBlocking {
+ override fun invokeSync(vararg args: Any?): Any? = runBlocking {
invoke(*args).await()
}
- suspend fun invokeAsync(vararg args: Any?): Any? {
+ override suspend fun invokeAsync(vararg args: Any?): Any? {
return invoke(*args).await()
}
override fun close() {
removerd = true
- this@EventLoopQueue.removeV8Callback(this)
+ addTask {
+ util.invokeVoid("removeCallback", id)
+ }
}
+ }
- fun cancel() {
+ /**
+ * 弱引用回调,当V8ValueFunction在js上下文失去引用并被回收后,此回调将失效
+ */
+ inner class WeakV8Callback(val fn: V8ValueFunction) : V8Callback {
+ init {
+ fn.setWeak()
+ }
+ private fun call(vararg args: Any?): Any? {
+ if (fn.isClosed) {
+ Log.d(TAG, "this callback[${fn}] has been closed")
+ return null
+ }
+ val result = runtime.converter.toObject(fn.call(null, *args))
+ if (result is V8Value) {
+ result.close()
+ return null
+ } else return result
}
+
+ override fun invoke(vararg args: Any?): CompletableDeferred {
+ val deferred = CompletableDeferred(job)
+ this@EventLoopQueue.addTask { deferred.complete(call(*args)) }
+ return deferred
+ }
+
+ override fun invokeSync(vararg args: Any?): Any? = runBlocking {
+ invoke(*args).await()
+ }
+
+ override suspend fun invokeAsync(vararg args: Any?): Any? {
+ return invoke(*args).await()
+ }
+
+ override fun close() {
+ }
+ }
+
+ interface V8Callback : AutoCloseable {
+ fun invoke(vararg args: Any?): CompletableDeferred
+ fun invokeSync(vararg args: Any?): Any?
+ suspend fun invokeAsync(vararg args: Any?): Any?
}
companion object {
diff --git a/autojs/src/main/java/com/aiselp/autox/engine/NativeApiManager.kt b/autojs/src/main/java/com/aiselp/autox/engine/NativeApiManager.kt
index e71ed5e3c..e4a77fce0 100644
--- a/autojs/src/main/java/com/aiselp/autox/engine/NativeApiManager.kt
+++ b/autojs/src/main/java/com/aiselp/autox/engine/NativeApiManager.kt
@@ -7,6 +7,7 @@ import com.caoccao.javet.annotations.V8Property
import com.caoccao.javet.interop.V8Runtime
import com.caoccao.javet.values.V8Value
import com.caoccao.javet.values.reference.V8ValueError
+import com.caoccao.javet.values.reference.V8ValueFunction
import com.caoccao.javet.values.reference.V8ValueObject
import com.stardust.autojs.runtime.exception.ScriptException
import kotlinx.coroutines.cancel
@@ -20,10 +21,16 @@ class NativeApiManager(engine: NodeScriptEngine) {
apis[api.moduleId] = api
}
+ fun getNativeApi(moduleId: String): NativeApi? {
+ return apis[moduleId]
+ }
+
fun initialize(v8Runtime: V8Runtime, global: V8ValueObject) {
v8Runtime.createV8ValueObject().use { autoxObject ->
autoxObject.bind(rootObject)
- global.set(INSTANCE_NAME, autoxObject)
+ v8Runtime.getExecutor(DEFINE_PROPERTY).execute().use {
+ it.callVoid(null, global, INSTANCE_NAME, autoxObject)
+ }
for (api in apis.values) {
val bindingMode = api.install(v8Runtime, global)
when (bindingMode) {
@@ -87,5 +94,14 @@ class NativeApiManager(engine: NodeScriptEngine) {
companion object {
private const val INSTANCE_NAME = "Autox"
+ private val DEFINE_PROPERTY = """
+ (obj, name, value) => {
+ Object.defineProperty(obj, name, {
+ value,
+ writable: false,
+ enumerable: false,
+ })
+ }
+ """.trimIndent()
}
}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt b/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt
index 99a55af3a..92c4e0a6a 100644
--- a/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt
+++ b/autojs/src/main/java/com/aiselp/autox/engine/NodeScriptEngine.kt
@@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log
import com.aiselp.autox.api.JavaInteractor
import com.aiselp.autox.api.JsClipManager
+import com.aiselp.autox.api.JsDialogs
+import com.aiselp.autox.api.JsEngines
import com.aiselp.autox.api.JsMedia
import com.aiselp.autox.api.JsToast
import com.aiselp.autox.api.JsUi
@@ -34,7 +36,7 @@ import kotlinx.coroutines.withTimeout
import java.io.File
class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
- ScriptEngine.AbstractScriptEngine() {
+ ScriptEngine.AbstractScriptEngine(), ScriptEngine.EngineEvent {
val runtime: NodeRuntime = V8Host.getNodeInstance().createV8Runtime()
private val tags = mutableMapOf()
@@ -81,6 +83,7 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
override fun init() {
runtime.converter = converter
runtime.allowEval(true)
+ runtime.isStopping = true
runtime.getExecutor(
"""
(()=>{
@@ -109,6 +112,8 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
nativeApiManager.register(JavaInteractor(scope, converter, promiseFactory))
nativeApiManager.register(JsToast(context, scope))
nativeApiManager.register(JsMedia(context))
+ nativeApiManager.register(JsDialogs(eventLoopQueue, context, scope))
+ nativeApiManager.register(JsEngines(this))
nativeApiManager.initialize(runtime, global)
}
@@ -124,7 +129,7 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
while (scope.isActive) {
// Log.d(TAG,"loop ing...")
if (runtime.await(V8AwaitMode.RunNoWait) or
- eventLoopQueue.executeQueue() or
+ eventLoopQueue.executeQueue() ||
resultListener.result.isActive
) {
Thread.sleep(1)
@@ -146,6 +151,11 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
}
}
+ override fun emit(name: String, vararg args: Any?) {
+ val jsEngines = nativeApiManager.getNativeApi(JsEngines.ID) as? JsEngines
+ jsEngines?.let { eventLoopQueue.addTask { it.emitEngineEvent(name, args) } }
+ }
+
private fun exceptionHandling(e: Any?) {
when (e) {
is Throwable -> run {
@@ -165,19 +175,19 @@ class NodeScriptEngine(val context: Context, val uiHandler: UiHandler) :
private fun initializeModule(file: File): V8Value {
val parentFile = file.parentFile ?: File("/")
runtime.getNodeModule(NodeModuleProcess::class.java).workingDirectory = parentFile
- runtime.getNodeModule(NodeModuleModule::class.java).setRequireRootDirectory(parentFile)
- val nodeModuleResolver = NodeModuleResolver(parentFile, moduleDirectory)
+ val nodeModuleResolver = NodeModuleResolver(runtime, parentFile, moduleDirectory)
runtime.v8ModuleResolver = nodeModuleResolver
+ runtime.globalObject.delete(NodeModuleModule.PROPERTY_REQUIRE)
return if (NodeModuleResolver.isEsModule(file)) {
//es module
- runtime.getExecutor(file).setResourceName(file.path).compileV8Module(true).run {
+ NodeModuleResolver.compileV8Module(runtime, file.readText(), file.path).run {
nodeModuleResolver.addCacheModule(this)
execute()
}
} else {
//commonjs
- runtime.globalObject.invoke(
- NodeModuleModule.PROPERTY_REQUIRE, runtime.createV8ValueString(file.path)
+ nodeModuleResolver.require.call(
+ null, runtime.createV8ValueString(file.path)
)
}
}
diff --git a/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt b/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt
index 672bc7e5a..b2987c012 100644
--- a/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt
+++ b/autojs/src/main/java/com/aiselp/autox/module/NodeModuleResolver.kt
@@ -2,15 +2,28 @@ package com.aiselp.autox.module
import android.net.Uri
import android.util.Log
+import com.caoccao.javet.interop.NodeRuntime
import com.caoccao.javet.interop.V8Runtime
-import com.caoccao.javet.interop.callback.JavetBuiltInModuleResolver
+import com.caoccao.javet.interop.callback.IV8ModuleResolver
+import com.caoccao.javet.node.modules.NodeModuleModule
import com.caoccao.javet.values.reference.IV8Module
+import com.caoccao.javet.values.reference.V8Module
+import com.caoccao.javet.values.reference.V8ValueFunction
+import com.caoccao.javet.values.reference.V8ValueObject
import java.io.File
import java.net.URI
-class NodeModuleResolver(val workingDirectory: File, private val globalModuleDirectory: File) :
- JavetBuiltInModuleResolver() {
+class NodeModuleResolver(
+ val runtime: NodeRuntime,
+ val workingDirectory: File,
+ private val globalModuleDirectory: File
+) : IV8ModuleResolver {
private val esModuleCache = mutableMapOf()
+ val require: V8ValueFunction = runtime.getNodeModule(NodeModuleModule::class.java).moduleObject
+ .invoke(
+ NodeModuleModule.FUNCTION_CREATE_REQUIRE,
+ workingDirectory.absolutePath
+ )
override fun resolve(
v8Runtime: V8Runtime, resourceName: String, v8ModuleReferrer: IV8Module
@@ -21,13 +34,28 @@ class NodeModuleResolver(val workingDirectory: File, private val globalModuleDir
return parsingModule(v8Runtime, Uri.parse(s))
}
val uri = Uri.parse(resourceName)
- return if (uri.scheme == null) {
- try {
- super.resolve(v8Runtime, "node:$resourceName", v8ModuleReferrer)
- }catch (e: Exception){
- null
- } ?: parsingPackageModule(v8Runtime, workingDirectory, resourceName)
- } else parsingModule(v8Runtime, uri)
+ if (resourceName.startsWith("/")) {
+ return parsingModule(v8Runtime, uri)
+ }
+ return when (uri.scheme) {
+ "node" -> loadNodeModule(resourceName)
+ null -> run {
+ try {
+ loadNodeModule("node:$resourceName")
+ } catch (e: Exception) {
+ parsingPackageModule(v8Runtime, workingDirectory, resourceName)
+ }
+ }
+
+ else -> parsingModule(v8Runtime, uri)
+ }
+ }
+
+ private fun loadNodeModule(resourceName: String): V8Module? {
+ require.call(null, resourceName).let { module ->
+ module.set("default", module)
+ return runtime.createV8Module(resourceName, module)
+ }
}
private fun checkCacheModule(resourceName: String, load: () -> IV8Module?): IV8Module? {
@@ -47,13 +75,12 @@ class NodeModuleResolver(val workingDirectory: File, private val globalModuleDir
v8Runtime: V8Runtime, file: File, isModule: Boolean = isEsModule(file)
): IV8Module? = checkCacheModule(file.path) {
return@checkCacheModule if (isModule) {
- v8Runtime.getExecutor(file).setResourceName(file.path)
- .compileV8Module()
+ compileV8Module(v8Runtime, file.readText(), file.path)
} else {
- v8Runtime.getExecutor(
- "export default require(`${file.path}`)"
- ).setResourceName(file.path)
- .compileV8Module()
+ require.call(null, file.path).use { valueObject ->
+ valueObject.set("default", valueObject)
+ runtime.createV8Module(file.path, valueObject)
+ }
}
}
@@ -90,6 +117,12 @@ class NodeModuleResolver(val workingDirectory: File, private val globalModuleDir
companion object {
private const val TAG = "NodeModuleResolver"
+ fun compileV8Module(runtime: V8Runtime, script: String, resourceName: String): IV8Module {
+ val executor = runtime.getExecutor(script)
+ executor.v8ScriptOrigin.resourceName = resourceName
+ return executor.setModule(true).compileV8Module()
+ }
+
fun isEsModule(file: File): Boolean {
return if (file.isFile) {
if (file.path.endsWith(".mjs")) return true
diff --git a/autojs/src/main/java/com/stardust/autojs/IndependentScriptService.kt b/autojs/src/main/java/com/stardust/autojs/IndependentScriptService.kt
index b1ec3ce52..febebb6a3 100644
--- a/autojs/src/main/java/com/stardust/autojs/IndependentScriptService.kt
+++ b/autojs/src/main/java/com/stardust/autojs/IndependentScriptService.kt
@@ -1,10 +1,20 @@
package com.stardust.autojs
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
import android.app.Service
+import android.content.Context
import android.content.Intent
+import android.content.pm.ServiceInfo
+import android.os.Build
import android.os.IBinder
import android.os.Process
import android.util.Log
+import androidx.core.app.NotificationCompat
+import androidx.core.app.ServiceCompat
+import com.stardust.autojs.core.pref.Pref
+import com.stardust.autojs.core.pref.PrefKey
import com.stardust.autojs.servicecomponents.ScriptBinder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -16,14 +26,60 @@ class IndependentScriptService : Service() {
super.onCreate()
Log.i(TAG, "onCreate")
Log.i(TAG, "Pid: ${Process.myPid()}")
+ val isForeground = Pref.getDefault(this).getBoolean(PrefKey.KEY_FOREGROUND_SERVIE, false)
+ Log.d(TAG, "isForeground: $isForeground")
+ if (isForeground) {
+ startForeground()
+ }
}
override fun onLowMemory() {
super.onLowMemory()
}
+ private fun startForeground() {
+ ServiceCompat.startForeground(
+ this, 25, buildNotification(),
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+ } else {
+ 0
+ },
+ )
+ }
+
+ private fun buildNotification(): Notification {
+ val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
+ val name: CharSequence = "AutoJS Service"
+ val description = "script foreground service"
+ val channel = NotificationChannel(
+ CHANEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT
+ )
+ channel.description = description
+ channel.enableLights(false)
+ manager.createNotificationChannel(channel)
+
+ return NotificationCompat.Builder(this, CHANEL_ID)
+ .setContentTitle(getString(R.string.foreground_notification_title))
+ .setContentText("前台服务运行中")
+ .setSmallIcon(R.drawable.autojs_logo)
+ .setWhen(System.currentTimeMillis())
+ .setChannelId(CHANEL_ID)
+ .setVibrate(LongArray(0))
+ .setOngoing(true)
+ .build()
+ }
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- return super.onStartCommand(intent, flags, startId)
+ val action = intent?.action
+ when (action) {
+ ACTION_START_FOREGROUND -> startForeground()
+
+ ACTION_STOP_FOREGROUND -> {
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
+ }
+ }
+ return START_STICKY
}
override fun onDestroy() {
@@ -43,12 +99,21 @@ class IndependentScriptService : Service() {
return ScriptBinder(this, scope)
}
- fun initAutojs() {
-
- }
-
companion object {
private const val TAG = "ScriptService"
+ private val CHANEL_ID = IndependentScriptService::class.java.name + "_foreground"
+ const val ACTION_START_FOREGROUND = "action_start_foreground"
+ const val ACTION_STOP_FOREGROUND = "action_stop_foreground"
+ fun startForeground(context: Context) {
+ val intent = Intent(context, IndependentScriptService::class.java)
+ intent.action = ACTION_START_FOREGROUND
+ context.startService(intent)
+ }
+ fun stopForeground(context: Context) {
+ val intent = Intent(context, IndependentScriptService::class.java)
+ intent.action = ACTION_STOP_FOREGROUND
+ context.startService(intent)
+ }
}
}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/capture/CaptureForegroundService.kt b/autojs/src/main/java/com/stardust/autojs/core/image/capture/CaptureForegroundService.kt
index e28d6d4ae..1cdc3cbdd 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/image/capture/CaptureForegroundService.kt
+++ b/autojs/src/main/java/com/stardust/autojs/core/image/capture/CaptureForegroundService.kt
@@ -6,13 +6,14 @@ import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
+import android.content.pm.ServiceInfo
import android.media.projection.MediaProjection
import android.os.Build
import android.os.Handler
import android.os.IBinder
import android.util.Log
-import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
+import androidx.core.app.ServiceCompat
import com.stardust.autojs.R
import com.stardust.autojs.core.image.capture.ScreenCaptureRequestActivity
@@ -43,16 +44,18 @@ class CaptureForegroundService : Service() {
override fun onCreate() {
super.onCreate()
- startForeground(NOTIFICATION_ID, buildNotification())
+ ServiceCompat.startForeground(
+ this, NOTIFICATION_ID, buildNotification(),
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
+ } else 0
+ )
}
private fun buildNotification(): Notification {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- createNotificationChannel()
- }
- val flags =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
+ createNotificationChannel()
+ val flags = PendingIntent.FLAG_IMMUTABLE
val contentIntent = PendingIntent.getActivity(
this, 0,
Intent(this, ScreenCaptureRequestActivity::class.java), flags
@@ -68,7 +71,6 @@ class CaptureForegroundService : Service() {
.build()
}
- @RequiresApi(api = Build.VERSION_CODES.O)
private fun createNotificationChannel() {
val manager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)
val channel = NotificationChannel(
@@ -111,7 +113,7 @@ class CaptureForegroundService : Service() {
var mediaProjection: MediaProjection? = null
private const val TAG = "CaptureService"
private const val STOP = "STOP_SERVICE"
- private const val NOTIFICATION_ID = 2
+ private const val NOTIFICATION_ID = 26
private val CHANNEL_ID = CaptureForegroundService::class.java.name + ".foreground"
private const val NOTIFICATION_TITLE = "前台截图服务运行中"
}
diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureManager.kt b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureManager.kt
index 66b62cf43..8b8060a26 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureManager.kt
+++ b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureManager.kt
@@ -7,7 +7,6 @@ import android.media.projection.MediaProjection
import android.media.projection.MediaProjectionManager
import com.stardust.app.OnActivityResultDelegate
import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.coroutineScope
import java.util.concurrent.CancellationException
class ScreenCaptureManager : ScreenCaptureRequester {
@@ -20,23 +19,19 @@ class ScreenCaptureManager : ScreenCaptureRequester {
screenCapture?.setOrientation(orientation, context)
return
}
+ context.startService(Intent(context, CaptureForegroundService::class.java))
val result = if (context is OnActivityResultDelegate.DelegateHost && context is Activity) {
ScreenCaptureRequester.ActivityScreenCaptureRequester(
context.onActivityResultDelegateMediator, context
).request()
} else {
- coroutineScope {
- val result = CompletableDeferred()
- ScreenCaptureRequestActivity.request(context,
- object : ScreenCaptureRequestActivity.Callback {
- override fun onResult(data: Intent?) {
- if (data != null) {
- result.complete(data)
- } else result.cancel(CancellationException("data is null"))
- }
- })
- result.await()
+ val result = CompletableDeferred()
+ ScreenCaptureRequestActivity.request(context) { data ->
+ if (data != null) {
+ result.complete(data)
+ } else result.cancel(CancellationException("data is null"))
}
+ result.await()
}
mediaProjection =
(context.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager).getMediaProjection(
@@ -44,7 +39,6 @@ class ScreenCaptureManager : ScreenCaptureRequester {
result
)
CaptureForegroundService.mediaProjection = mediaProjection
- context.startService(Intent(context, CaptureForegroundService::class.java))
screenCapture = ScreenCapturer(mediaProjection!!, orientation)
}
diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureRequestActivity.kt b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureRequestActivity.kt
index 6463872e5..a93c9e0c5 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureRequestActivity.kt
+++ b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCaptureRequestActivity.kt
@@ -1,24 +1,26 @@
package com.stardust.autojs.core.image.capture
-import android.app.Activity
import android.content.Context
import android.content.Intent
+import android.media.projection.MediaProjectionManager
import android.os.Bundle
-import com.stardust.app.OnActivityResultDelegate
-import com.stardust.autojs.core.image.capture.ScreenCaptureRequester.ActivityScreenCaptureRequester
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.platform.LocalContext
import com.stardust.util.IntentExtras
-import kotlinx.coroutines.MainScope
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.delay
/**
* Created by Stardust on 2017/5/22.
*/
-class ScreenCaptureRequestActivity : Activity() {
- interface Callback {
+class ScreenCaptureRequestActivity : AppCompatActivity() {
+ fun interface Callback {
fun onResult(data: Intent?)
}
- private val mOnActivityResultDelegateMediator = OnActivityResultDelegate.Mediator()
private var mCallback: Callback? = null
private var extraId = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -35,17 +37,16 @@ class ScreenCaptureRequestActivity : Activity() {
return
}
- MainScope().launch {
- val screenCaptureRequester = ActivityScreenCaptureRequester(
- mOnActivityResultDelegateMediator,
- this@ScreenCaptureRequestActivity
- )
- val intent = try {
- screenCaptureRequester.request()
- } catch (e: Exception) {
- null
+ setContent {
+ val requester = rememberLauncherForActivityResult(ScreenCaptureRequester()) {
+ mCallback?.onResult(it)
+ finish()
+ }
+ val context = LocalContext.current
+ LaunchedEffect(key1 = Unit) {
+ delay(10)
+ requester.launch(context)
}
- mCallback?.onResult(intent)
}
}
@@ -55,10 +56,14 @@ class ScreenCaptureRequestActivity : Activity() {
mCallback = null
}
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- mOnActivityResultDelegateMediator.onActivityResult(requestCode, resultCode, data)
- IntentExtras.fromIdAndRelease(extraId)
- finish()
+ class ScreenCaptureRequester : ActivityResultContract() {
+ override fun createIntent(context: Context, input: Context): Intent {
+ return (input.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager).createScreenCaptureIntent()
+ }
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Intent? {
+ return intent
+ }
}
companion object {
diff --git a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.kt b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.kt
index 0e5fd2790..4954c6e96 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.kt
+++ b/autojs/src/main/java/com/stardust/autojs/core/image/capture/ScreenCapturer.kt
@@ -7,7 +7,6 @@ import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.media.Image
import android.media.ImageReader
-import android.media.MediaRecorder
import android.media.projection.MediaProjection
import android.os.Handler
import android.os.Looper
@@ -47,13 +46,13 @@ class ScreenCapturer(
val screenHeight = ScreenMetrics.getOrientationAwareScreenHeight(orientation)
val screenWidth = ScreenMetrics.getOrientationAwareScreenWidth(orientation)
mImageReader = createImageReader(screenWidth, screenHeight)
- mVirtualDisplay = createVirtualDisplay(screenWidth, screenHeight, screenDensity)
mediaProjection.registerCallback(object : MediaProjection.Callback() {
override fun onStop() {
available = false
release()
}
}, mHandler)
+ mVirtualDisplay = createVirtualDisplay(screenWidth, screenHeight, screenDensity)
}
private fun createImageReader(width: Int, height: Int): ImageReader {
diff --git a/autojs/src/main/java/com/stardust/autojs/core/pref/Pref.kt b/autojs/src/main/java/com/stardust/autojs/core/pref/Pref.kt
index 7790ac3f6..d3e8886c6 100644
--- a/autojs/src/main/java/com/stardust/autojs/core/pref/Pref.kt
+++ b/autojs/src/main/java/com/stardust/autojs/core/pref/Pref.kt
@@ -1,5 +1,7 @@
package com.stardust.autojs.core.pref
+import android.content.Context
+import android.content.SharedPreferences
import androidx.preference.PreferenceManager;
import com.stardust.app.GlobalAppContext
@@ -14,4 +16,16 @@ object Pref {
get() {
return preferences.getBoolean("key_gesture_observing", false)
}
+
+ fun getDefault(context: Context): SharedPreferences {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ }
+}
+
+object PrefKey {
+ const val KEY_STABLE_MODE = "key_stable_mode"
+ const val KEY_GESTURE_OBSERVING = "key_gesture_observing"
+ const val KEY_AUTO_BACKUP = "key_auto_backup"
+ const val KEY_FOREGROUND_SERVIE = "key_foreground_servie"
+ const val KEY_USE_VOLUME_CONTROL_RECORD = "key_use_volume_control_record"
}
\ No newline at end of file
diff --git a/autojs/src/main/java/com/stardust/autojs/engine/JavaScriptEngine.java b/autojs/src/main/java/com/stardust/autojs/engine/JavaScriptEngine.java
index 586ade0d4..ddefddd17 100644
--- a/autojs/src/main/java/com/stardust/autojs/engine/JavaScriptEngine.java
+++ b/autojs/src/main/java/com/stardust/autojs/engine/JavaScriptEngine.java
@@ -1,5 +1,7 @@
package com.stardust.autojs.engine;
+import androidx.annotation.NonNull;
+
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.script.JavaScriptSource;
import com.stardust.autojs.script.ScriptSource;
@@ -8,7 +10,8 @@
* Created by Stardust on 2017/8/3.
*/
-public abstract class JavaScriptEngine extends ScriptEngine.AbstractScriptEngine {
+public abstract class JavaScriptEngine extends ScriptEngine.AbstractScriptEngine
+ implements ScriptEngine.EngineEvent {
private ScriptRuntime mRuntime;
private Object mExecArgv;
@@ -35,7 +38,8 @@ public void setRuntime(ScriptRuntime runtime) {
put("runtime", runtime);
}
- public void emit(String eventName, Object... args) {
+ @Override
+ public void emit(@NonNull String eventName, Object... args) {
mRuntime.timers.getMainTimer().postDelayed(() -> mRuntime.events.emit(eventName, args), 0);
}
diff --git a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.kt b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.kt
index 6bfefd283..323d73db8 100644
--- a/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.kt
+++ b/autojs/src/main/java/com/stardust/autojs/engine/ScriptEngine.kt
@@ -20,31 +20,28 @@ import java.util.concurrent.atomic.AtomicInteger
* If you want to stop the engine in other threads, you should call [ScriptEngine.forceStop].
*/
interface ScriptEngine {
+ var id: Int
+ val isDestroyed: Boolean
+ val uncaughtException: Throwable?
fun put(name: String, value: Any?)
fun execute(scriptSource: S): Any?
fun forceStop()
fun destroy()
- val isDestroyed: Boolean
fun setTag(key: String, value: Any?)
fun getTag(key: String): Any?
fun cwd(): String?
fun uncaughtException(throwable: Throwable?)
- val uncaughtException: Throwable?
- var id: Int
-
- /**
- * @hide
- */
fun setOnDestroyListener(listener: OnDestroyListener)
-
- /**
- * @hide
- */
fun init()
+
interface OnDestroyListener {
fun onDestroy(engine: ScriptEngine<*>)
}
+ interface EngineEvent {
+ fun emit(name: String, vararg args: Any?)
+ }
+
abstract class AbstractScriptEngine : ScriptEngine {
private val mTags: MutableMap = ConcurrentHashMap()
diff --git a/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt b/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt
index d4b37f282..f15134b28 100644
--- a/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt
+++ b/autojs/src/main/java/com/stardust/autojs/execution/ExecutionConfig.kt
@@ -16,12 +16,7 @@ data class ExecutionConfig(
var loopTimes: Int = 1,
var scriptConfig: ScriptConfig = ScriptConfig()
) : Parcelable {
-
-
- private val mArguments = HashMap()
-
- val arguments: Map
- get() = mArguments
+ val arguments = mutableMapOf()
constructor(parcel: Parcel) : this(
parcel.readString().orEmpty(),
@@ -32,12 +27,12 @@ data class ExecutionConfig(
parcel.readInt()
)
- fun setArgument(key: String, `object`: Any) {
- mArguments[key] = `object`
+ fun setArgument(key: String, `object`: Any?) {
+ arguments[key] = `object`
}
fun getArgument(key: String): Any? {
- return mArguments[key]
+ return arguments[key]
}
override fun equals(other: Any?): Boolean {
@@ -52,7 +47,7 @@ data class ExecutionConfig(
if (delay != other.delay) return false
if (interval != other.interval) return false
if (loopTimes != other.loopTimes) return false
- if (mArguments != other.mArguments) return false
+ if (arguments != other.arguments) return false
return true
}
@@ -64,7 +59,7 @@ data class ExecutionConfig(
result = 31 * result + delay.hashCode()
result = 31 * result + interval.hashCode()
result = 31 * result + loopTimes
- result = 31 * result + mArguments.hashCode()
+ result = 31 * result + arguments.hashCode()
return result
}
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Engines.java b/autojs/src/main/java/com/stardust/autojs/runtime/api/Engines.java
index 78fbaeab7..7f06f1b2e 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Engines.java
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Engines.java
@@ -7,6 +7,7 @@
import com.stardust.autojs.runtime.ScriptRuntime;
import com.stardust.autojs.script.AutoFileSource;
import com.stardust.autojs.script.JavaScriptFileSource;
+import com.stardust.autojs.script.ScriptFile;
import com.stardust.autojs.script.StringScriptSource;
/**
@@ -29,7 +30,7 @@ public ScriptExecution execScript(String name, String script, ExecutionConfig co
}
public ScriptExecution execScriptFile(String path, ExecutionConfig config) {
- return mEngineService.execute(new JavaScriptFileSource(mScriptRuntime.files.path(path)), config);
+ return mEngineService.execute(new ScriptFile(mScriptRuntime.files.path(path)).toSource(), config);
}
public ScriptExecution execAutoFile(String path, ExecutionConfig config) {
diff --git a/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.kt b/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.kt
index 311077ffb..fc4dc1ca8 100644
--- a/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.kt
+++ b/autojs/src/main/java/com/stardust/autojs/runtime/api/Images.kt
@@ -51,14 +51,19 @@ class Images(
val colorFinder: ColorFinder = ColorFinder(mScreenMetrics)
fun requestScreenCapture(orientation: Int): Boolean = runBlocking {
- return@runBlocking runCatching {
+ try {
mScreenCaptureRequester.requestScreenCapture(
mContext, orientation
)
captureScreen()
- }.isSuccess
+ true
+ } catch (e: Exception) {
+ e.printStackTrace()
+ false
+ }
}
- fun stopScreenCapturer(){
+
+ fun stopScreenCapturer() {
mScreenCaptureRequester.recycle()
}
diff --git a/autojs/src/main/res-i18n/values/strings.xml b/autojs/src/main/res-i18n/values/strings.xml
index a89877668..78401b210 100644
--- a/autojs/src/main/res-i18n/values/strings.xml
+++ b/autojs/src/main/res-i18n/values/strings.xml
@@ -21,6 +21,7 @@
打开侧拉菜单
关闭侧拉菜单
没有读取设备信息权限
+ 脚本服务
diff --git a/autojs/src/main/res/values/styles.xml b/autojs/src/main/res/values/styles.xml
index a2f2a0b41..8a31be359 100644
--- a/autojs/src/main/res/values/styles.xml
+++ b/autojs/src/main/res/values/styles.xml
@@ -27,6 +27,12 @@
- true
- false
+
+