From db6c4b9bb88d567af3feaf6490da045b3bc42efd Mon Sep 17 00:00:00 2001 From: "zhimin.liu" Date: Thu, 16 Jul 2026 21:58:53 +0800 Subject: [PATCH] =?UTF-8?q?feat():=E5=AE=9E=E7=8E=B0=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../miuzarte/scrcpyforandroid/MainActivity.kt | 6 ++ .../models/AdbLaunchParams.kt | 72 +++++++++++++++++++ .../scrcpyforandroid/pages/DeviceTabScreen.kt | 1 + .../pages/DeviceTabViewModel.kt | 70 ++++++++++++++++-- .../scrcpyforandroid/services/AppRuntime.kt | 2 + 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/io/github/miuzarte/scrcpyforandroid/models/AdbLaunchParams.kt diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/MainActivity.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/MainActivity.kt index 544e8f3..fb3ba5c 100644 --- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/MainActivity.kt +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/MainActivity.kt @@ -10,6 +10,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.core.content.edit import androidx.fragment.app.FragmentActivity +import io.github.miuzarte.scrcpyforandroid.models.AdbLaunchParams import io.github.miuzarte.scrcpyforandroid.pages.MainScreen import io.github.miuzarte.scrcpyforandroid.password.BiometricGate import io.github.miuzarte.scrcpyforandroid.password.PasswordRepository @@ -44,6 +45,11 @@ class MainActivity: FragmentActivity() { AppRuntime.init(applicationContext) AppScreenOn.register(window) + // 解析命令行启动参数(adb shell am start --es host ...) + AdbLaunchParams.fromIntent(intent)?.let { + AppRuntime.adbLaunchParams = it + } + runBlocking { PasswordRepository.refresh() val cached = getAppLanguageTag(applicationContext) diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/models/AdbLaunchParams.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/models/AdbLaunchParams.kt new file mode 100644 index 0000000..e058fdc --- /dev/null +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/models/AdbLaunchParams.kt @@ -0,0 +1,72 @@ +package io.github.miuzarte.scrcpyforandroid.models + +import android.content.Intent +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + * 从命令行 Intent extras 中解析的启动参数。 + * + * 使用方式(adb shell): + * ``` + * adb shell am start -n io.github.miuzarte.scrcpyforandroid/.MainActivity \ + * --es host "192.168.1.100" \ + * --ei port 5555 \ + * --ei maxSize 1920 \ + * --ei videoBitRate 8000000 \ + * --es maxFps "60" \ + * --ez fullscreen true \ + * --es startApp "com.example.app" + * ``` + */ +@Parcelize +data class AdbLaunchParams( + /** 目标设备 IP/主机名(必填) */ + val host: String, + /** ADB 端口,默认 5555 */ + val port: Int = 5555, + /** 视频最大分辨率,0 表示不限制 */ + val maxSize: Int = 0, + /** 视频码率(bps),0 表示使用默认值 */ + val videoBitRate: Int = 0, + /** 最大帧率,空字符串表示不限制 */ + val maxFps: String = "", + /** 启动后自动进入全屏 */ + val fullscreen: Boolean = false, + /** 在远程设备上启动指定应用包名 */ + val startApp: String = "", + /** 显示触摸点 */ + val showTouches: Boolean = false, + /** 启动后关闭设备屏幕 */ + val turnScreenOff: Boolean = false, + /** 启用触控操作,默认 true */ + val control: Boolean = true, + /** 启用视频流,默认 true */ + val video: Boolean = true, + /** 启用音频流,默认 true */ + val audio: Boolean = true, + /** 关闭后断掉 ADB 连接 */ + val killAdbOnClose: Boolean = false, +) : Parcelable { + companion object { + fun fromIntent(intent: Intent?): AdbLaunchParams? { + intent ?: return null + val host = intent.getStringExtra("host")?.takeIf { it.isNotBlank() } ?: return null + return AdbLaunchParams( + host = host, + port = intent.getIntExtra("port", 5555), + maxSize = intent.getIntExtra("maxSize", 0), + videoBitRate = intent.getIntExtra("videoBitRate", 0), + maxFps = intent.getStringExtra("maxFps") ?: "", + fullscreen = intent.getBooleanExtra("fullscreen", false), + startApp = intent.getStringExtra("startApp") ?: "", + showTouches = intent.getBooleanExtra("showTouches", false), + turnScreenOff = intent.getBooleanExtra("turnScreenOff", false), + control = intent.getBooleanExtra("control", true), + video = intent.getBooleanExtra("video", true), + audio = intent.getBooleanExtra("audio", true), + killAdbOnClose = intent.getBooleanExtra("killAdbOnClose", false), + ) + } + } +} diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabScreen.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabScreen.kt index d9298fc..d96f300 100644 --- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabScreen.kt +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabScreen.kt @@ -230,6 +230,7 @@ internal fun DeviceTabPage( LaunchedEffect(Unit) { viewModel.startAutoReconnectLoop() } LaunchedEffect(Unit) { viewModel.startProfileIdSync() } LaunchedEffect(Unit) { viewModel.startRecentTasksAutoRefresh() } + LaunchedEffect(Unit) { viewModel.processLaunchParams() } fun openFullscreenControl() { if (viewModel.shouldOpenFullscreenCompat()) diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabViewModel.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabViewModel.kt index 229e502..6a828f8 100644 --- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabViewModel.kt +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DeviceTabViewModel.kt @@ -7,9 +7,11 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import io.github.miuzarte.scrcpyforandroid.R import io.github.miuzarte.scrcpyforandroid.StreamActivity +import io.github.miuzarte.scrcpyforandroid.models.AdbLaunchParams import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts +import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy import io.github.miuzarte.scrcpyforandroid.services.* import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent @@ -512,6 +514,7 @@ internal class DeviceTabViewModel( autoStartScrcpy: Boolean = false, autoEnterFullScreen: Boolean = false, scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID, + scrcpyOptionsOverride: ((ClientOptions) -> ClientOptions)? = null, ) { val connected = connectionController.handleAdbConnected(host, port, scrcpyProfileId) val info = connected.info @@ -551,7 +554,10 @@ internal class DeviceTabViewModel( if (autoStartScrcpy && sessionInfo.value == null) { runBusy(EventLogMessage.Resource(R.string.vm_start_scrcpy)) { - startScrcpySession(openFullscreen = autoStartScrcpy && autoEnterFullScreen) + startScrcpySession( + openFullscreen = autoStartScrcpy && autoEnterFullScreen, + extraOptions = scrcpyOptionsOverride, + ) } } } @@ -559,13 +565,21 @@ internal class DeviceTabViewModel( suspend fun startScrcpySession( openFullscreen: Boolean = false, startAppOverride: String? = null, + extraOptions: ((ClientOptions) -> ClientOptions)? = null, ) { val activeBundle = resolveScrcpyBundle(connectedScrcpyProfileId.value) val options = scrcpyOptions.toClientOptions(activeBundle).fix() - val resolvedOptions = startAppOverride - ?.takeIf { it.isNotBlank() } - ?.let { options.copy(startApp = it) } - ?: options + val resolvedOptions = extraOptions?.invoke( + startAppOverride + ?.takeIf { it.isNotBlank() } + ?.let { options.copy(startApp = it) } + ?: options + ) ?: ( + startAppOverride + ?.takeIf { it.isNotBlank() } + ?.let { options.copy(startApp = it) } + ?: options + ) val session = scrcpy.start(resolvedOptions) _pendingScrollToPreview.value = resolvedOptions.video && resolvedOptions.videoPlayback @@ -786,6 +800,52 @@ internal class DeviceTabViewModel( } } + /** + * 处理命令行启动参数:连接到指定设备并自动启动 scrcpy。 + * 从 AppRuntime.adbLaunchParams 读取参数,用后即清。 + */ + fun processLaunchParams() { + val params = AppRuntime.adbLaunchParams ?: return + AppRuntime.adbLaunchParams = null // consume + + runAdbConnect( + label = EventLogMessage.Raw("Auto-connect"), + ) { + disconnectCurrentTargetBeforeConnecting(params.host, params.port) + try { + connectWithTimeout(params.host, params.port) + handleAdbConnected( + host = params.host, + port = params.port, + autoStartScrcpy = true, + autoEnterFullScreen = params.fullscreen, + scrcpyOptionsOverride = { options -> + options.copy( + maxSize = + if (params.maxSize > 0) params.maxSize.toUShort() else options.maxSize, + videoBitRate = + if (params.videoBitRate > 0) params.videoBitRate else options.videoBitRate, + maxFps = params.maxFps.ifBlank { options.maxFps }, + showTouches = params.showTouches, + fullscreen = params.fullscreen, + control = params.control, + video = params.video, + audio = params.audio, + turnScreenOff = params.turnScreenOff, + killAdbOnClose = params.killAdbOnClose, + startApp = params.startApp.ifBlank { options.startApp }, + ) + }, + ) + connectionController.updateQuickConnected(false) + } catch (error: Exception) { + connectionController.markConnectionFailed(error) + logEvent(R.string.vm_adb_connection_failed, level = Log.ERROR, error = error) + AppRuntime.snackbar(R.string.vm_adb_connection_failed) + } + } + } + fun onDisconnectCurrent(target: ConnectionTarget?) { runAdbConnect(EventLogMessage.Resource(R.string.vm_disconnect_adb)) { target?.let { diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppRuntime.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppRuntime.kt index 86bc06d..d687cdb 100644 --- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppRuntime.kt +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppRuntime.kt @@ -3,6 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.services import android.content.Context import androidx.annotation.StringRes import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget +import io.github.miuzarte.scrcpyforandroid.models.AdbLaunchParams import io.github.miuzarte.scrcpyforandroid.nativecore.AdbMdnsDiscoverer import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy import kotlinx.coroutines.CoroutineScope @@ -28,6 +29,7 @@ object AppRuntime { var scrcpy: Scrcpy? = null var currentConnectionTarget: ConnectionTarget? = null var currentConnectedDevice: ConnectedDeviceInfo? = null + var adbLaunchParams: AdbLaunchParams? = null private val snackbarHostStateLock = Any() private val snackbarHostStateStack = mutableListOf()