feat():实现命令行启动
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
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 {
|
||||
|
||||
@@ -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<SnackbarHostState>()
|
||||
|
||||
Reference in New Issue
Block a user