improve: implement more scrcpy options
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,3 +34,5 @@ google-services.json
|
|||||||
*.hprof
|
*.hprof
|
||||||
|
|
||||||
.kiro/
|
.kiro/
|
||||||
|
|
||||||
|
scrcpy.help*
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ fun Preset<Int>.indexOfOrNearest(raw: String): Int {
|
|||||||
object ScrcpyPresets {
|
object ScrcpyPresets {
|
||||||
val MaxSize = Preset(listOf(0, 720, 1080, 1280, 1600, 1920, 2160, 2560, 3200, 3840)) // px
|
val MaxSize = Preset(listOf(0, 720, 1080, 1280, 1600, 1920, 2160, 2560, 3200, 3840)) // px
|
||||||
val MaxFPS = Preset(listOf(0, 24, 30, 45, 60, 90, 120)) // fps
|
val MaxFPS = Preset(listOf(0, 24, 30, 45, 60, 90, 120)) // fps
|
||||||
|
val ScreenOffTimeout = Preset(listOf(0, 15, 30, 60, 120, 300, 600)) // sec
|
||||||
val AudioBitRate = Preset(listOf(0, 32, 64, 96, 128, 160, 192, 256, 320, 384, 512)) // Kbps
|
val AudioBitRate = Preset(listOf(0, 32, 64, 96, 128, 160, 192, 256, 320, 384, 512)) // Kbps
|
||||||
val CameraFps = Preset(listOf(0, 10, 15, 24, 30, 60, 120, 240, 480, 960)) // fps
|
val CameraFps = Preset(listOf(0, 10, 15, 24, 30, 60, 120, 240, 480, 960)) // fps
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,41 @@ object NativeAdbService {
|
|||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun startApp(
|
||||||
|
packageName: String,
|
||||||
|
displayId: Int? = null,
|
||||||
|
forceStop: Boolean = false,
|
||||||
|
): String = mutex.withLock {
|
||||||
|
val normalizedPackageName = packageName.trim()
|
||||||
|
require(normalizedPackageName.isNotBlank()) { "package name is blank" }
|
||||||
|
|
||||||
|
if (forceStop) {
|
||||||
|
requireConnection().shell(
|
||||||
|
"am force-stop ${quoteShellArg(normalizedPackageName)}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolveOutput = requireConnection().shell(
|
||||||
|
"cmd package resolve-activity --brief ${quoteShellArg(normalizedPackageName)}"
|
||||||
|
)
|
||||||
|
val componentName = resolveOutput
|
||||||
|
.lineSequence()
|
||||||
|
.map(String::trim)
|
||||||
|
.lastOrNull { '/' in it }
|
||||||
|
?: throw IllegalStateException(
|
||||||
|
"Cannot resolve launch activity for $normalizedPackageName"
|
||||||
|
)
|
||||||
|
|
||||||
|
val displayArg = displayId
|
||||||
|
?.takeIf { it >= 0 }
|
||||||
|
?.let { " --display $it" }
|
||||||
|
.orEmpty()
|
||||||
|
val command = "am start-activity$displayArg -n ${quoteShellArg(componentName)}"
|
||||||
|
val response = requireConnection().shell(command)
|
||||||
|
Log.d(TAG, "startApp(): package=$normalizedPackageName component=$componentName")
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun openShellStream(command: String): AdbSocketStream = mutex.withLock {
|
suspend fun openShellStream(command: String): AdbSocketStream = mutex.withLock {
|
||||||
requireConnection().openStream("shell:$command")
|
requireConnection().openStream("shell:$command")
|
||||||
}
|
}
|
||||||
@@ -161,5 +196,9 @@ object NativeAdbService {
|
|||||||
?: throw IllegalStateException("ADB not connected")
|
?: throw IllegalStateException("ADB not connected")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun quoteShellArg(value: String): String {
|
||||||
|
return "'" + value.replace("'", "'\\''") + "'"
|
||||||
|
}
|
||||||
|
|
||||||
private const val TAG = "NativeAdbService"
|
private const val TAG = "NativeAdbService"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcuts
|
|||||||
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
|
import io.github.miuzarte.scrcpyforandroid.nativecore.NativeAdbService
|
||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.AppWakeLocks
|
import io.github.miuzarte.scrcpyforandroid.services.AppWakeLocks
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
import io.github.miuzarte.scrcpyforandroid.services.EventLogger
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
import io.github.miuzarte.scrcpyforandroid.services.EventLogger.logEvent
|
||||||
@@ -89,6 +90,13 @@ private const val ADB_TCP_PROBE_TIMEOUT_MS = 500
|
|||||||
private const val PREVIEW_CARD_ITEM_KEY = "preview_card"
|
private const val PREVIEW_CARD_ITEM_KEY = "preview_card"
|
||||||
private const val PREVIEW_CARD_ITEM_INDEX = 3
|
private const val PREVIEW_CARD_ITEM_INDEX = 3
|
||||||
|
|
||||||
|
private data class StartAppRequest(
|
||||||
|
val packageName: String,
|
||||||
|
val displayId: Int?,
|
||||||
|
val forceStop: Boolean,
|
||||||
|
val matchedAppLabel: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeviceTabScreen(
|
fun DeviceTabScreen(
|
||||||
scrollBehavior: ScrollBehavior,
|
scrollBehavior: ScrollBehavior,
|
||||||
@@ -517,11 +525,102 @@ fun DeviceTabPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun resolveStartAppRequest(
|
||||||
|
scrcpy: Scrcpy,
|
||||||
|
options: io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions,
|
||||||
|
): StartAppRequest? {
|
||||||
|
val raw = options.startApp.trim()
|
||||||
|
if (raw.isBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = raw
|
||||||
|
val forceStop = query.startsWith("+")
|
||||||
|
if (forceStop) {
|
||||||
|
query = query.drop(1).trimStart()
|
||||||
|
}
|
||||||
|
require(query.isNotBlank()) { "应用名或包名不能为空" }
|
||||||
|
|
||||||
|
val displayId = when {
|
||||||
|
options.videoSource != VideoSource.DISPLAY -> null
|
||||||
|
options.displayId >= 0 -> options.displayId
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.startsWith("?")) {
|
||||||
|
return StartAppRequest(
|
||||||
|
packageName = query,
|
||||||
|
displayId = displayId,
|
||||||
|
forceStop = forceStop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val searchName = query.drop(1).trim()
|
||||||
|
require(searchName.isNotBlank()) { "应用名称不能为空" }
|
||||||
|
|
||||||
|
val apps = scrcpy.listings.getApps(forceRefresh = false)
|
||||||
|
val matches = apps.filter { it.label.startsWith(searchName, ignoreCase = true) }
|
||||||
|
|
||||||
|
require(matches.isNotEmpty()) { "未找到应用名以 \"$searchName\" 开头的应用" }
|
||||||
|
require(matches.size == 1) {
|
||||||
|
"按名称匹配到多个应用: " +
|
||||||
|
matches.take(5).joinToString { "${it.label} (${it.packageName})" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return StartAppRequest(
|
||||||
|
packageName = matches[0].packageName,
|
||||||
|
displayId = displayId,
|
||||||
|
forceStop = forceStop,
|
||||||
|
matchedAppLabel = matches[0].label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun startScrcpySession(openFullscreen: Boolean = false) {
|
suspend fun startScrcpySession(openFullscreen: Boolean = false) {
|
||||||
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
||||||
val session = scrcpy.start(options)
|
val session = scrcpy.start(options)
|
||||||
pendingScrollToPreview = true
|
pendingScrollToPreview = true
|
||||||
if (openFullscreen) withContext(Dispatchers.Main) {
|
val startAppRequest = runCatching {
|
||||||
|
resolveStartAppRequest(scrcpy, options)
|
||||||
|
}.getOrElse { error ->
|
||||||
|
logEvent(
|
||||||
|
"启动应用请求无效: ${
|
||||||
|
error.message?.takeIf { it.isNotBlank() } ?: error.javaClass.simpleName
|
||||||
|
}",
|
||||||
|
Log.WARN,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
startAppRequest?.let { request ->
|
||||||
|
if (options.newDisplay.isNotBlank() && request.displayId == null) {
|
||||||
|
logEvent(
|
||||||
|
"当前实现无法获取 new display 的真实 displayId,应用会启动到默认显示",
|
||||||
|
Log.WARN,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
NativeAdbService.startApp(
|
||||||
|
packageName = request.packageName,
|
||||||
|
displayId = request.displayId,
|
||||||
|
forceStop = request.forceStop,
|
||||||
|
)
|
||||||
|
}.onSuccess {
|
||||||
|
val appLabelPart = request.matchedAppLabel?.let { " ($it)" }.orEmpty()
|
||||||
|
logEvent(
|
||||||
|
"已启动应用: ${request.packageName}$appLabelPart" +
|
||||||
|
request.displayId?.let { " @display=$it" }.orEmpty()
|
||||||
|
)
|
||||||
|
}.onFailure { error ->
|
||||||
|
logEvent(
|
||||||
|
"启动应用失败: ${request.packageName} (${
|
||||||
|
error.message?.takeIf { it.isNotBlank() } ?: error.javaClass.simpleName
|
||||||
|
})",
|
||||||
|
Log.WARN,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.fullscreen || openFullscreen) withContext(Dispatchers.Main) {
|
||||||
context.startActivity(StreamActivity.createIntent(context))
|
context.startActivity(StreamActivity.createIntent(context))
|
||||||
}
|
}
|
||||||
if (options.disableScreensaver)
|
if (options.disableScreensaver)
|
||||||
@@ -549,6 +648,15 @@ fun DeviceTabPage(
|
|||||||
snackbar.show("scrcpy 已启动")
|
snackbar.show("scrcpy 已启动")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun stopScrcpySession() {
|
||||||
|
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
||||||
|
scrcpy.stop()
|
||||||
|
if (options.killAdbOnClose) {
|
||||||
|
// TODO
|
||||||
|
disconnectAdbConnection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(pendingScrollToPreview, isPreviewCardVisible) {
|
LaunchedEffect(pendingScrollToPreview, isPreviewCardVisible) {
|
||||||
if (!pendingScrollToPreview) return@LaunchedEffect
|
if (!pendingScrollToPreview) return@LaunchedEffect
|
||||||
if (isPreviewCardVisible) return@LaunchedEffect
|
if (isPreviewCardVisible) return@LaunchedEffect
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -40,8 +41,13 @@ import io.github.miuzarte.scrcpyforandroid.scaffolds.LazyColumn
|
|||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperSlider
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperSlider
|
||||||
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperTextField
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Scrcpy
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.DisplayImePolicy
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.LogLevel
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Tick
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.VideoSource
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
import io.github.miuzarte.scrcpyforandroid.services.LocalSnackbarController
|
||||||
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
import io.github.miuzarte.scrcpyforandroid.storage.Settings
|
||||||
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
import io.github.miuzarte.scrcpyforandroid.storage.Storage.scrcpyOptions
|
||||||
@@ -61,6 +67,9 @@ import top.yukonga.miuix.kmp.basic.SpinnerEntry
|
|||||||
import top.yukonga.miuix.kmp.basic.Text
|
import top.yukonga.miuix.kmp.basic.Text
|
||||||
import top.yukonga.miuix.kmp.basic.TextField
|
import top.yukonga.miuix.kmp.basic.TextField
|
||||||
import top.yukonga.miuix.kmp.basic.TopAppBar
|
import top.yukonga.miuix.kmp.basic.TopAppBar
|
||||||
|
import top.yukonga.miuix.kmp.icon.MiuixIcons
|
||||||
|
import top.yukonga.miuix.kmp.icon.extended.Store
|
||||||
|
import top.yukonga.miuix.kmp.icon.extended.Tune
|
||||||
import top.yukonga.miuix.kmp.preference.ArrowPreference
|
import top.yukonga.miuix.kmp.preference.ArrowPreference
|
||||||
import top.yukonga.miuix.kmp.preference.OverlayDropdownPreference
|
import top.yukonga.miuix.kmp.preference.OverlayDropdownPreference
|
||||||
import top.yukonga.miuix.kmp.preference.OverlaySpinnerPreference
|
import top.yukonga.miuix.kmp.preference.OverlaySpinnerPreference
|
||||||
@@ -153,26 +162,50 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
.coerceAtLeast(0)
|
.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoSourceItems = rememberSaveable { Shared.VideoSource.entries.map { it.string } }
|
val videoSourceItems = rememberSaveable { VideoSource.entries.map { it.string } }
|
||||||
val videoSourceIndex = rememberSaveable(soBundle.videoSource) {
|
val videoSourceIndex = rememberSaveable(soBundle.videoSource) {
|
||||||
Shared.VideoSource.entries
|
VideoSource.entries
|
||||||
.indexOfFirst { it.string == soBundle.videoSource }
|
.indexOfFirst { it.string == soBundle.videoSource }
|
||||||
.coerceAtLeast(0)
|
.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayIdInput by rememberSaveable(soBundle.displayId) {
|
val displays = scrcpy.listings.displays
|
||||||
mutableStateOf(
|
val displayDropdownItems = rememberSaveable(displays, listRefreshVersion) {
|
||||||
if (soBundle.displayId == -1) ""
|
listOf("默认") + displays.map { "${it.id} (${it.width}x${it.height})" }
|
||||||
else soBundle.displayId.toString()
|
}
|
||||||
)
|
val displayDropdownIndex = rememberSaveable(
|
||||||
|
soBundle.displayId,
|
||||||
|
displays,
|
||||||
|
listRefreshVersion,
|
||||||
|
) {
|
||||||
|
(displays.indexOfFirst { it.id == soBundle.displayId } + 1).coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var cameraIdInput by rememberSaveable(soBundle.cameraId) {
|
val cameras = scrcpy.listings.cameras
|
||||||
mutableStateOf(soBundle.cameraId)
|
val cameraDropdownItems = rememberSaveable(cameras, listRefreshVersion) {
|
||||||
|
listOf("默认") + cameras.map { info ->
|
||||||
|
buildString {
|
||||||
|
append(info.id)
|
||||||
|
append(" (")
|
||||||
|
append(info.facing.string)
|
||||||
|
if (info.activeSize.isNotBlank()) {
|
||||||
|
append(", ")
|
||||||
|
append(info.activeSize)
|
||||||
|
}
|
||||||
|
append(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cameraDropdownIndex = rememberSaveable(
|
||||||
|
soBundle.cameraId,
|
||||||
|
cameras,
|
||||||
|
listRefreshVersion,
|
||||||
|
) {
|
||||||
|
(cameras.indexOfFirst { it.id == soBundle.cameraId } + 1).coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val cameraFacingItems = rememberSaveable {
|
val cameraFacingItems = rememberSaveable {
|
||||||
listOf("默认") + Shared.CameraFacing.entries
|
listOf("默认") + CameraFacing.entries
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.map { it.string }
|
.map { it.string }
|
||||||
}
|
}
|
||||||
@@ -180,7 +213,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
if (soBundle.cameraFacing.isEmpty()) {
|
if (soBundle.cameraFacing.isEmpty()) {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
val idx = Shared.CameraFacing.entries
|
val idx = CameraFacing.entries
|
||||||
.indexOfFirst { it.string == soBundle.cameraFacing }
|
.indexOfFirst { it.string == soBundle.cameraFacing }
|
||||||
if (idx > 0) idx else 0
|
if (idx > 0) idx else 0
|
||||||
}
|
}
|
||||||
@@ -192,24 +225,23 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
|
|
||||||
val cameraSizes = scrcpy.listings.cameraSizes
|
val cameraSizes = scrcpy.listings.cameraSizes
|
||||||
val cameraSizeDropdownItems = rememberSaveable(cameraSizes, listRefreshVersion) {
|
val cameraSizeDropdownItems = rememberSaveable(cameraSizes, listRefreshVersion) {
|
||||||
listOf("自动", "自定义") + cameraSizes
|
listOf("默认", "自定义") + cameraSizes
|
||||||
}
|
}
|
||||||
var cameraSizeDropdownIndex by rememberSaveable(
|
val cameraSizeDropdownIndex = rememberSaveable(
|
||||||
soBundle.cameraSize,
|
soBundle.cameraSize,
|
||||||
|
soBundle.cameraSizeCustom,
|
||||||
soBundle.cameraSizeUseCustom,
|
soBundle.cameraSizeUseCustom,
|
||||||
cameraSizes,
|
cameraSizes,
|
||||||
listRefreshVersion,
|
listRefreshVersion,
|
||||||
) {
|
) {
|
||||||
mutableIntStateOf(
|
when {
|
||||||
when {
|
soBundle.cameraSizeUseCustom -> 1
|
||||||
soBundle.cameraSizeUseCustom -> 1 // "自定义"
|
soBundle.cameraSize.isEmpty() -> 0
|
||||||
soBundle.cameraSize.isEmpty() -> 0 // "自动"
|
soBundle.cameraSize in cameraSizes ->
|
||||||
soBundle.cameraSize in cameraSizes ->
|
cameraSizes.indexOf(soBundle.cameraSize) + 2
|
||||||
cameraSizes.indexOf(soBundle.cameraSize) + 2
|
|
||||||
|
|
||||||
else -> 0 // 默认自动
|
else -> 0
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var cameraArInput by rememberSaveable(soBundle.cameraAr) {
|
var cameraArInput by rememberSaveable(soBundle.cameraAr) {
|
||||||
@@ -220,28 +252,19 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
ScrcpyPresets.CameraFps.indexOfOrNearest(soBundle.cameraFps)
|
ScrcpyPresets.CameraFps.indexOfOrNearest(soBundle.cameraFps)
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioSourceItems = rememberSaveable {
|
val screenOffTimeoutPresetIndex = rememberSaveable(soBundle.screenOffTimeout) {
|
||||||
Shared.AudioSource.entries.map { it.string }
|
ScrcpyPresets.ScreenOffTimeout.indexOfOrNearest(
|
||||||
}
|
Tick(soBundle.screenOffTimeout).sec.toInt().coerceAtLeast(0)
|
||||||
val audioSourceIndex = rememberSaveable(soBundle.audioSource) {
|
)
|
||||||
Shared.AudioSource.entries
|
|
||||||
.indexOfFirst { it.string == soBundle.audioSource }
|
|
||||||
.coerceAtLeast(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoEncoderInfos = scrcpy.listings.videoEncoders
|
val audioSourceItems = rememberSaveable {
|
||||||
val audioEncoderInfos = scrcpy.listings.audioEncoders
|
AudioSource.entries.map { it.string }
|
||||||
val videoEncoders = remember(videoEncoderInfos) {
|
|
||||||
videoEncoderInfos.map { it.id }
|
|
||||||
}
|
}
|
||||||
val audioEncoders = remember(audioEncoderInfos) {
|
val audioSourceIndex = rememberSaveable(soBundle.audioSource) {
|
||||||
audioEncoderInfos.map { it.id }
|
AudioSource.entries
|
||||||
}
|
.indexOfFirst { it.string == soBundle.audioSource }
|
||||||
val videoEncoderTypes = remember(videoEncoderInfos) {
|
.coerceAtLeast(0)
|
||||||
videoEncoderInfos.associate { it.id to it.type.s }
|
|
||||||
}
|
|
||||||
val audioEncoderTypes = remember(audioEncoderInfos) {
|
|
||||||
audioEncoderInfos.associate { it.id to it.type.s }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxSizePresetIndex = rememberSaveable(soBundle.maxSize) {
|
val maxSizePresetIndex = rememberSaveable(soBundle.maxSize) {
|
||||||
@@ -260,46 +283,118 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
mutableStateOf(soBundle.audioCodecOptions)
|
mutableStateOf(soBundle.audioCodecOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoEncoderDropdownItems = rememberSaveable(videoEncoders, listRefreshVersion) {
|
val videoEncoders = scrcpy.listings.videoEncoders
|
||||||
listOf("") + videoEncoders
|
val videoEncoderItems by remember(videoEncoders, listRefreshVersion) {
|
||||||
}
|
derivedStateOf {
|
||||||
val videoEncoderIndex =
|
buildList {
|
||||||
rememberSaveable(soBundle.videoEncoder, videoEncoders, listRefreshVersion) {
|
add(SpinnerEntry(title = "自动"))
|
||||||
(videoEncoders.indexOf(soBundle.videoEncoder) + 1).coerceAtLeast(0)
|
videoEncoders.forEach { info ->
|
||||||
|
add(
|
||||||
|
SpinnerEntry(
|
||||||
|
title = info.id,
|
||||||
|
summary = info.type.s,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
val videoEncoderIndex = rememberSaveable(
|
||||||
|
soBundle.videoEncoder,
|
||||||
|
videoEncoders,
|
||||||
|
listRefreshVersion,
|
||||||
|
) {
|
||||||
|
(videoEncoders.indexOfFirst { it.id == soBundle.videoEncoder } + 1)
|
||||||
|
.coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
val audioEncoderDropdownItems = rememberSaveable(audioEncoders, listRefreshVersion) {
|
val audioEncoders = scrcpy.listings.audioEncoders
|
||||||
listOf("") + audioEncoders
|
val audioEncoderItems by remember(audioEncoders, listRefreshVersion) {
|
||||||
|
derivedStateOf {
|
||||||
|
buildList {
|
||||||
|
add(SpinnerEntry(title = "自动"))
|
||||||
|
audioEncoders.forEach { info ->
|
||||||
|
add(
|
||||||
|
SpinnerEntry(
|
||||||
|
title = info.id,
|
||||||
|
summary = info.type.s,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val audioEncoderIndex = rememberSaveable(
|
val audioEncoderIndex = rememberSaveable(
|
||||||
soBundle.audioEncoder,
|
soBundle.audioEncoder,
|
||||||
audioEncoders,
|
audioEncoders,
|
||||||
listRefreshVersion
|
listRefreshVersion,
|
||||||
) {
|
) {
|
||||||
(audioEncoders.indexOf(soBundle.audioEncoder) + 1).coerceAtLeast(0)
|
(audioEncoders.indexOfFirst { it.id == soBundle.audioEncoder } + 1)
|
||||||
|
.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoEncoderEntries = videoEncoderDropdownItems.map { encoderName ->
|
val displayImePolicyItems = rememberSaveable {
|
||||||
if (encoderName == "") {
|
listOf("默认") + DisplayImePolicy.entries
|
||||||
SpinnerEntry(title = "自动")
|
.drop(1)
|
||||||
|
.map { it.string }
|
||||||
|
}
|
||||||
|
val displayImePolicyIndex = rememberSaveable(soBundle.displayImePolicy) {
|
||||||
|
if (soBundle.displayImePolicy.isEmpty()) {
|
||||||
|
0
|
||||||
} else {
|
} else {
|
||||||
SpinnerEntry(
|
val idx = DisplayImePolicy.entries
|
||||||
title = encoderName,
|
.indexOfFirst { it.string == soBundle.displayImePolicy }
|
||||||
summary = videoEncoderTypes[encoderName],
|
if (idx > 0) idx else 0
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioEncoderEntries = audioEncoderDropdownItems.map { encoderName ->
|
val apps = remember(scrcpy.listings.apps, listRefreshVersion) {
|
||||||
if (encoderName == "") {
|
scrcpy.listings.apps.sortedBy { it.packageName }
|
||||||
SpinnerEntry(title = "自动")
|
}
|
||||||
} else {
|
val appDropdownItems by remember(apps, listRefreshVersion) {
|
||||||
SpinnerEntry(
|
derivedStateOf {
|
||||||
title = encoderName,
|
buildList {
|
||||||
summary = audioEncoderTypes[encoderName],
|
add(SpinnerEntry(title = "无"))
|
||||||
)
|
add(SpinnerEntry(title = "自定义"))
|
||||||
|
apps.forEach { app ->
|
||||||
|
add(
|
||||||
|
SpinnerEntry(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector =
|
||||||
|
if (app.system) MiuixIcons.Tune
|
||||||
|
else MiuixIcons.Store,
|
||||||
|
contentDescription = app.label,
|
||||||
|
modifier = Modifier.padding(end = UiSpacing.ContentVertical),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = app.label,
|
||||||
|
summary = app.packageName,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val appDropdownIndex = rememberSaveable(
|
||||||
|
soBundle.startApp,
|
||||||
|
soBundle.startAppCustom,
|
||||||
|
soBundle.startAppUseCustom,
|
||||||
|
apps,
|
||||||
|
listRefreshVersion,
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
soBundle.startAppUseCustom -> 1
|
||||||
|
soBundle.startApp.isEmpty() -> 0
|
||||||
|
apps.any { it.packageName == soBundle.startApp } ->
|
||||||
|
apps.indexOfFirst { it.packageName == soBundle.startApp } + 2
|
||||||
|
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var startAppCustomInput by rememberSaveable(soBundle.startAppCustom) {
|
||||||
|
mutableStateOf(soBundle.startAppCustom)
|
||||||
|
}
|
||||||
|
|
||||||
// [<width>x<height>][/<dpi>]
|
// [<width>x<height>][/<dpi>]
|
||||||
val (ndWidth, ndHeight, ndDpi) = remember(soBundle.newDisplay) {
|
val (ndWidth, ndHeight, ndDpi) = remember(soBundle.newDisplay) {
|
||||||
@@ -332,6 +427,13 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
mutableStateOf(cY?.toString() ?: "")
|
mutableStateOf(cY?.toString() ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val logLevelItems = rememberSaveable { LogLevel.entries.map { it.string } }
|
||||||
|
val logLevelIndex = rememberSaveable(soBundle.logLevel) {
|
||||||
|
LogLevel.entries
|
||||||
|
.indexOfFirst { it.string == soBundle.logLevel }
|
||||||
|
.coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
var serverParamsPreview by rememberSaveable { mutableStateOf("") }
|
var serverParamsPreview by rememberSaveable { mutableStateOf("") }
|
||||||
// 监听选项变化, 自动更新预览
|
// 监听选项变化, 自动更新预览
|
||||||
LaunchedEffect(soBundle) {
|
LaunchedEffect(soBundle) {
|
||||||
@@ -351,7 +453,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
|
|
||||||
serverParamsPreview = clientOptions
|
serverParamsPreview = clientOptions
|
||||||
.toServerParams(0u)
|
.toServerParams(0u)
|
||||||
.toList(simplify = true)
|
.toList(preview = true)
|
||||||
// improve readability using hard line breaks
|
// improve readability using hard line breaks
|
||||||
.joinToString("\n")
|
.joinToString("\n")
|
||||||
}
|
}
|
||||||
@@ -374,19 +476,19 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
item {
|
item {
|
||||||
Card {
|
Card {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = "启动后关闭屏幕",
|
title = "scrcpy 启动后熄灭设备屏幕",
|
||||||
summary = "--turn-screen-off",
|
summary = "--turn-screen-off",
|
||||||
checked = soBundle.turnScreenOff,
|
checked = soBundle.turnScreenOff,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
turnScreenOff = it
|
turnScreenOff = it
|
||||||
)
|
)
|
||||||
if (it) {
|
if (it) snackbar.show(
|
||||||
// github.com/Genymobile/scrcpy/issues/3376
|
// github.com/Genymobile/scrcpy/issues/3376
|
||||||
// github.com/Genymobile/scrcpy/issues/4587
|
// github.com/Genymobile/scrcpy/issues/4587
|
||||||
// github.com/Genymobile/scrcpy/issues/5676
|
// github.com/Genymobile/scrcpy/issues/5676
|
||||||
snackbar.show("注意:大部分设备在关闭屏幕后刷新率会降低/减半")
|
"注意:大部分设备在关闭屏幕后刷新率会降低/减半"
|
||||||
}
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
@@ -424,38 +526,45 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
// enabled = control || video,
|
// enabled = control || video,
|
||||||
)
|
)
|
||||||
SuperSlider(
|
SuperSlider(
|
||||||
title = "投屏过程中受控机的息屏时间",
|
title = "scrcpy 启动后受控机的息屏时间",
|
||||||
summary = "--screen-off-timeout",
|
summary = "--screen-off-timeout",
|
||||||
value = soBundle.screenOffTimeout.coerceAtLeast(0).toFloat(),
|
value = screenOffTimeoutPresetIndex.toFloat(),
|
||||||
onValueChange = {
|
onValueChange = { value ->
|
||||||
|
val idx = value.roundToInt()
|
||||||
|
.coerceIn(0, ScrcpyPresets.ScreenOffTimeout.lastIndex)
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
screenOffTimeout = it.roundToInt()
|
screenOffTimeout = ScrcpyPresets.ScreenOffTimeout[idx]
|
||||||
.takeIf { value -> value > 0 }
|
.takeIf { it > 0 }
|
||||||
?.toLong() ?: -1
|
?.toLong()
|
||||||
|
?.let(Tick::fromSec)
|
||||||
|
?.value
|
||||||
|
?: -1
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
valueRange = 0f..600f,
|
valueRange = 0f..ScrcpyPresets.ScreenOffTimeout.lastIndex.toFloat(),
|
||||||
steps = 600 - 1,
|
steps = (ScrcpyPresets.ScreenOffTimeout.size - 2).coerceAtLeast(0),
|
||||||
unit = "s",
|
unit = "s",
|
||||||
zeroStateText = "默认",
|
zeroStateText = "不修改",
|
||||||
displayText =
|
showKeyPoints = true,
|
||||||
if (soBundle.screenOffTimeout <= 0) "默认"
|
keyPoints = ScrcpyPresets.ScreenOffTimeout.indices.map { it.toFloat() },
|
||||||
else soBundle.screenOffTimeout.toString(),
|
displayText = Tick(soBundle.screenOffTimeout).sec.toString(),
|
||||||
inputInitialValue =
|
inputInitialValue =
|
||||||
if (soBundle.screenOffTimeout <= 0) ""
|
if (soBundle.screenOffTimeout <= 0) ""
|
||||||
else soBundle.screenOffTimeout.toString(),
|
else Tick(soBundle.screenOffTimeout).sec.toString(),
|
||||||
inputFilter = { it.filter(Char::isDigit) },
|
inputFilter = { it.filter(Char::isDigit) },
|
||||||
inputValueRange = 0f..86400f,
|
inputValueRange = 0f..86400f,
|
||||||
onInputConfirm = {
|
onInputConfirm = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
screenOffTimeout = it.toLongOrNull()
|
screenOffTimeout = it.toLongOrNull()
|
||||||
?.takeIf { value -> value > 0 }
|
?.takeIf { value -> value > 0 }
|
||||||
|
?.let(Tick::fromSec)
|
||||||
|
?.value
|
||||||
?: -1
|
?: -1
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = "开始投屏时不唤醒屏幕",
|
title = "scrcpy 启动时不唤醒屏幕",
|
||||||
summary = "--no-power-on",
|
summary = "--no-power-on",
|
||||||
checked = !soBundle.powerOn,
|
checked = !soBundle.powerOn,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -465,7 +574,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = "结束投屏时息屏",
|
title = "scrcpy 结束后息屏",
|
||||||
summary = "--power-off-on-close",
|
summary = "--power-off-on-close",
|
||||||
checked = soBundle.powerOffOnClose,
|
checked = soBundle.powerOffOnClose,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
@@ -474,6 +583,16 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "scrcpy 启动后保持设备唤醒状态(插入电源)",
|
||||||
|
summary = "--stay-awake",
|
||||||
|
checked = soBundle.stayAwake,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
stayAwake = it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = "显示物理触控",
|
title = "显示物理触控",
|
||||||
summary = "--show-touches",
|
summary = "--show-touches",
|
||||||
@@ -485,15 +604,42 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
title = "投屏时保持本机屏幕唤醒",
|
title = "启动后进入全屏",
|
||||||
|
summary = "--fullscreen",
|
||||||
|
checked = soBundle.fullscreen,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
fullscreen = it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "scrcpy 启动后保持本机屏幕唤醒",
|
||||||
summary = "--disable-screensaver",
|
summary = "--disable-screensaver",
|
||||||
checked = soBundle.disableScreensaver,
|
checked = soBundle.disableScreensaver,
|
||||||
onCheckedChange = {
|
onCheckedChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
disableScreensaver = it
|
disableScreensaver = it
|
||||||
)
|
)
|
||||||
|
if (it) snackbar.show(
|
||||||
|
"不保证可用"
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "scrcpy 结束时断开 adb",
|
||||||
|
summary = "--kill-adb-on-close",
|
||||||
|
checked = soBundle.killAdbOnClose,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
killAdbOnClose = it
|
||||||
|
)
|
||||||
|
if (it) snackbar.show(
|
||||||
|
"未实现"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,26 +756,46 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
selectedIndex = videoSourceIndex,
|
selectedIndex = videoSourceIndex,
|
||||||
onSelectedIndexChange = {
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
videoSource = Shared.VideoSource.entries[it].string
|
videoSource = VideoSource.entries[it].string
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
AnimatedVisibility(soBundle.videoSource == "display") {
|
AnimatedVisibility(soBundle.videoSource == "display") {
|
||||||
Column {
|
Column {
|
||||||
SuperTextField(
|
ArrowPreference(
|
||||||
value = displayIdInput,
|
title = "获取 Displays",
|
||||||
onValueChange = { displayIdInput = it },
|
summary = "--list-displays",
|
||||||
onFocusLost = {
|
onClick = {
|
||||||
|
if (refreshBusy) return@ArrowPreference
|
||||||
|
scope.launch {
|
||||||
|
refreshBusy = true
|
||||||
|
snackbar.show("获取中")
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
scrcpy.listings.getDisplays(forceRefresh = true)
|
||||||
|
}
|
||||||
|
listRefreshVersion += 1
|
||||||
|
snackbar.show("获取成功")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackbar.show("刷新失败: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
refreshBusy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
OverlayDropdownPreference(
|
||||||
|
title = "监视器 ID",
|
||||||
|
summary = "--display-id",
|
||||||
|
items = displayDropdownItems,
|
||||||
|
selectedIndex = displayDropdownIndex,
|
||||||
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
displayId = displayIdInput.toIntOrNull() ?: -1
|
displayId =
|
||||||
|
if (it == 0) -1
|
||||||
|
else displays[it - 1].id
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = "--display-id",
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = UiSpacing.Large),
|
|
||||||
)
|
)
|
||||||
SuperSlider(
|
SuperSlider(
|
||||||
title = "最大分辨率",
|
title = "最大分辨率",
|
||||||
@@ -694,19 +860,40 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(soBundle.videoSource == "camera") {
|
AnimatedVisibility(soBundle.videoSource == "camera") {
|
||||||
Column {
|
Column {
|
||||||
SuperTextField(
|
ArrowPreference(
|
||||||
value = cameraIdInput,
|
title = "获取 Cameras",
|
||||||
onValueChange = { cameraIdInput = it },
|
summary = "--list-cameras",
|
||||||
onFocusLost = {
|
onClick = {
|
||||||
|
if (refreshBusy) return@ArrowPreference
|
||||||
|
scope.launch {
|
||||||
|
refreshBusy = true
|
||||||
|
snackbar.show("获取中")
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
scrcpy.listings.getCameras(forceRefresh = true)
|
||||||
|
}
|
||||||
|
listRefreshVersion += 1
|
||||||
|
snackbar.show("获取成功")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackbar.show("刷新失败: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
refreshBusy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
OverlayDropdownPreference(
|
||||||
|
title = "摄像头 ID",
|
||||||
|
summary = "--camera-id",
|
||||||
|
items = cameraDropdownItems,
|
||||||
|
selectedIndex = cameraDropdownIndex,
|
||||||
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
cameraId = cameraIdInput
|
cameraId =
|
||||||
|
if (it == 0) ""
|
||||||
|
else cameras[it - 1].id
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
label = "--camera-id",
|
|
||||||
singleLine = true,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(all = UiSpacing.Large),
|
|
||||||
)
|
)
|
||||||
OverlayDropdownPreference(
|
OverlayDropdownPreference(
|
||||||
title = "摄像头朝向",
|
title = "摄像头朝向",
|
||||||
@@ -717,7 +904,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
cameraFacing =
|
cameraFacing =
|
||||||
if (it == 0) ""
|
if (it == 0) ""
|
||||||
else Shared.CameraFacing.entries[it].string
|
else CameraFacing.entries[it].string
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -749,7 +936,6 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
items = cameraSizeDropdownItems,
|
items = cameraSizeDropdownItems,
|
||||||
selectedIndex = cameraSizeDropdownIndex,
|
selectedIndex = cameraSizeDropdownIndex,
|
||||||
onSelectedIndexChange = {
|
onSelectedIndexChange = {
|
||||||
cameraSizeDropdownIndex = it
|
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// "自动"
|
// "自动"
|
||||||
@@ -765,7 +951,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
cameraSizeUseCustom = true
|
cameraSizeUseCustom = true
|
||||||
)
|
)
|
||||||
cameraSizeCustomInput = soBundle.cameraSize
|
cameraSizeCustomInput = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -785,15 +971,14 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
value = cameraSizeCustomInput,
|
value = cameraSizeCustomInput,
|
||||||
onValueChange = { cameraSizeCustomInput = it },
|
onValueChange = { cameraSizeCustomInput = it },
|
||||||
onFocusLost = {
|
onFocusLost = {
|
||||||
if (cameraSizeCustomInput in cameraSizes) {
|
soBundle = if (cameraSizeCustomInput in cameraSizes) {
|
||||||
// 输入的值存在于列表中, 取消自定义输入
|
// 输入的值存在于列表中, 取消自定义输入
|
||||||
cameraSizeDropdownIndex = cameraSizes
|
soBundle.copy(
|
||||||
.indexOf(cameraSizeCustomInput) + 2
|
cameraSize = cameraSizeCustomInput,
|
||||||
soBundle = soBundle.copy(
|
|
||||||
cameraSizeUseCustom = false
|
cameraSizeUseCustom = false
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
soBundle = soBundle.copy(
|
soBundle.copy(
|
||||||
cameraSizeCustom = cameraSizeCustomInput
|
cameraSizeCustom = cameraSizeCustomInput
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -859,26 +1044,6 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
SwitchPreference(
|
|
||||||
title = "关闭虚拟显示器时保留内容",
|
|
||||||
summary = "--no-vd-destroy-content",
|
|
||||||
checked = !soBundle.vdDestroyContent,
|
|
||||||
onCheckedChange = {
|
|
||||||
soBundle = soBundle.copy(
|
|
||||||
vdDestroyContent = !it
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
SwitchPreference(
|
|
||||||
title = "禁用虚拟显示器系统装饰",
|
|
||||||
summary = "--no-vd-system-decorations",
|
|
||||||
checked = !soBundle.vdSystemDecorations,
|
|
||||||
onCheckedChange = {
|
|
||||||
soBundle = soBundle.copy(
|
|
||||||
vdSystemDecorations = !it
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,7 +1059,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
selectedIndex = audioSourceIndex,
|
selectedIndex = audioSourceIndex,
|
||||||
onSelectedIndexChange = {
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
audioSource = Shared.AudioSource.entries[it].string
|
audioSource = AudioSource.entries[it].string
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -943,7 +1108,7 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
snackbar.show("获取中")
|
snackbar.show("获取中")
|
||||||
try {
|
try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
scrcpy.listings.getVideoEncoders(forceRefresh = true)
|
scrcpy.listings.getEncoders(forceRefresh = true)
|
||||||
}
|
}
|
||||||
listRefreshVersion += 1
|
listRefreshVersion += 1
|
||||||
snackbar.show("获取成功")
|
snackbar.show("获取成功")
|
||||||
@@ -959,11 +1124,13 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
OverlaySpinnerPreference(
|
OverlaySpinnerPreference(
|
||||||
title = "视频编码器",
|
title = "视频编码器",
|
||||||
summary = "--video-encoder",
|
summary = "--video-encoder",
|
||||||
items = videoEncoderEntries,
|
items = videoEncoderItems,
|
||||||
selectedIndex = videoEncoderIndex,
|
selectedIndex = videoEncoderIndex,
|
||||||
onSelectedIndexChange = {
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
videoEncoder = videoEncoderEntries[it].title ?: ""
|
videoEncoder =
|
||||||
|
if (it == 0) ""
|
||||||
|
else videoEncoders[it - 1].id
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -984,11 +1151,13 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
OverlaySpinnerPreference(
|
OverlaySpinnerPreference(
|
||||||
title = "音频编码器",
|
title = "音频编码器",
|
||||||
summary = "--audio-encoder",
|
summary = "--audio-encoder",
|
||||||
items = audioEncoderEntries,
|
items = audioEncoderItems,
|
||||||
selectedIndex = audioEncoderIndex,
|
selectedIndex = audioEncoderIndex,
|
||||||
onSelectedIndexChange = {
|
onSelectedIndexChange = {
|
||||||
soBundle = soBundle.copy(
|
soBundle = soBundle.copy(
|
||||||
audioEncoder = audioEncoderEntries[it].title ?: ""
|
audioEncoder =
|
||||||
|
if (it == 0) ""
|
||||||
|
else audioEncoders[it - 1].id
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1009,6 +1178,160 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (soBundle.videoSource == "display") item {
|
||||||
|
Card {
|
||||||
|
OverlayDropdownPreference(
|
||||||
|
title = "输入法显示策略",
|
||||||
|
summary = "--display-ime-policy",
|
||||||
|
items = displayImePolicyItems,
|
||||||
|
selectedIndex = displayImePolicyIndex,
|
||||||
|
onSelectedIndexChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
displayImePolicy =
|
||||||
|
if (it == 0) ""
|
||||||
|
else DisplayImePolicy.entries[it].string
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "关闭虚拟显示器时保留内容",
|
||||||
|
summary = "--no-vd-destroy-content",
|
||||||
|
checked = !soBundle.vdDestroyContent,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
vdDestroyContent = !it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "禁用虚拟显示器系统装饰",
|
||||||
|
summary = "--no-vd-system-decorations",
|
||||||
|
checked = !soBundle.vdSystemDecorations,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
vdSystemDecorations = !it
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "禁用结束后清理",
|
||||||
|
summary = "--no-downsize-on-error",
|
||||||
|
checked = !soBundle.downsizeOnError,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
downsizeOnError = !it
|
||||||
|
)
|
||||||
|
if (it) snackbar.show(
|
||||||
|
"默认情况下,在 MediaCodec 出错时," +
|
||||||
|
"scrcpy 会自动尝试使用更低的分辨率重新开始。" +
|
||||||
|
"\n此选项将禁用此行为。"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SwitchPreference(
|
||||||
|
title = "禁用结束后清理",
|
||||||
|
summary = "--no-cleanup",
|
||||||
|
checked = !soBundle.cleanup,
|
||||||
|
onCheckedChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
cleanup = !it
|
||||||
|
)
|
||||||
|
if (it) snackbar.show(
|
||||||
|
"默认情况下,scrcpy 会从设备中移除服务器二进制文件," +
|
||||||
|
"并在退出时恢复设备状态(显示触摸、保持唤醒和电源模式)" +
|
||||||
|
"\n此选项将禁用此清理操作"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soBundle.videoSource == "display") item {
|
||||||
|
Card {
|
||||||
|
ArrowPreference(
|
||||||
|
title = "获取应用列表",
|
||||||
|
summary = "--list-apps",
|
||||||
|
onClick = {
|
||||||
|
if (refreshBusy) return@ArrowPreference
|
||||||
|
scope.launch {
|
||||||
|
refreshBusy = true
|
||||||
|
snackbar.show("获取中")
|
||||||
|
try {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
scrcpy.listings.getApps(forceRefresh = true)
|
||||||
|
}
|
||||||
|
listRefreshVersion += 1
|
||||||
|
snackbar.show("获取成功")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
snackbar.show("刷新失败: ${e.message}")
|
||||||
|
} finally {
|
||||||
|
refreshBusy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
OverlaySpinnerPreference(
|
||||||
|
title = "scrcpy 启动后打开应用",
|
||||||
|
summary = "--start-app\nTODO: 未实现虚拟屏配合",
|
||||||
|
items = appDropdownItems,
|
||||||
|
selectedIndex = appDropdownIndex,
|
||||||
|
onSelectedIndexChange = {
|
||||||
|
when (it) {
|
||||||
|
0 -> {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
startApp = "",
|
||||||
|
startAppUseCustom = false,
|
||||||
|
)
|
||||||
|
startAppCustomInput = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
startAppUseCustom = true
|
||||||
|
)
|
||||||
|
startAppCustomInput = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val selectedApp = apps[it - 2]
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
startApp = selectedApp.packageName,
|
||||||
|
startAppUseCustom = false,
|
||||||
|
)
|
||||||
|
startAppCustomInput = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
AnimatedVisibility(soBundle.startAppUseCustom) {
|
||||||
|
SuperTextField(
|
||||||
|
value = startAppCustomInput,
|
||||||
|
onValueChange = { startAppCustomInput = it },
|
||||||
|
onFocusLost = {
|
||||||
|
val matchedAppIndex = apps
|
||||||
|
.indexOfFirst { it.packageName == startAppCustomInput }
|
||||||
|
soBundle = if (matchedAppIndex >= 0) {
|
||||||
|
soBundle.copy(
|
||||||
|
startApp = apps[matchedAppIndex].packageName,
|
||||||
|
startAppUseCustom = false,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
soBundle.copy(
|
||||||
|
startAppCustom = startAppCustomInput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = "--start-app",
|
||||||
|
useLabelAsPlaceholder = true,
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = UiSpacing.Large),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (soBundle.videoSource == "display") item {
|
if (soBundle.videoSource == "display") item {
|
||||||
Card {
|
Card {
|
||||||
Column(
|
Column(
|
||||||
@@ -1243,6 +1566,22 @@ internal fun ScrcpyAllOptionsPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Card {
|
||||||
|
OverlayDropdownPreference(
|
||||||
|
title = "日志等级",
|
||||||
|
summary = "--verbosity",
|
||||||
|
items = logLevelItems,
|
||||||
|
selectedIndex = logLevelIndex,
|
||||||
|
onSelectedIndexChange = {
|
||||||
|
soBundle = soBundle.copy(
|
||||||
|
logLevel = LogLevel.entries[it].string
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item { Spacer(Modifier.height(UiSpacing.PageBottom)) }
|
item { Spacer(Modifier.height(UiSpacing.PageBottom)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ data class ClientOptions(
|
|||||||
// var clipboardAutosync: Boolean = true, // to server
|
// var clipboardAutosync: Boolean = true, // to server
|
||||||
|
|
||||||
// --no-downsize-on-error
|
// --no-downsize-on-error
|
||||||
// var downsizeOnError: Boolean = true, // to server
|
var downsizeOnError: Boolean = true, // to server
|
||||||
|
|
||||||
// var tcpip: Boolean, // to server
|
// var tcpip: Boolean, // to server
|
||||||
// var tcpipDst: String = "", // to server
|
// var tcpipDst: String = "", // to server
|
||||||
@@ -172,7 +172,7 @@ data class ClientOptions(
|
|||||||
// var selectTcpip: Boolean, // to server
|
// var selectTcpip: Boolean, // to server
|
||||||
|
|
||||||
// --no-cleanup
|
// --no-cleanup
|
||||||
var cleanUp: Boolean = true, // to server
|
var cleanup: Boolean = true, // to server
|
||||||
|
|
||||||
// var startFpsCounter: Boolean,
|
// var startFpsCounter: Boolean,
|
||||||
|
|
||||||
@@ -226,6 +226,12 @@ data class ClientOptions(
|
|||||||
M4A, MKA, OPUS, AAC, FLAC, WAV -> true
|
M4A, MKA, OPUS, AAC, FLAC, WAV -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String) =
|
||||||
|
entries.find { it.string.equals(value, ignoreCase = true) }
|
||||||
|
?: AUTO
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fix(): ClientOptions {
|
fun fix(): ClientOptions {
|
||||||
@@ -552,7 +558,9 @@ data class ClientOptions(
|
|||||||
|
|
||||||
powerOffOnClose = powerOffOnClose,
|
powerOffOnClose = powerOffOnClose,
|
||||||
|
|
||||||
cleanUp = cleanUp,
|
downsizeOnError = downsizeOnError,
|
||||||
|
|
||||||
|
cleanUp = cleanup,
|
||||||
powerOn = powerOn,
|
powerOn = powerOn,
|
||||||
|
|
||||||
// killAdbOnClose == killAdbOnClose, // client side
|
// killAdbOnClose == killAdbOnClose, // client side
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ class Scrcpy(
|
|||||||
return getEncoders(forceRefresh).second
|
return getEncoders(forceRefresh).second
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getEncoders(forceRefresh: Boolean = false)
|
suspend fun getEncoders(forceRefresh: Boolean = false)
|
||||||
: Pair<List<EncoderInfo>, List<EncoderInfo>> {
|
: Pair<List<EncoderInfo>, List<EncoderInfo>> {
|
||||||
if (!forceRefresh && cachedVideoEncoders != null && cachedAudioEncoders != null)
|
if (!forceRefresh && cachedVideoEncoders != null && cachedAudioEncoders != null)
|
||||||
return cachedVideoEncoders.orEmpty() to cachedAudioEncoders.orEmpty()
|
return cachedVideoEncoders.orEmpty() to cachedAudioEncoders.orEmpty()
|
||||||
@@ -455,7 +455,7 @@ class Scrcpy(
|
|||||||
video = false,
|
video = false,
|
||||||
audio = false,
|
audio = false,
|
||||||
control = false,
|
control = false,
|
||||||
cleanUp = false,
|
cleanup = false,
|
||||||
list = list,
|
list = list,
|
||||||
)
|
)
|
||||||
val serverParams = options.toServerParams(scid)
|
val serverParams = options.toServerParams(scid)
|
||||||
@@ -1065,6 +1065,7 @@ class Scrcpy(
|
|||||||
}
|
}
|
||||||
serverLogBuffer.addLast(line)
|
serverLogBuffer.addLast(line)
|
||||||
}
|
}
|
||||||
|
logEvent(line)
|
||||||
Log.i(TAG, "[server:$socketName] $line")
|
Log.i(TAG, "[server:$socketName] $line")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ data class ServerParams(
|
|||||||
|
|
||||||
var powerOffOnClose: Boolean,
|
var powerOffOnClose: Boolean,
|
||||||
|
|
||||||
// var downsizeOnError: Boolean,
|
var downsizeOnError: Boolean,
|
||||||
// var tcpip: Boolean,
|
// var tcpip: Boolean,
|
||||||
// var tcpipDst: String,
|
// var tcpipDst: String,
|
||||||
// var selectUsb: Boolean,
|
// var selectUsb: Boolean,
|
||||||
@@ -107,13 +107,12 @@ data class ServerParams(
|
|||||||
return (extraArgs.toList() + this.toList()).joinToString(SEPARATOR)
|
return (extraArgs.toList() + this.toList()).joinToString(SEPARATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toList(simplify: Boolean = false): MutableList<String> {
|
fun toList(preview: Boolean = false): MutableList<String> {
|
||||||
val cmd = mutableListOf<String>()
|
val cmd = mutableListOf<String>()
|
||||||
|
|
||||||
if (!simplify) {
|
if (!preview) cmd.add("scid=${scid.toString(16)}")
|
||||||
cmd.add("scid=${scid.toString(16)}")
|
|
||||||
cmd.add("log_level=${logLevel.string}")
|
cmd.add("log_level=${logLevel.string}")
|
||||||
}
|
|
||||||
|
|
||||||
if (!video) {
|
if (!video) {
|
||||||
cmd.add("video=false")
|
cmd.add("video=false")
|
||||||
@@ -216,7 +215,7 @@ data class ServerParams(
|
|||||||
require(screenOffTimeout >= 0) {
|
require(screenOffTimeout >= 0) {
|
||||||
"screen_off_timeout must be >= 0"
|
"screen_off_timeout must be >= 0"
|
||||||
}
|
}
|
||||||
cmd.add("screen_off_timeout=${screenOffTimeout.toMs()}")
|
cmd.add("screen_off_timeout=${screenOffTimeout.ms}")
|
||||||
}
|
}
|
||||||
if (videoCodecOptions.isNotBlank()) {
|
if (videoCodecOptions.isNotBlank()) {
|
||||||
validate(videoCodecOptions)
|
validate(videoCodecOptions)
|
||||||
@@ -242,8 +241,6 @@ data class ServerParams(
|
|||||||
if (!clipBoardAutosync) {
|
if (!clipBoardAutosync) {
|
||||||
cmd.add("clipboard_autosync=false")
|
cmd.add("clipboard_autosync=false")
|
||||||
}
|
}
|
||||||
// not implemented
|
|
||||||
val downsizeOnError = false
|
|
||||||
if (!downsizeOnError) {
|
if (!downsizeOnError) {
|
||||||
cmd.add("downsize_on_error=false")
|
cmd.add("downsize_on_error=false")
|
||||||
}
|
}
|
||||||
@@ -285,4 +282,4 @@ data class ServerParams(
|
|||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ class Shared {
|
|||||||
fun toMs(): Long = value / 1000
|
fun toMs(): Long = value / 1000
|
||||||
fun toSec(): Long = value / 1_000_000
|
fun toSec(): Long = value / 1_000_000
|
||||||
fun toSecDouble(): Double = value / 1_000_000.0
|
fun toSecDouble(): Double = value / 1_000_000.0
|
||||||
|
val ns: Long get() = toNs()
|
||||||
|
val us: Long get() = toUs()
|
||||||
|
val ms: Long get() = toMs()
|
||||||
|
val sec: Long get() = toSec()
|
||||||
|
val secDouble: Double get() = toSecDouble()
|
||||||
|
|
||||||
operator fun plus(other: Tick): Tick = Tick(value + other.value)
|
operator fun plus(other: Tick): Tick = Tick(value + other.value)
|
||||||
operator fun minus(other: Tick): Tick = Tick(value - other.value)
|
operator fun minus(other: Tick): Tick = Tick(value - other.value)
|
||||||
@@ -38,17 +43,23 @@ class Shared {
|
|||||||
fun Duration.toTick(): Tick = Tick(this.inWholeMicroseconds)
|
fun Duration.toTick(): Tick = Tick(this.inWholeMicroseconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ListOptions(val value: Int) {
|
enum class ListOptions(val string: String, val value: Int) {
|
||||||
NULL(0x0),
|
NULL("null", 0x0),
|
||||||
ENCODERS(0x1), // --list-encoders
|
ENCODERS("encoders", 0x1), // --list-encoders
|
||||||
DISPLAYS(0x2), // --list-displays
|
DISPLAYS("displays", 0x2), // --list-displays
|
||||||
CAMERAS(0x4), // --list-cameras
|
CAMERAS("cameras", 0x4), // --list-cameras
|
||||||
CAMERA_SIZES(0x8), // --list-camera-sizes
|
CAMERA_SIZES("camera_sizes", 0x8), // --list-camera-sizes
|
||||||
APPS(0x10); // --list-apps
|
APPS("apps", 0x10); // --list-apps
|
||||||
|
|
||||||
infix fun or(other: ListOptions) = value or other.value
|
infix fun or(other: ListOptions) = value or other.value
|
||||||
infix fun and(other: ListOptions) = value and other.value
|
infix fun and(other: ListOptions) = value and other.value
|
||||||
infix fun has(other: ListOptions) = this and other != 0
|
infix fun has(other: ListOptions) = this and other != 0
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String) =
|
||||||
|
entries.find { it.string.equals(value, ignoreCase = true) }
|
||||||
|
?: NULL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class EncoderType(val s: String) {
|
enum class EncoderType(val s: String) {
|
||||||
@@ -62,6 +73,12 @@ class Shared {
|
|||||||
INFO("info"),
|
INFO("info"),
|
||||||
WARN("warn"),
|
WARN("warn"),
|
||||||
ERROR("error");
|
ERROR("error");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String) =
|
||||||
|
entries.find { it.string.equals(value, ignoreCase = true) }
|
||||||
|
?: VERBOSE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Codec(
|
enum class Codec(
|
||||||
@@ -177,6 +194,12 @@ class Shared {
|
|||||||
UNLOCKED("unlocked"), // ignore
|
UNLOCKED("unlocked"), // ignore
|
||||||
LOCKED_VALUE("locked_value"), // "@${orientation.string}"
|
LOCKED_VALUE("locked_value"), // "@${orientation.string}"
|
||||||
LOCKED_INITIAL("locked_initial"); // "@"
|
LOCKED_INITIAL("locked_initial"); // "@"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String) =
|
||||||
|
entries.find { it.string.equals(value, ignoreCase = true) }
|
||||||
|
?: UNLOCKED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DisplayImePolicy(val string: String) {
|
enum class DisplayImePolicy(val string: String) {
|
||||||
@@ -184,5 +207,11 @@ class Shared {
|
|||||||
LOCAL("local"),
|
LOCAL("local"),
|
||||||
FALLBACK("fallback"),
|
FALLBACK("fallback"),
|
||||||
HIDE("hide");
|
HIDE("hide");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(value: String) =
|
||||||
|
entries.find { it.string.equals(value, ignoreCase = true) }
|
||||||
|
?: UNDEFINED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.intPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.longPreferencesKey
|
import androidx.datastore.preferences.core.longPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions
|
||||||
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.ClientOptions.RecordFormat
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.AudioSource
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.CameraFacing
|
||||||
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
import io.github.miuzarte.scrcpyforandroid.scrcpy.Shared.Codec
|
||||||
@@ -74,11 +75,11 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
)
|
)
|
||||||
val LOG_LEVEL = Pair(
|
val LOG_LEVEL = Pair(
|
||||||
stringPreferencesKey("log_level"),
|
stringPreferencesKey("log_level"),
|
||||||
"info",
|
LogLevel.INFO.string,
|
||||||
)
|
)
|
||||||
val VIDEO_CODEC = Pair(
|
val VIDEO_CODEC = Pair(
|
||||||
stringPreferencesKey("video_codec"),
|
stringPreferencesKey("video_codec"),
|
||||||
"h264",
|
Codec.H264.string,
|
||||||
)
|
)
|
||||||
val AUDIO_CODEC = Pair(
|
val AUDIO_CODEC = Pair(
|
||||||
stringPreferencesKey("audio_codec"),
|
stringPreferencesKey("audio_codec"),
|
||||||
@@ -138,7 +139,7 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
)
|
)
|
||||||
val DISPLAY_IME_POLICY = Pair(
|
val DISPLAY_IME_POLICY = Pair(
|
||||||
stringPreferencesKey("display_ime_policy"),
|
stringPreferencesKey("display_ime_policy"),
|
||||||
"undefined",
|
DisplayImePolicy.UNDEFINED.string,
|
||||||
)
|
)
|
||||||
val DISPLAY_ID = Pair(
|
val DISPLAY_ID = Pair(
|
||||||
intPreferencesKey("display_id"),
|
intPreferencesKey("display_id"),
|
||||||
@@ -184,6 +185,10 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
booleanPreferencesKey("power_off_on_close"),
|
booleanPreferencesKey("power_off_on_close"),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
val DOWNSIZE_ON_ERROR = Pair(
|
||||||
|
booleanPreferencesKey("downsize_on_error"),
|
||||||
|
true,
|
||||||
|
)
|
||||||
val CLEANUP = Pair(
|
val CLEANUP = Pair(
|
||||||
booleanPreferencesKey("cleanup"),
|
booleanPreferencesKey("cleanup"),
|
||||||
true,
|
true,
|
||||||
@@ -228,6 +233,14 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
stringPreferencesKey("start_app"),
|
stringPreferencesKey("start_app"),
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
val START_APP_CUSTOM = Pair(
|
||||||
|
stringPreferencesKey("start_app_custom"),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
val START_APP_USE_CUSTOM = Pair(
|
||||||
|
booleanPreferencesKey("start_app_use_custom"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
val VD_DESTROY_CONTENT = Pair(
|
val VD_DESTROY_CONTENT = Pair(
|
||||||
booleanPreferencesKey("vd_destroy_content"),
|
booleanPreferencesKey("vd_destroy_content"),
|
||||||
true,
|
true,
|
||||||
@@ -334,6 +347,7 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
val stayAwake: Boolean,
|
val stayAwake: Boolean,
|
||||||
val disableScreensaver: Boolean,
|
val disableScreensaver: Boolean,
|
||||||
val powerOffOnClose: Boolean,
|
val powerOffOnClose: Boolean,
|
||||||
|
val downsizeOnError: Boolean,
|
||||||
val cleanup: Boolean,
|
val cleanup: Boolean,
|
||||||
val powerOn: Boolean,
|
val powerOn: Boolean,
|
||||||
val video: Boolean,
|
val video: Boolean,
|
||||||
@@ -345,6 +359,8 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
val audioDup: Boolean,
|
val audioDup: Boolean,
|
||||||
val newDisplay: String,
|
val newDisplay: String,
|
||||||
val startApp: String,
|
val startApp: String,
|
||||||
|
val startAppCustom: String,
|
||||||
|
val startAppUseCustom: Boolean,
|
||||||
val vdDestroyContent: Boolean,
|
val vdDestroyContent: Boolean,
|
||||||
val vdSystemDecorations: Boolean
|
val vdSystemDecorations: Boolean
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
@@ -449,6 +465,7 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
stayAwake = preferences.read(STAY_AWAKE),
|
stayAwake = preferences.read(STAY_AWAKE),
|
||||||
disableScreensaver = preferences.read(DISABLE_SCREENSAVER),
|
disableScreensaver = preferences.read(DISABLE_SCREENSAVER),
|
||||||
powerOffOnClose = preferences.read(POWER_OFF_ON_CLOSE),
|
powerOffOnClose = preferences.read(POWER_OFF_ON_CLOSE),
|
||||||
|
downsizeOnError = preferences.read(DOWNSIZE_ON_ERROR),
|
||||||
cleanup = preferences.read(CLEANUP),
|
cleanup = preferences.read(CLEANUP),
|
||||||
powerOn = preferences.read(POWER_ON),
|
powerOn = preferences.read(POWER_ON),
|
||||||
video = preferences.read(VIDEO),
|
video = preferences.read(VIDEO),
|
||||||
@@ -460,6 +477,8 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
audioDup = preferences.read(AUDIO_DUP),
|
audioDup = preferences.read(AUDIO_DUP),
|
||||||
newDisplay = preferences.read(NEW_DISPLAY),
|
newDisplay = preferences.read(NEW_DISPLAY),
|
||||||
startApp = preferences.read(START_APP),
|
startApp = preferences.read(START_APP),
|
||||||
|
startAppCustom = preferences.read(START_APP_CUSTOM),
|
||||||
|
startAppUseCustom = preferences.read(START_APP_USE_CUSTOM),
|
||||||
vdDestroyContent = preferences.read(VD_DESTROY_CONTENT),
|
vdDestroyContent = preferences.read(VD_DESTROY_CONTENT),
|
||||||
vdSystemDecorations = preferences.read(VD_SYSTEM_DECORATIONS),
|
vdSystemDecorations = preferences.read(VD_SYSTEM_DECORATIONS),
|
||||||
)
|
)
|
||||||
@@ -493,12 +512,12 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
cameraSize = if (!bundle.cameraSizeUseCustom) bundle.cameraSize else bundle.cameraSizeCustom,
|
cameraSize = if (!bundle.cameraSizeUseCustom) bundle.cameraSize else bundle.cameraSizeCustom,
|
||||||
cameraAr = bundle.cameraAr,
|
cameraAr = bundle.cameraAr,
|
||||||
cameraFps = bundle.cameraFps.toUShort(),
|
cameraFps = bundle.cameraFps.toUShort(),
|
||||||
logLevel = LogLevel.valueOf(bundle.logLevel.uppercase()),
|
logLevel = LogLevel.fromString(bundle.logLevel),
|
||||||
videoCodec = Codec.fromString(bundle.videoCodec),
|
videoCodec = Codec.fromString(bundle.videoCodec),
|
||||||
audioCodec = Codec.fromString(bundle.audioCodec),
|
audioCodec = Codec.fromString(bundle.audioCodec),
|
||||||
videoSource = VideoSource.fromString(bundle.videoSource),
|
videoSource = VideoSource.fromString(bundle.videoSource),
|
||||||
audioSource = AudioSource.fromString(bundle.audioSource),
|
audioSource = AudioSource.fromString(bundle.audioSource),
|
||||||
recordFormat = ClientOptions.RecordFormat.valueOf(bundle.recordFormat.uppercase()),
|
recordFormat = RecordFormat.fromString(bundle.recordFormat),
|
||||||
cameraFacing = CameraFacing.fromString(bundle.cameraFacing),
|
cameraFacing = CameraFacing.fromString(bundle.cameraFacing),
|
||||||
maxSize = bundle.maxSize.toUShort(),
|
maxSize = bundle.maxSize.toUShort(),
|
||||||
videoBitRate = bundle.videoBitRate,
|
videoBitRate = bundle.videoBitRate,
|
||||||
@@ -506,12 +525,10 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
maxFps = bundle.maxFps,
|
maxFps = bundle.maxFps,
|
||||||
angle = bundle.angle,
|
angle = bundle.angle,
|
||||||
captureOrientation = Orientation.fromInt(bundle.captureOrientation),
|
captureOrientation = Orientation.fromInt(bundle.captureOrientation),
|
||||||
captureOrientationLock = OrientationLock.valueOf(
|
captureOrientationLock = OrientationLock.fromString(bundle.captureOrientationLock),
|
||||||
bundle.captureOrientationLock.uppercase()
|
|
||||||
),
|
|
||||||
displayOrientation = Orientation.fromInt(bundle.displayOrientation),
|
displayOrientation = Orientation.fromInt(bundle.displayOrientation),
|
||||||
recordOrientation = Orientation.fromInt(bundle.recordOrientation),
|
recordOrientation = Orientation.fromInt(bundle.recordOrientation),
|
||||||
displayImePolicy = DisplayImePolicy.valueOf(bundle.displayImePolicy.uppercase()),
|
displayImePolicy = DisplayImePolicy.fromString(bundle.displayImePolicy),
|
||||||
displayId = bundle.displayId,
|
displayId = bundle.displayId,
|
||||||
screenOffTimeout = Tick(bundle.screenOffTimeout),
|
screenOffTimeout = Tick(bundle.screenOffTimeout),
|
||||||
showTouches = bundle.showTouches,
|
showTouches = bundle.showTouches,
|
||||||
@@ -523,17 +540,18 @@ class ScrcpyOptions(context: Context) : Settings(context, "ScrcpyOptions") {
|
|||||||
stayAwake = bundle.stayAwake,
|
stayAwake = bundle.stayAwake,
|
||||||
disableScreensaver = bundle.disableScreensaver,
|
disableScreensaver = bundle.disableScreensaver,
|
||||||
powerOffOnClose = bundle.powerOffOnClose,
|
powerOffOnClose = bundle.powerOffOnClose,
|
||||||
cleanUp = bundle.cleanup,
|
downsizeOnError = bundle.downsizeOnError,
|
||||||
|
cleanup = bundle.cleanup,
|
||||||
powerOn = bundle.powerOn,
|
powerOn = bundle.powerOn,
|
||||||
video = bundle.video,
|
video = bundle.video,
|
||||||
audio = bundle.audio,
|
audio = bundle.audio,
|
||||||
requireAudio = bundle.requireAudio,
|
requireAudio = bundle.requireAudio,
|
||||||
killAdbOnClose = bundle.killAdbOnClose,
|
killAdbOnClose = bundle.killAdbOnClose,
|
||||||
cameraHighSpeed = bundle.cameraHighSpeed,
|
cameraHighSpeed = bundle.cameraHighSpeed,
|
||||||
list = ListOptions.valueOf(bundle.list.uppercase()),
|
list = ListOptions.fromString(bundle.list),
|
||||||
audioDup = bundle.audioDup,
|
audioDup = bundle.audioDup,
|
||||||
newDisplay = bundle.newDisplay,
|
newDisplay = bundle.newDisplay,
|
||||||
startApp = bundle.startApp,
|
startApp = if (!bundle.startAppUseCustom) bundle.startApp else bundle.startAppCustom,
|
||||||
vdDestroyContent = bundle.vdDestroyContent,
|
vdDestroyContent = bundle.vdDestroyContent,
|
||||||
vdSystemDecorations = bundle.vdSystemDecorations
|
vdSystemDecorations = bundle.vdSystemDecorations
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user