feat():实现命令行启动
This commit is contained in:
@@ -10,6 +10,7 @@ import androidx.activity.compose.setContent
|
|||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.models.AdbLaunchParams
|
||||||
import io.github.miuzarte.scrcpyforandroid.pages.MainScreen
|
import io.github.miuzarte.scrcpyforandroid.pages.MainScreen
|
||||||
import io.github.miuzarte.scrcpyforandroid.password.BiometricGate
|
import io.github.miuzarte.scrcpyforandroid.password.BiometricGate
|
||||||
import io.github.miuzarte.scrcpyforandroid.password.PasswordRepository
|
import io.github.miuzarte.scrcpyforandroid.password.PasswordRepository
|
||||||
@@ -44,6 +45,11 @@ class MainActivity: FragmentActivity() {
|
|||||||
AppRuntime.init(applicationContext)
|
AppRuntime.init(applicationContext)
|
||||||
AppScreenOn.register(window)
|
AppScreenOn.register(window)
|
||||||
|
|
||||||
|
// 解析命令行启动参数(adb shell am start --es host ...)
|
||||||
|
AdbLaunchParams.fromIntent(intent)?.let {
|
||||||
|
AppRuntime.adbLaunchParams = it
|
||||||
|
}
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
PasswordRepository.refresh()
|
PasswordRepository.refresh()
|
||||||
val cached = getAppLanguageTag(applicationContext)
|
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.startAutoReconnectLoop() }
|
||||||
LaunchedEffect(Unit) { viewModel.startProfileIdSync() }
|
LaunchedEffect(Unit) { viewModel.startProfileIdSync() }
|
||||||
LaunchedEffect(Unit) { viewModel.startRecentTasksAutoRefresh() }
|
LaunchedEffect(Unit) { viewModel.startRecentTasksAutoRefresh() }
|
||||||
|
LaunchedEffect(Unit) { viewModel.processLaunchParams() }
|
||||||
|
|
||||||
fun openFullscreenControl() {
|
fun openFullscreenControl() {
|
||||||
if (viewModel.shouldOpenFullscreenCompat())
|
if (viewModel.shouldOpenFullscreenCompat())
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import io.github.miuzarte.scrcpyforandroid.R
|
import io.github.miuzarte.scrcpyforandroid.R
|
||||||
import io.github.miuzarte.scrcpyforandroid.StreamActivity
|
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.ConnectionTarget
|
||||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
|
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
|
||||||
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
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.scrcpy.Scrcpy
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.*
|
import io.github.miuzarte.scrcpyforandroid.services.*
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
||||||
@@ -512,6 +514,7 @@ internal class DeviceTabViewModel(
|
|||||||
autoStartScrcpy: Boolean = false,
|
autoStartScrcpy: Boolean = false,
|
||||||
autoEnterFullScreen: Boolean = false,
|
autoEnterFullScreen: Boolean = false,
|
||||||
scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
|
scrcpyProfileId: String = ScrcpyOptions.GLOBAL_PROFILE_ID,
|
||||||
|
scrcpyOptionsOverride: ((ClientOptions) -> ClientOptions)? = null,
|
||||||
) {
|
) {
|
||||||
val connected = connectionController.handleAdbConnected(host, port, scrcpyProfileId)
|
val connected = connectionController.handleAdbConnected(host, port, scrcpyProfileId)
|
||||||
val info = connected.info
|
val info = connected.info
|
||||||
@@ -551,7 +554,10 @@ internal class DeviceTabViewModel(
|
|||||||
|
|
||||||
if (autoStartScrcpy && sessionInfo.value == null) {
|
if (autoStartScrcpy && sessionInfo.value == null) {
|
||||||
runBusy(EventLogMessage.Resource(R.string.vm_start_scrcpy)) {
|
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(
|
suspend fun startScrcpySession(
|
||||||
openFullscreen: Boolean = false,
|
openFullscreen: Boolean = false,
|
||||||
startAppOverride: String? = null,
|
startAppOverride: String? = null,
|
||||||
|
extraOptions: ((ClientOptions) -> ClientOptions)? = null,
|
||||||
) {
|
) {
|
||||||
val activeBundle = resolveScrcpyBundle(connectedScrcpyProfileId.value)
|
val activeBundle = resolveScrcpyBundle(connectedScrcpyProfileId.value)
|
||||||
val options = scrcpyOptions.toClientOptions(activeBundle).fix()
|
val options = scrcpyOptions.toClientOptions(activeBundle).fix()
|
||||||
val resolvedOptions = startAppOverride
|
val resolvedOptions = extraOptions?.invoke(
|
||||||
?.takeIf { it.isNotBlank() }
|
startAppOverride
|
||||||
?.let { options.copy(startApp = it) }
|
?.takeIf { it.isNotBlank() }
|
||||||
?: options
|
?.let { options.copy(startApp = it) }
|
||||||
|
?: options
|
||||||
|
) ?: (
|
||||||
|
startAppOverride
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { options.copy(startApp = it) }
|
||||||
|
?: options
|
||||||
|
)
|
||||||
val session = scrcpy.start(resolvedOptions)
|
val session = scrcpy.start(resolvedOptions)
|
||||||
_pendingScrollToPreview.value = resolvedOptions.video && resolvedOptions.videoPlayback
|
_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?) {
|
fun onDisconnectCurrent(target: ConnectionTarget?) {
|
||||||
runAdbConnect(EventLogMessage.Resource(R.string.vm_disconnect_adb)) {
|
runAdbConnect(EventLogMessage.Resource(R.string.vm_disconnect_adb)) {
|
||||||
target?.let {
|
target?.let {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package io.github.miuzarte.scrcpyforandroid.services
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
|
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.nativecore.AdbMdnsDiscoverer
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -28,6 +29,7 @@ object AppRuntime {
|
|||||||
var scrcpy: Scrcpy? = null
|
var scrcpy: Scrcpy? = null
|
||||||
var currentConnectionTarget: ConnectionTarget? = null
|
var currentConnectionTarget: ConnectionTarget? = null
|
||||||
var currentConnectedDevice: ConnectedDeviceInfo? = null
|
var currentConnectedDevice: ConnectedDeviceInfo? = null
|
||||||
|
var adbLaunchParams: AdbLaunchParams? = null
|
||||||
|
|
||||||
private val snackbarHostStateLock = Any()
|
private val snackbarHostStateLock = Any()
|
||||||
private val snackbarHostStateStack = mutableListOf<SnackbarHostState>()
|
private val snackbarHostStateStack = mutableListOf<SnackbarHostState>()
|
||||||
|
|||||||
Reference in New Issue
Block a user