refactor: impl more scrcpy options; remove unused class/constants

- 移除不再需要的 `AppDefaults` 与 `AppPreferenceKeys`
- 实现了 `--show-touches`, `--no-power-on`, `--power-off-on-close`, `--disable-screensaver`, `--screen-off-timeout=seconds`, `--require-audio`, `--no-vd-destroy-content`, `--no-vd-system-decorations`
This commit is contained in:
Miuzarte
2026-04-10 01:19:20 +08:00
parent 6c1564f461
commit e17e1badaf
14 changed files with 371 additions and 905 deletions

16
TODO.md
View File

@@ -1,26 +1,10 @@
# TODO
## CURRENT
- Refactoring
## WIDGETS
- `SuperTextField` Click to pop a dialog with custom notes/summary
## PARAMS
- orientation locking
- `-r, --record=file.mp4` 投屏时录制到文件 Record screen to file. The format is determined by the --record-format option if set, or by the file extension.
- `--record-format` 录制格式 Force recording format (mp4, mkv, m4a, mka, opus, aac, flac or wav).
- `-t, --show-touches` 显示受控机的物理触控 Enable "show touches" on start, restore the initial value on exit. It only shows physical touches (not clicks from scrcpy).
- `--no-power-on` 开始投屏时不唤醒屏幕 Do not power on the device on start.
- `--power-off-on-close` 结束投屏时息屏 Turn the device screen off when closing scrcpy.
- `--disable-screensaver` 投屏时禁用自动息屏 Disable screensaver while scrcpy is running.
- `--screen-off-timeout=seconds` 投屏过程中的息屏时间 Set the screen off timeout while scrcpy is running (restore the initial value on exit).
- `--require-audio` This option makes scrcpy fail if audio is enabled but does not work.
- `--no-vd-destroy-content` 投屏结束关闭虚拟显示器时不结束进程 Disable virtual display "destroy content on removal" flag. With this option, when the virtual display is closed, the running apps are moved to the main display rather than being destroyed.
- `--no-vd-system-decorations` Disable virtual display system decorations flag.
## FEATURES

View File

@@ -1,82 +0,0 @@
package io.github.miuzarte.scrcpyforandroid.constants
object AppDefaults {
const val EVENT_LOG_LINES = 512
const val ADB_PORT = 5555
// Devices
const val QUICK_CONNECT_INPUT = ""
const val PAIR_HOST = ""
const val PAIR_PORT = ""
const val PAIR_CODE = ""
const val AUDIO_ENABLED = true
const val AUDIO_CODEC = "opus"
const val AUDIO_BIT_RATE_KBPS = 128
const val AUDIO_BIT_RATE_INPUT = "128"
const val VIDEO_CODEC = "h264"
const val VIDEO_BIT_RATE_MBPS = 8f
const val VIDEO_BIT_RATE_INPUT = "8.0"
const val TURN_SCREEN_OFF = false
const val NO_CONTROL = false
const val NO_VIDEO = false
const val VIDEO_SOURCE_PRESET = "display"
const val DISPLAY_ID = ""
const val CAMERA_ID = ""
const val CAMERA_FACING_PRESET = ""
const val CAMERA_SIZE_PRESET = ""
const val CAMERA_SIZE_CUSTOM = ""
const val CAMERA_AR = ""
const val CAMERA_FPS = ""
const val CAMERA_HIGH_SPEED = false
const val AUDIO_SOURCE_PRESET = "output"
const val AUDIO_SOURCE_CUSTOM = ""
const val AUDIO_DUP = false
const val NO_AUDIO_PLAYBACK = false
const val REQUIRE_AUDIO = false
const val MAX_SIZE_INPUT = ""
const val MAX_FPS_INPUT = ""
const val VIDEO_ENCODER = ""
const val VIDEO_CODEC_OPTION = ""
const val AUDIO_ENCODER = ""
const val AUDIO_CODEC_OPTION = ""
const val NEW_DISPLAY_WIDTH = ""
const val NEW_DISPLAY_HEIGHT = ""
const val NEW_DISPLAY_DPI = ""
const val CROP_WIDTH = ""
const val CROP_HEIGHT = ""
const val CROP_X = ""
const val CROP_Y = ""
// Settings
const val THEME_BASE_INDEX = 0
const val MONET = false
const val FULLSCREEN_DEBUG_INFO = false
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = true
const val KEEP_SCREEN_ON_WHEN_STREAMING = false
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = 320
const val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = true
const val VIRTUAL_BUTTONS_LAYOUT =
"more:1,app_switch:1,home:0,back:1,menu:0,notification:0,volume_up:0,volume_down:0,volume_mute:0,power:0,screenshot:0"
const val CUSTOM_SERVER_URI = ""
const val SERVER_REMOTE_PATH = "/data/local/tmp/scrcpy-server.jar"
const val SERVER_REMOTE_PATH_INPUT = ""
const val ADB_KEY_NAME = "scrcpy"
const val ADB_KEY_NAME_INPUT = ""
const val ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN = true
const val ADB_AUTO_RECONNECT_PAIRED_DEVICE = true
const val ADB_MDNS_LAN_DISCOVERY = true
}

View File

@@ -1,79 +0,0 @@
package io.github.miuzarte.scrcpyforandroid.constants
object AppPreferenceKeys {
const val PREFS_NAME = "scrcpy_app_prefs"
// Devices
const val QUICK_DEVICES = "quick_devices"
const val QUICK_CONNECT_INPUT = "quick_connect_input"
const val PAIR_HOST = "pair_host"
const val PAIR_PORT = "pair_port"
const val PAIR_CODE = "pair_code"
const val AUDIO_ENABLED = "audio_enabled"
const val AUDIO_CODEC = "audio_codec"
const val AUDIO_BIT_RATE_INPUT = "audio_bit_rate_input"
const val AUDIO_BIT_RATE_KBPS = "audio_bit_rate_kbps"
const val VIDEO_CODEC = "video_codec"
const val VIDEO_BIT_RATE_MBPS = "video_bit_rate_mbps"
const val VIDEO_BIT_RATE_INPUT = "video_bit_rate_input"
const val TURN_SCREEN_OFF = "turn_screen_off"
const val NO_CONTROL = "no_control"
const val NO_VIDEO = "no_video"
const val VIDEO_SOURCE_PRESET = "video_source_preset"
const val DISPLAY_ID = "display_id"
const val CAMERA_ID = "camera_id"
const val CAMERA_FACING_PRESET = "camera_facing_preset"
const val CAMERA_SIZE_PRESET = "camera_size_preset"
const val CAMERA_SIZE_CUSTOM = "camera_size_custom"
const val CAMERA_AR = "camera_ar"
const val CAMERA_FPS = "camera_fps"
const val CAMERA_HIGH_SPEED = "camera_high_speed"
const val AUDIO_SOURCE_PRESET = "audio_source_preset"
const val AUDIO_SOURCE_CUSTOM = "audio_source_custom"
const val AUDIO_DUP = "audio_dup"
const val NO_AUDIO_PLAYBACK = "no_audio_playback"
const val REQUIRE_AUDIO = "require_audio"
const val MAX_SIZE_INPUT = "max_size_input"
const val MAX_FPS_INPUT = "max_fps_input"
const val VIDEO_ENCODER = "video_encoder"
const val VIDEO_CODEC_OPTION = "video_codec_options"
const val AUDIO_ENCODER = "audio_encoder"
const val AUDIO_CODEC_OPTION = "audio_codec_options"
const val NEW_DISPLAY_WIDTH = "new_display_width"
const val NEW_DISPLAY_HEIGHT = "new_display_height"
const val NEW_DISPLAY_DPI = "new_display_dpi"
const val CROP_WIDTH = "crop_width"
const val CROP_HEIGHT = "crop_height"
const val CROP_X = "crop_x"
const val CROP_Y = "crop_y"
// Settings
const val THEME_BASE_INDEX = "theme_base_index"
const val MONET = "monet"
const val FULLSCREEN_DEBUG_INFO = "fullscreen_debug_info"
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = "show_fullscreen_virtual_buttons"
const val KEEP_SCREEN_ON_WHEN_STREAMING = "keep_screen_on_when_streaming"
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = "device_preview_card_height_dp"
const val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = "preview_virtual_button_show_text"
const val VIRTUAL_BUTTONS_LAYOUT = "virtual_buttons_layout"
const val CUSTOM_SERVER_URI = "custom_server_uri"
const val SERVER_REMOTE_PATH = "server_remote_path"
const val ADB_KEY_NAME = "adb_key_name"
const val ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN = "adb_pairing_auto_discover_on_dialog_open"
const val ADB_AUTO_RECONNECT_PAIRED_DEVICE = "adb_auto_reconnect_paired_device"
const val ADB_MDNS_LAN_DISCOVERY = "adb_mdns_lan_discovery"
}

View File

@@ -1,6 +1,5 @@
package io.github.miuzarte.scrcpyforandroid.constants
object Defaults {
const val EVENT_LOG_LINES = 512
const val ADB_PORT = 5555;
}

View File

@@ -1,7 +1,9 @@
package io.github.miuzarte.scrcpyforandroid.pages
import android.annotation.SuppressLint
import android.app.Activity
import android.util.Log
import android.view.WindowManager
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -22,6 +24,7 @@ import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
import io.github.miuzarte.scrcpyforandroid.constants.Defaults
@@ -92,7 +95,6 @@ fun DeviceTabScreen(
snackbar: SnackbarController,
scrollBehavior: ScrollBehavior,
onOpenVirtualButtonOrder: () -> Unit,
onSessionStartedChange: (Boolean) -> Unit,
onOpenReorderDevices: () -> Unit,
onOpenAdvancedPage: () -> Unit,
onOpenFullscreenPage: (Scrcpy.Session.SessionInfo) -> Unit,
@@ -142,7 +144,6 @@ fun DeviceTabScreen(
scrcpy = scrcpy,
snackbar = snackbar,
scrollBehavior = scrollBehavior,
onSessionStartedChange = onSessionStartedChange,
onOpenAdvancedPage = onOpenAdvancedPage,
onOpenFullscreenPage = onOpenFullscreenPage,
)
@@ -157,13 +158,14 @@ fun DeviceTabPage(
scrcpy: Scrcpy,
snackbar: SnackbarController,
scrollBehavior: ScrollBehavior,
onSessionStartedChange: (Boolean) -> Unit,
onOpenAdvancedPage: () -> Unit,
onOpenFullscreenPage: (Scrcpy.Session.SessionInfo) -> Unit,
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val taskScope = remember { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
val haptics = rememberAppHaptics()
val activity = remember(context) { context as? Activity }
val asBundleShared by appSettings.bundleState.collectAsState()
val asBundleSharedLatest by rememberUpdatedState(asBundleShared)
@@ -214,6 +216,23 @@ fun DeviceTabPage(
// read only
val soBundleShared by scrcpyOptions.bundleState.collectAsState()
fun setKeepScreenOn(enabled: Boolean) {
val window = activity?.window ?: return
window.decorView.post {
if (enabled) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
DisposableEffect(activity) {
onDispose {
setKeepScreenOn(false)
}
}
// Run adb operations on a dedicated single thread.
// Try to avoid blocking UI/recomposition and keeps adb call ordering deterministic.
@@ -323,6 +342,7 @@ fun DeviceTabPage(
) {
// Also stops scrcpy.
runCatching { scrcpy.stop() }
setKeepScreenOn(false)
runCatching { adbService.disconnect() }
adbConnected = false
currentTargetHost = ""
@@ -706,7 +726,6 @@ fun DeviceTabPage(
sessionInfoCodec = null
sessionInfoControlEnabled = false
}
onSessionStartedChange(sessionInfo != null)
}
fun sendVirtualButtonAction(action: VirtualButtonAction) {
@@ -920,6 +939,9 @@ fun DeviceTabPage(
runBusy("启动 scrcpy") {
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
val session = scrcpy.start(options)
if (options.disableScreensaver) {
setKeepScreenOn(true)
}
sessionInfo = session.copy(
host = currentTargetHost,
port = currentTargetPort
@@ -949,6 +971,7 @@ fun DeviceTabPage(
onStop = {
runBusy("停止 scrcpy") {
scrcpy.stop()
setKeepScreenOn(false)
sessionInfo = null
statusLine = "${currentTarget!!.host}:${currentTarget.port}"
logEvent("scrcpy 已停止")

View File

@@ -5,7 +5,6 @@ import android.content.Intent
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.SystemClock
import android.view.WindowManager
import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.PredictiveBackHandler
@@ -266,27 +265,11 @@ fun MainScreen() {
}
}
var sessionStarted by remember { mutableStateOf(false) }
var showReorderDevices by rememberSaveable { mutableStateOf(false) }
var fullscreenOrientation by rememberSaveable {
mutableIntStateOf(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
}
val keepScreenOnWhenStreamingEnabled = asBundle.keepScreenOnWhenStreaming
// Keep-screen-on is controlled globally, so fullscreen and preview share the same behavior.
DisposableEffect(activity, keepScreenOnWhenStreamingEnabled, sessionStarted) {
val window = activity?.window
val shouldKeepScreenOn = keepScreenOnWhenStreamingEnabled && sessionStarted
if (window != null && shouldKeepScreenOn) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
onDispose {
if (window != null && shouldKeepScreenOn) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}
// Fullscreen route can force orientation based on stream ratio; all other routes are portrait.
LaunchedEffect(activity, currentRootScreen, fullscreenOrientation) {
val targetOrientation = when (currentRootScreen) {
@@ -356,7 +339,6 @@ fun MainScreen() {
snackbar = snackbarController,
scrollBehavior = devicesPageScrollBehavior,
onOpenVirtualButtonOrder = { rootBackStack.add(RootScreen.VirtualButtonOrder) },
onSessionStartedChange = { sessionStarted = it },
onOpenReorderDevices = { showReorderDevices = true },
onOpenAdvancedPage = { rootBackStack.add(RootScreen.Advanced) },
onOpenFullscreenPage = { session ->

View File

@@ -264,14 +264,19 @@ internal fun ScrcpyAllOptionsPage(
val videoEncoderDropdownItems = rememberSaveable(videoEncoders, listRefreshVersion) {
listOf("") + videoEncoders
}
val videoEncoderIndex = rememberSaveable(soBundle.videoEncoder, videoEncoders, listRefreshVersion) {
(videoEncoders.indexOf(soBundle.videoEncoder) + 1).coerceAtLeast(0)
}
val videoEncoderIndex =
rememberSaveable(soBundle.videoEncoder, videoEncoders, listRefreshVersion) {
(videoEncoders.indexOf(soBundle.videoEncoder) + 1).coerceAtLeast(0)
}
val audioEncoderDropdownItems = rememberSaveable(audioEncoders, listRefreshVersion) {
listOf("") + audioEncoders
}
val audioEncoderIndex = rememberSaveable(soBundle.audioEncoder, audioEncoders, listRefreshVersion) {
val audioEncoderIndex = rememberSaveable(
soBundle.audioEncoder,
audioEncoders,
listRefreshVersion
) {
(audioEncoders.indexOf(soBundle.audioEncoder) + 1).coerceAtLeast(0)
}
@@ -419,6 +424,77 @@ internal fun ScrcpyAllOptionsPage(
},
// enabled = control || video,
)
SuperSlider(
title = "投屏过程中受控机的息屏时间",
summary = "--screen-off-timeout",
value = soBundle.screenOffTimeout.coerceAtLeast(0).toFloat(),
onValueChange = {
soBundle = soBundle.copy(
screenOffTimeout = it.roundToInt()
.takeIf { value -> value > 0 }
?.toLong() ?: -1
)
},
valueRange = 0f..600f,
steps = 600 - 1,
unit = "s",
zeroStateText = "默认",
displayText =
if (soBundle.screenOffTimeout <= 0) "默认"
else soBundle.screenOffTimeout.toString(),
inputInitialValue =
if (soBundle.screenOffTimeout <= 0) ""
else soBundle.screenOffTimeout.toString(),
inputFilter = { it.filter(Char::isDigit) },
inputValueRange = 0f..86400f,
onInputConfirm = {
soBundle = soBundle.copy(
screenOffTimeout = it.toLongOrNull()
?.takeIf { value -> value > 0 }
?: -1
)
},
)
SuperSwitch(
title = "开始投屏时不唤醒屏幕",
summary = "--no-power-on",
checked = !soBundle.powerOn,
onCheckedChange = {
soBundle = soBundle.copy(
powerOn = !it
)
},
)
SuperSwitch(
title = "结束投屏时息屏",
summary = "--power-off-on-close",
checked = soBundle.powerOffOnClose,
onCheckedChange = {
soBundle = soBundle.copy(
powerOffOnClose = it
)
},
)
SuperSwitch(
title = "显示物理触控",
summary = "--show-touches",
checked = soBundle.showTouches,
onCheckedChange = {
soBundle = soBundle.copy(
showTouches = it
)
},
)
SuperSwitch(
title = "投屏时保持本机屏幕唤醒",
summary = "--disable-screensaver",
checked = soBundle.disableScreensaver,
onCheckedChange = {
soBundle = soBundle.copy(
disableScreensaver = it
)
},
)
}
}
@@ -784,6 +860,26 @@ internal fun ScrcpyAllOptionsPage(
)
},
)
SuperSwitch(
title = "关闭虚拟显示器时保留内容",
summary = "--no-vd-destroy-content",
checked = !soBundle.vdDestroyContent,
onCheckedChange = {
soBundle = soBundle.copy(
vdDestroyContent = !it
)
},
)
SuperSwitch(
title = "禁用虚拟显示器系统装饰",
summary = "--no-vd-system-decorations",
checked = !soBundle.vdSystemDecorations,
onCheckedChange = {
soBundle = soBundle.copy(
vdSystemDecorations = !it
)
},
)
}
}

View File

@@ -194,14 +194,6 @@ fun SettingsPage(
asBundle = asBundle.copy(fullscreenDebugInfo = it)
},
)
SuperSwitch(
title = "投屏时保持屏幕常亮",
summary = "Scrcpy 启动后保持本机屏幕常亮,避免锁屏导致 ADB 断开",
checked = asBundle.keepScreenOnWhenStreaming,
onCheckedChange = {
asBundle = asBundle.copy(keepScreenOnWhenStreaming = it)
},
)
SuperSlider(
title = "预览卡高度",
summary = "设备页预览卡高度",

View File

@@ -32,18 +32,16 @@ fun LazyColumn(
content: LazyListScope.() -> Unit,
) {
val focusManager = LocalFocusManager.current
val focusClearModifier = if (clearFocusOnTap) {
Modifier.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() })
}
} else {
Modifier
}
LazyColumn(
modifier = modifier
.fillMaxSize()
.then(focusClearModifier)
.then(
if (clearFocusOnTap)
Modifier.pointerInput(Unit) {
detectTapGestures(onTap = { focusManager.clearFocus() })
} else Modifier
)
.overScrollVertical()
.nestedScroll(scrollBehavior.nestedScrollConnection)
.padding(contentPadding),

View File

@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -12,6 +13,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import top.yukonga.miuix.kmp.basic.ButtonDefaults
import top.yukonga.miuix.kmp.basic.Slider
@@ -131,6 +134,10 @@ private fun SliderInputDialog(
label = label,
useLabelAsPlaceholder = useLabelAsPlaceholder,
maxLines = 1,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done,
),
onValueChange = { newValue ->
text = inputFilter(newValue)
},

View File

@@ -1,528 +0,0 @@
package io.github.miuzarte.scrcpyforandroid.services
import android.content.Context
import androidx.core.content.edit
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
import io.github.miuzarte.scrcpyforandroid.constants.AppPreferenceKeys
internal data class MainSettings(
val audioEnabled: Boolean = AppDefaults.AUDIO_ENABLED,
val audioCodec: String = AppDefaults.AUDIO_CODEC,
val videoCodec: String = AppDefaults.VIDEO_CODEC,
val themeBaseIndex: Int = AppDefaults.THEME_BASE_INDEX,
val monetEnabled: Boolean = AppDefaults.MONET,
val fullscreenDebugInfoEnabled: Boolean = AppDefaults.FULLSCREEN_DEBUG_INFO,
val showFullscreenVirtualButtons: Boolean = AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
val showPreviewVirtualButtonText: Boolean = AppDefaults.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
val keepScreenOnWhenStreamingEnabled: Boolean = AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
val devicePreviewCardHeightDp: Int = AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
val virtualButtonsLayout: String = AppDefaults.VIRTUAL_BUTTONS_LAYOUT,
val customServerUri: String? = AppDefaults.CUSTOM_SERVER_URI,
val serverRemotePath: String = AppDefaults.SERVER_REMOTE_PATH_INPUT,
val adbKeyName: String = AppDefaults.ADB_KEY_NAME_INPUT,
val adbPairingAutoDiscoverOnDialogOpen: Boolean =
AppDefaults.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
val adbAutoReconnectPairedDevice: Boolean = AppDefaults.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
val adbMdnsLanDiscoveryEnabled: Boolean = AppDefaults.ADB_MDNS_LAN_DISCOVERY,
)
internal data class DevicePageSettings(
val quickConnectInput: String = AppDefaults.QUICK_CONNECT_INPUT,
val pairHost: String = AppDefaults.PAIR_HOST,
val pairPort: String = AppDefaults.PAIR_PORT,
val pairCode: String = AppDefaults.PAIR_CODE,
val audioBitRateKbps: Int = AppDefaults.AUDIO_BIT_RATE_KBPS,
val audioBitRateInput: String = AppDefaults.AUDIO_BIT_RATE_INPUT,
val videoBitRateMbps: Float = AppDefaults.VIDEO_BIT_RATE_MBPS,
val videoBitRateInput: String = AppDefaults.VIDEO_BIT_RATE_INPUT,
val turnScreenOff: Boolean = AppDefaults.TURN_SCREEN_OFF,
val noControl: Boolean = AppDefaults.NO_CONTROL,
val noVideo: Boolean = AppDefaults.NO_VIDEO,
val videoSourcePreset: String = AppDefaults.VIDEO_SOURCE_PRESET,
val displayIdInput: String = AppDefaults.DISPLAY_ID,
val cameraIdInput: String = AppDefaults.CAMERA_ID,
val cameraFacingPreset: String = AppDefaults.CAMERA_FACING_PRESET,
val cameraSizePreset: String = AppDefaults.CAMERA_SIZE_PRESET,
val cameraSizeCustom: String = AppDefaults.CAMERA_SIZE_CUSTOM,
val cameraAr: String = AppDefaults.CAMERA_AR,
val cameraFps: String = AppDefaults.CAMERA_FPS,
val cameraHighSpeed: Boolean = AppDefaults.CAMERA_HIGH_SPEED,
val audioSourcePreset: String = AppDefaults.AUDIO_SOURCE_PRESET,
val audioSourceCustom: String = AppDefaults.AUDIO_SOURCE_CUSTOM,
val audioDup: Boolean = AppDefaults.AUDIO_DUP,
val noAudioPlayback: Boolean = AppDefaults.NO_AUDIO_PLAYBACK,
val requireAudio: Boolean = AppDefaults.REQUIRE_AUDIO,
val maxSizeInput: String = AppDefaults.MAX_SIZE_INPUT,
val maxFpsInput: String = AppDefaults.MAX_FPS_INPUT,
val videoEncoder: String = AppDefaults.VIDEO_ENCODER,
val videoCodecOptions: String = AppDefaults.VIDEO_CODEC_OPTION,
val audioEncoder: String = AppDefaults.AUDIO_ENCODER,
val audioCodecOptions: String = AppDefaults.AUDIO_CODEC_OPTION,
val newDisplayWidth: String = AppDefaults.NEW_DISPLAY_WIDTH,
val newDisplayHeight: String = AppDefaults.NEW_DISPLAY_HEIGHT,
val newDisplayDpi: String = AppDefaults.NEW_DISPLAY_DPI,
val cropWidth: String = AppDefaults.CROP_WIDTH,
val cropHeight: String = AppDefaults.CROP_HEIGHT,
val cropX: String = AppDefaults.CROP_X,
val cropY: String = AppDefaults.CROP_Y,
)
internal fun loadMainSettings(context: Context): MainSettings {
val prefs = context.getSharedPreferences(
AppPreferenceKeys.PREFS_NAME,
Context.MODE_PRIVATE,
)
return MainSettings(
audioEnabled = prefs.getBoolean(
AppPreferenceKeys.AUDIO_ENABLED,
AppDefaults.AUDIO_ENABLED,
),
audioCodec = prefs.getString(
AppPreferenceKeys.AUDIO_CODEC,
AppDefaults.AUDIO_CODEC,
).orEmpty().ifBlank { AppDefaults.AUDIO_CODEC },
videoCodec = prefs.getString(
AppPreferenceKeys.VIDEO_CODEC,
AppDefaults.VIDEO_CODEC,
).orEmpty().ifBlank { AppDefaults.VIDEO_CODEC },
themeBaseIndex = prefs.getInt(
AppPreferenceKeys.THEME_BASE_INDEX,
AppDefaults.THEME_BASE_INDEX,
),
monetEnabled = prefs.getBoolean(
AppPreferenceKeys.MONET,
AppDefaults.MONET,
),
fullscreenDebugInfoEnabled = prefs.getBoolean(
AppPreferenceKeys.FULLSCREEN_DEBUG_INFO,
AppDefaults.FULLSCREEN_DEBUG_INFO,
),
showFullscreenVirtualButtons = prefs.getBoolean(
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
),
showPreviewVirtualButtonText = prefs.getBoolean(
AppPreferenceKeys.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
AppDefaults.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
),
keepScreenOnWhenStreamingEnabled = prefs.getBoolean(
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
),
devicePreviewCardHeightDp = prefs.getInt(
AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
).coerceAtLeast(120),
virtualButtonsLayout = prefs.getString(
AppPreferenceKeys.VIRTUAL_BUTTONS_LAYOUT,
AppDefaults.VIRTUAL_BUTTONS_LAYOUT,
).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_LAYOUT },
customServerUri = prefs.getString(
AppPreferenceKeys.CUSTOM_SERVER_URI,
AppDefaults.CUSTOM_SERVER_URI
).orEmpty().ifBlank { null },
serverRemotePath = prefs.getString(
AppPreferenceKeys.SERVER_REMOTE_PATH,
AppDefaults.SERVER_REMOTE_PATH_INPUT,
).orEmpty(),
adbKeyName = prefs.getString(
AppPreferenceKeys.ADB_KEY_NAME,
AppDefaults.ADB_KEY_NAME_INPUT,
).orEmpty(),
adbPairingAutoDiscoverOnDialogOpen = prefs.getBoolean(
AppPreferenceKeys.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
AppDefaults.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
),
adbAutoReconnectPairedDevice = prefs.getBoolean(
AppPreferenceKeys.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
AppDefaults.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
),
adbMdnsLanDiscoveryEnabled = prefs.getBoolean(
AppPreferenceKeys.ADB_MDNS_LAN_DISCOVERY,
AppDefaults.ADB_MDNS_LAN_DISCOVERY,
),
)
}
internal fun saveMainSettings(context: Context, settings: MainSettings) {
context.getSharedPreferences(
AppPreferenceKeys.PREFS_NAME,
Context.MODE_PRIVATE,
).edit {
putBoolean(
AppPreferenceKeys.AUDIO_ENABLED,
settings.audioEnabled,
)
.putString(
AppPreferenceKeys.AUDIO_CODEC,
settings.audioCodec,
)
.putString(
AppPreferenceKeys.VIDEO_CODEC,
settings.videoCodec,
)
.putInt(
AppPreferenceKeys.THEME_BASE_INDEX,
settings.themeBaseIndex,
)
.putBoolean(
AppPreferenceKeys.MONET,
settings.monetEnabled,
)
.putBoolean(
AppPreferenceKeys.FULLSCREEN_DEBUG_INFO,
settings.fullscreenDebugInfoEnabled,
)
.putBoolean(
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
settings.showFullscreenVirtualButtons,
)
.putBoolean(
AppPreferenceKeys.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
settings.showPreviewVirtualButtonText,
)
.putBoolean(
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
settings.keepScreenOnWhenStreamingEnabled,
)
.putInt(
AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
settings.devicePreviewCardHeightDp.coerceAtLeast(120),
)
.putString(
AppPreferenceKeys.VIRTUAL_BUTTONS_LAYOUT,
settings.virtualButtonsLayout,
)
.putString(
AppPreferenceKeys.CUSTOM_SERVER_URI,
settings.customServerUri,
)
.putString(
AppPreferenceKeys.SERVER_REMOTE_PATH,
settings.serverRemotePath,
)
.putString(
AppPreferenceKeys.ADB_KEY_NAME,
settings.adbKeyName,
)
.putBoolean(
AppPreferenceKeys.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
settings.adbPairingAutoDiscoverOnDialogOpen,
)
.putBoolean(
AppPreferenceKeys.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
settings.adbAutoReconnectPairedDevice,
)
.putBoolean(
AppPreferenceKeys.ADB_MDNS_LAN_DISCOVERY,
settings.adbMdnsLanDiscoveryEnabled,
)
}
}
internal fun loadDevicePageSettings(context: Context): DevicePageSettings {
val prefs = context.getSharedPreferences(
AppPreferenceKeys.PREFS_NAME,
Context.MODE_PRIVATE,
)
val audioBitRateKbps = prefs.getInt(
AppPreferenceKeys.AUDIO_BIT_RATE_KBPS,
AppDefaults.AUDIO_BIT_RATE_KBPS,
)
return DevicePageSettings(
quickConnectInput = prefs.getString(
AppPreferenceKeys.QUICK_CONNECT_INPUT,
AppDefaults.QUICK_CONNECT_INPUT,
).orEmpty(),
pairHost = AppDefaults.PAIR_HOST,
pairPort = AppDefaults.PAIR_PORT,
pairCode = AppDefaults.PAIR_CODE,
audioBitRateKbps = audioBitRateKbps,
audioBitRateInput = prefs.getString(
AppPreferenceKeys.AUDIO_BIT_RATE_INPUT,
AppDefaults.AUDIO_BIT_RATE_INPUT,
).orEmpty().ifBlank { audioBitRateKbps.toString() },
videoBitRateMbps = prefs.getFloat(
AppPreferenceKeys.VIDEO_BIT_RATE_MBPS,
AppDefaults.VIDEO_BIT_RATE_MBPS,
),
videoBitRateInput = prefs.getString(
AppPreferenceKeys.VIDEO_BIT_RATE_INPUT,
AppDefaults.VIDEO_BIT_RATE_INPUT
).orEmpty().ifBlank { AppDefaults.VIDEO_BIT_RATE_INPUT },
turnScreenOff = prefs.getBoolean(
AppPreferenceKeys.TURN_SCREEN_OFF,
AppDefaults.TURN_SCREEN_OFF,
),
noControl = prefs.getBoolean(
AppPreferenceKeys.NO_CONTROL,
AppDefaults.NO_CONTROL,
),
noVideo = prefs.getBoolean(
AppPreferenceKeys.NO_VIDEO,
AppDefaults.NO_VIDEO,
),
videoSourcePreset = prefs.getString(
AppPreferenceKeys.VIDEO_SOURCE_PRESET,
AppDefaults.VIDEO_SOURCE_PRESET,
).orEmpty().ifBlank { AppDefaults.VIDEO_SOURCE_PRESET },
displayIdInput = prefs.getString(
AppPreferenceKeys.DISPLAY_ID,
AppDefaults.DISPLAY_ID,
)
.orEmpty(),
cameraIdInput = prefs.getString(
AppPreferenceKeys.CAMERA_ID,
AppDefaults.CAMERA_ID,
)
.orEmpty(),
cameraFacingPreset = prefs.getString(
AppPreferenceKeys.CAMERA_FACING_PRESET,
AppDefaults.CAMERA_FACING_PRESET,
).orEmpty(),
cameraSizePreset = prefs.getString(
AppPreferenceKeys.CAMERA_SIZE_PRESET,
AppDefaults.CAMERA_SIZE_PRESET,
).orEmpty(),
cameraSizeCustom = prefs.getString(
AppPreferenceKeys.CAMERA_SIZE_CUSTOM,
AppDefaults.CAMERA_SIZE_CUSTOM,
).orEmpty(),
cameraAr = prefs.getString(
AppPreferenceKeys.CAMERA_AR,
AppDefaults.CAMERA_AR,
).orEmpty(),
cameraFps = prefs.getString(
AppPreferenceKeys.CAMERA_FPS,
AppDefaults.CAMERA_FPS,
).orEmpty(),
cameraHighSpeed = prefs.getBoolean(
AppPreferenceKeys.CAMERA_HIGH_SPEED,
AppDefaults.CAMERA_HIGH_SPEED,
),
audioSourcePreset = prefs.getString(
AppPreferenceKeys.AUDIO_SOURCE_PRESET,
AppDefaults.AUDIO_SOURCE_PRESET,
).orEmpty().ifBlank { AppDefaults.AUDIO_SOURCE_PRESET },
audioSourceCustom = prefs.getString(
AppPreferenceKeys.AUDIO_SOURCE_CUSTOM,
AppDefaults.AUDIO_SOURCE_CUSTOM,
).orEmpty(),
audioDup = prefs.getBoolean(
AppPreferenceKeys.AUDIO_DUP,
AppDefaults.AUDIO_DUP,
),
noAudioPlayback = prefs.getBoolean(
AppPreferenceKeys.NO_AUDIO_PLAYBACK,
AppDefaults.NO_AUDIO_PLAYBACK,
),
requireAudio = prefs.getBoolean(
AppPreferenceKeys.REQUIRE_AUDIO,
AppDefaults.REQUIRE_AUDIO,
),
maxSizeInput = prefs.getString(
AppPreferenceKeys.MAX_SIZE_INPUT,
AppDefaults.MAX_SIZE_INPUT,
)
.orEmpty(),
maxFpsInput = prefs.getString(
AppPreferenceKeys.MAX_FPS_INPUT,
AppDefaults.MAX_FPS_INPUT,
)
.orEmpty(),
videoEncoder = prefs.getString(
AppPreferenceKeys.VIDEO_ENCODER,
AppDefaults.VIDEO_ENCODER,
)
.orEmpty(),
videoCodecOptions = prefs.getString(
AppPreferenceKeys.VIDEO_CODEC_OPTION,
AppDefaults.VIDEO_CODEC_OPTION,
).orEmpty(),
audioEncoder = prefs.getString(
AppPreferenceKeys.AUDIO_ENCODER,
AppDefaults.AUDIO_ENCODER,
).orEmpty(),
audioCodecOptions = prefs.getString(
AppPreferenceKeys.AUDIO_CODEC_OPTION,
AppDefaults.AUDIO_CODEC_OPTION,
).orEmpty(),
newDisplayWidth = prefs.getString(
AppPreferenceKeys.NEW_DISPLAY_WIDTH,
AppDefaults.NEW_DISPLAY_WIDTH,
).orEmpty(),
newDisplayHeight = prefs.getString(
AppPreferenceKeys.NEW_DISPLAY_HEIGHT,
AppDefaults.NEW_DISPLAY_HEIGHT,
).orEmpty(),
newDisplayDpi = prefs.getString(
AppPreferenceKeys.NEW_DISPLAY_DPI,
AppDefaults.NEW_DISPLAY_DPI,
).orEmpty(),
cropWidth = prefs.getString(
AppPreferenceKeys.CROP_WIDTH,
AppDefaults.CROP_WIDTH,
).orEmpty(),
cropHeight = prefs.getString(
AppPreferenceKeys.CROP_HEIGHT,
AppDefaults.CROP_HEIGHT,
).orEmpty(),
cropX = prefs.getString(
AppPreferenceKeys.CROP_X,
AppDefaults.CROP_X,
).orEmpty(),
cropY = prefs.getString(
AppPreferenceKeys.CROP_Y,
AppDefaults.CROP_Y,
).orEmpty(),
)
}
internal fun saveDevicePageSettings(context: Context, settings: DevicePageSettings) {
context.getSharedPreferences(AppPreferenceKeys.PREFS_NAME, Context.MODE_PRIVATE)
.edit {
remove(AppPreferenceKeys.PAIR_HOST)
.remove(AppPreferenceKeys.PAIR_PORT)
.remove(AppPreferenceKeys.PAIR_CODE)
.putString(
AppPreferenceKeys.QUICK_CONNECT_INPUT,
settings.quickConnectInput,
)
.putInt(
AppPreferenceKeys.AUDIO_BIT_RATE_KBPS,
settings.audioBitRateKbps,
)
.putString(
AppPreferenceKeys.AUDIO_BIT_RATE_INPUT,
settings.audioBitRateInput,
)
.putFloat(
AppPreferenceKeys.VIDEO_BIT_RATE_MBPS,
settings.videoBitRateMbps,
)
.putString(
AppPreferenceKeys.VIDEO_BIT_RATE_INPUT,
settings.videoBitRateInput,
)
.putBoolean(
AppPreferenceKeys.TURN_SCREEN_OFF,
settings.turnScreenOff,
)
.putBoolean(
AppPreferenceKeys.NO_CONTROL,
settings.noControl,
)
.putBoolean(
AppPreferenceKeys.NO_VIDEO,
settings.noVideo,
)
.putString(
AppPreferenceKeys.VIDEO_SOURCE_PRESET,
settings.videoSourcePreset,
)
.putString(
AppPreferenceKeys.DISPLAY_ID,
settings.displayIdInput,
)
.putString(
AppPreferenceKeys.CAMERA_ID,
settings.cameraIdInput,
)
.putString(
AppPreferenceKeys.CAMERA_FACING_PRESET,
settings.cameraFacingPreset,
)
.putString(
AppPreferenceKeys.CAMERA_SIZE_PRESET,
settings.cameraSizePreset,
)
.putString(
AppPreferenceKeys.CAMERA_SIZE_CUSTOM,
settings.cameraSizeCustom,
)
.putString(
AppPreferenceKeys.CAMERA_AR,
settings.cameraAr,
)
.putString(
AppPreferenceKeys.CAMERA_FPS,
settings.cameraFps,
)
.putBoolean(
AppPreferenceKeys.CAMERA_HIGH_SPEED,
settings.cameraHighSpeed,
)
.putString(
AppPreferenceKeys.AUDIO_SOURCE_PRESET,
settings.audioSourcePreset,
)
.putString(
AppPreferenceKeys.AUDIO_SOURCE_CUSTOM,
settings.audioSourceCustom,
)
.putBoolean(
AppPreferenceKeys.AUDIO_DUP,
settings.audioDup,
)
.putBoolean(
AppPreferenceKeys.NO_AUDIO_PLAYBACK,
settings.noAudioPlayback,
)
.putBoolean(
AppPreferenceKeys.REQUIRE_AUDIO,
settings.requireAudio,
)
.putString(
AppPreferenceKeys.MAX_SIZE_INPUT,
settings.maxSizeInput,
)
.putString(
AppPreferenceKeys.MAX_FPS_INPUT,
settings.maxFpsInput,
)
.putString(
AppPreferenceKeys.VIDEO_ENCODER,
settings.videoEncoder,
)
.putString(
AppPreferenceKeys.VIDEO_CODEC_OPTION,
settings.videoCodecOptions,
)
.putString(
AppPreferenceKeys.AUDIO_ENCODER,
settings.audioEncoder,
)
.putString(
AppPreferenceKeys.AUDIO_CODEC_OPTION,
settings.audioCodecOptions,
)
.putString(
AppPreferenceKeys.NEW_DISPLAY_WIDTH,
settings.newDisplayWidth,
)
.putString(
AppPreferenceKeys.NEW_DISPLAY_HEIGHT,
settings.newDisplayHeight,
)
.putString(
AppPreferenceKeys.NEW_DISPLAY_DPI,
settings.newDisplayDpi,
)
.putString(
AppPreferenceKeys.CROP_WIDTH,
settings.cropWidth,
)
.putString(
AppPreferenceKeys.CROP_HEIGHT,
settings.cropHeight,
)
.putString(
AppPreferenceKeys.CROP_X,
settings.cropX,
)
.putString(
AppPreferenceKeys.CROP_Y,
settings.cropY,
)
}
}

View File

@@ -17,6 +17,8 @@ import java.util.Locale
object EventLogger {
private const val LOG_TAG = "EventLogger"
const val MAX_LINES = 512
private val _eventLog: SnapshotStateList<String> = mutableStateListOf()
/**
@@ -36,8 +38,8 @@ object EventLogger {
_eventLog.add(0, "[$timestamp] $message")
// Rotate logs if exceeds max size
if (_eventLog.size > Defaults.EVENT_LOG_LINES) {
_eventLog.removeRange(Defaults.EVENT_LOG_LINES, _eventLog.size)
if (_eventLog.size > MAX_LINES) {
_eventLog.removeRange(MAX_LINES, _eventLog.size)
}
// Log to Android logcat

View File

@@ -28,10 +28,6 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
booleanPreferencesKey("show_fullscreen_virtual_buttons"),
true
)
val KEEP_SCREEN_ON_WHEN_STREAMING = Pair(
booleanPreferencesKey("keep_screen_on_when_streaming"),
false
)
val DEVICE_PREVIEW_CARD_HEIGHT_DP = Pair(
intPreferencesKey("device_preview_card_height_dp"),
320
@@ -81,7 +77,6 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
// Scrcpy Settings
val fullscreenDebugInfo by setting(FULLSCREEN_DEBUG_INFO)
val showFullscreenVirtualButtons by setting(SHOW_FULLSCREEN_VIRTUAL_BUTTONS)
val keepScreenOnWhenStreaming by setting(KEEP_SCREEN_ON_WHEN_STREAMING)
val devicePreviewCardHeightDp by setting(DEVICE_PREVIEW_CARD_HEIGHT_DP)
val previewVirtualButtonShowText by setting(PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT)
val virtualButtonsLayout by setting(VIRTUAL_BUTTONS_LAYOUT)
@@ -103,7 +98,6 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
val monet: Boolean,
val fullscreenDebugInfo: Boolean,
val showFullscreenVirtualButtons: Boolean,
val keepScreenOnWhenStreaming: Boolean,
val devicePreviewCardHeightDp: Int,
val previewVirtualButtonShowText: Boolean,
val virtualButtonsLayout: String,
@@ -122,7 +116,6 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
bundleField(MONET) { bundle: Bundle -> bundle.monet },
bundleField(FULLSCREEN_DEBUG_INFO) { bundle: Bundle -> bundle.fullscreenDebugInfo },
bundleField(SHOW_FULLSCREEN_VIRTUAL_BUTTONS) { bundle: Bundle -> bundle.showFullscreenVirtualButtons },
bundleField(KEEP_SCREEN_ON_WHEN_STREAMING) { bundle: Bundle -> bundle.keepScreenOnWhenStreaming },
bundleField(DEVICE_PREVIEW_CARD_HEIGHT_DP) { bundle: Bundle -> bundle.devicePreviewCardHeightDp },
bundleField(PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT) { bundle: Bundle -> bundle.previewVirtualButtonShowText },
bundleField(VIRTUAL_BUTTONS_LAYOUT) { bundle: Bundle -> bundle.virtualButtonsLayout },
@@ -142,7 +135,6 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
monet = preferences.read(MONET),
fullscreenDebugInfo = preferences.read(FULLSCREEN_DEBUG_INFO),
showFullscreenVirtualButtons = preferences.read(SHOW_FULLSCREEN_VIRTUAL_BUTTONS),
keepScreenOnWhenStreaming = preferences.read(KEEP_SCREEN_ON_WHEN_STREAMING),
devicePreviewCardHeightDp = preferences.read(DEVICE_PREVIEW_CARD_HEIGHT_DP),
previewVirtualButtonShowText = preferences.read(PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT),
virtualButtonsLayout = preferences.read(VIRTUAL_BUTTONS_LAYOUT),

View File

@@ -4,8 +4,6 @@ import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.core.content.edit
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
import io.github.miuzarte.scrcpyforandroid.constants.AppPreferenceKeys
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -16,7 +14,7 @@ private const val TAG = "PreferenceMigration"
*/
class PreferenceMigration(private val appContext: Context) {
private val appSharedPrefs: SharedPreferences by lazy {
appContext.getSharedPreferences(AppPreferenceKeys.PREFS_NAME, Context.MODE_PRIVATE)
appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
private val sharedPrefs: SharedPreferences by lazy {
@@ -80,80 +78,75 @@ class PreferenceMigration(private val appContext: Context) {
// Theme Settings
migrateInt(
AppPreferenceKeys.THEME_BASE_INDEX,
AppDefaults.THEME_BASE_INDEX,
appSettings.themeBaseIndex
THEME_BASE_INDEX,
AppSettings.THEME_BASE_INDEX.defaultValue,
appSettings.themeBaseIndex,
)
migrateBoolean(
AppPreferenceKeys.MONET,
AppDefaults.MONET,
appSettings.monet
MONET,
AppSettings.MONET.defaultValue,
appSettings.monet,
)
// Scrcpy Settings
migrateBoolean(
AppPreferenceKeys.FULLSCREEN_DEBUG_INFO,
AppDefaults.FULLSCREEN_DEBUG_INFO,
appSettings.fullscreenDebugInfo
FULLSCREEN_DEBUG_INFO,
AppSettings.FULLSCREEN_DEBUG_INFO.defaultValue,
appSettings.fullscreenDebugInfo,
)
migrateBoolean(
AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
appSettings.showFullscreenVirtualButtons
)
migrateBoolean(
AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
appSettings.keepScreenOnWhenStreaming
SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
AppSettings.SHOW_FULLSCREEN_VIRTUAL_BUTTONS.defaultValue,
appSettings.showFullscreenVirtualButtons,
)
migrateInt(
AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
appSettings.devicePreviewCardHeightDp
DEVICE_PREVIEW_CARD_HEIGHT_DP,
AppSettings.DEVICE_PREVIEW_CARD_HEIGHT_DP.defaultValue,
appSettings.devicePreviewCardHeightDp,
)
migrateBoolean(
AppPreferenceKeys.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
AppDefaults.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
appSettings.previewVirtualButtonShowText
PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT,
AppSettings.PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT.defaultValue,
appSettings.previewVirtualButtonShowText,
)
migrateString(
AppPreferenceKeys.VIRTUAL_BUTTONS_LAYOUT,
AppDefaults.VIRTUAL_BUTTONS_LAYOUT,
appSettings.virtualButtonsLayout
VIRTUAL_BUTTONS_LAYOUT,
AppSettings.VIRTUAL_BUTTONS_LAYOUT.defaultValue,
appSettings.virtualButtonsLayout,
)
// Scrcpy Server Settings
migrateString(
AppPreferenceKeys.CUSTOM_SERVER_URI,
AppDefaults.CUSTOM_SERVER_URI ?: "",
appSettings.customServerUri
CUSTOM_SERVER_URI,
AppSettings.CUSTOM_SERVER_URI.defaultValue,
appSettings.customServerUri,
)
migrateString(
AppPreferenceKeys.SERVER_REMOTE_PATH,
AppDefaults.SERVER_REMOTE_PATH_INPUT,
appSettings.serverRemotePath
SERVER_REMOTE_PATH,
AppSettings.SERVER_REMOTE_PATH.defaultValue,
appSettings.serverRemotePath,
)
// ADB Settings
migrateString(
AppPreferenceKeys.ADB_KEY_NAME,
AppDefaults.ADB_KEY_NAME_INPUT,
appSettings.adbKeyName
ADB_KEY_NAME,
AppSettings.ADB_KEY_NAME.defaultValue,
appSettings.adbKeyName,
)
migrateBoolean(
AppPreferenceKeys.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
AppDefaults.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
appSettings.adbPairingAutoDiscoverOnDialogOpen
ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN,
AppSettings.ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN.defaultValue,
appSettings.adbPairingAutoDiscoverOnDialogOpen,
)
migrateBoolean(
AppPreferenceKeys.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
AppDefaults.ADB_AUTO_RECONNECT_PAIRED_DEVICE,
appSettings.adbAutoReconnectPairedDevice
ADB_AUTO_RECONNECT_PAIRED_DEVICE,
AppSettings.ADB_AUTO_RECONNECT_PAIRED_DEVICE.defaultValue,
appSettings.adbAutoReconnectPairedDevice,
)
migrateBoolean(
AppPreferenceKeys.ADB_MDNS_LAN_DISCOVERY,
AppDefaults.ADB_MDNS_LAN_DISCOVERY,
appSettings.adbMdnsLanDiscovery
ADB_MDNS_LAN_DISCOVERY,
AppSettings.ADB_MDNS_LAN_DISCOVERY.defaultValue,
appSettings.adbMdnsLanDiscovery,
)
Log.d(TAG, "AppSettings migration completed")
@@ -167,114 +160,115 @@ class PreferenceMigration(private val appContext: Context) {
// Audio & Video Codecs
migrateString(
AppPreferenceKeys.AUDIO_CODEC,
AppDefaults.AUDIO_CODEC,
scrcpyOptions.audioCodec
AUDIO_CODEC,
ScrcpyOptions.AUDIO_CODEC.defaultValue,
scrcpyOptions.audioCodec,
)
migrateString(
AppPreferenceKeys.VIDEO_CODEC,
AppDefaults.VIDEO_CODEC,
scrcpyOptions.videoCodec
VIDEO_CODEC,
ScrcpyOptions.VIDEO_CODEC.defaultValue,
scrcpyOptions.videoCodec,
)
// Bit Rates
val audioBitRateKbps = sharedPrefs.getInt(
AppPreferenceKeys.AUDIO_BIT_RATE_KBPS,
AppDefaults.AUDIO_BIT_RATE_KBPS
AUDIO_BIT_RATE_KBPS,
ScrcpyOptions.AUDIO_BIT_RATE.defaultValue / 1_000,
)
scrcpyOptions.audioBitRate.set(audioBitRateKbps * 1000) // Convert to bps
scrcpyOptions.audioBitRate.set(audioBitRateKbps * 1_000) // Convert to bps
val videoBitRateMbps = sharedPrefs.getFloat(
AppPreferenceKeys.VIDEO_BIT_RATE_MBPS,
AppDefaults.VIDEO_BIT_RATE_MBPS
VIDEO_BIT_RATE_MBPS,
(ScrcpyOptions.VIDEO_BIT_RATE.defaultValue / 1_000_000).toFloat(),
)
scrcpyOptions.videoBitRate.set((videoBitRateMbps * 1_000_000).toInt())
// Control Options
migrateBoolean(
AppPreferenceKeys.TURN_SCREEN_OFF,
AppDefaults.TURN_SCREEN_OFF,
scrcpyOptions.turnScreenOff
TURN_SCREEN_OFF,
ScrcpyOptions.TURN_SCREEN_OFF.defaultValue,
scrcpyOptions.turnScreenOff,
)
migrateBoolean(
AppPreferenceKeys.NO_CONTROL,
AppDefaults.NO_CONTROL
NO_CONTROL,
!ScrcpyOptions.CONTROL.defaultValue,
) { value ->
scrcpyOptions.control.set(!value) // Invert logic
}
migrateBoolean(
AppPreferenceKeys.NO_VIDEO,
AppDefaults.NO_VIDEO
NO_VIDEO,
!ScrcpyOptions.VIDEO.defaultValue,
) { value ->
scrcpyOptions.video.set(!value) // Invert logic
}
// Video Source
val videoSourcePreset = sharedPrefs.getString(
AppPreferenceKeys.VIDEO_SOURCE_PRESET,
AppDefaults.VIDEO_SOURCE_PRESET
).orEmpty().ifBlank { AppDefaults.VIDEO_SOURCE_PRESET }
VIDEO_SOURCE_PRESET,
ScrcpyOptions.VIDEO_SOURCE.defaultValue,
).orEmpty().ifBlank { ScrcpyOptions.VIDEO_SOURCE.defaultValue }
scrcpyOptions.videoSource.set(videoSourcePreset)
migrateString(
AppPreferenceKeys.DISPLAY_ID,
AppDefaults.DISPLAY_ID
DISPLAY_ID,
ScrcpyOptions.DISPLAY_ID.defaultValue.toString(),
) { value ->
value.toIntOrNull()?.let { scrcpyOptions.displayId.set(it) }
}
// Camera Settings
migrateString(
AppPreferenceKeys.CAMERA_ID,
AppDefaults.CAMERA_ID,
scrcpyOptions.cameraId
CAMERA_ID,
ScrcpyOptions.CAMERA_ID.defaultValue,
scrcpyOptions.cameraId,
)
migrateString(
AppPreferenceKeys.CAMERA_FACING_PRESET,
AppDefaults.CAMERA_FACING_PRESET,
scrcpyOptions.cameraFacing
CAMERA_FACING_PRESET,
ScrcpyOptions.CAMERA_FACING.defaultValue,
scrcpyOptions.cameraFacing,
)
migrateString(
AppPreferenceKeys.CAMERA_SIZE_PRESET,
AppDefaults.CAMERA_SIZE_PRESET
CAMERA_SIZE_PRESET,
ScrcpyOptions.CAMERA_SIZE.defaultValue,
) { value ->
if (value == "custom") {
val customSize = sharedPrefs.getString(
AppPreferenceKeys.CAMERA_SIZE_CUSTOM,
AppDefaults.CAMERA_SIZE_CUSTOM
CAMERA_SIZE_CUSTOM,
ScrcpyOptions.CAMERA_SIZE_CUSTOM.defaultValue,
).orEmpty()
scrcpyOptions.cameraSize.set(customSize)
scrcpyOptions.cameraSizeCustom.set(customSize)
scrcpyOptions.cameraSizeUseCustom.set(true)
} else {
scrcpyOptions.cameraSize.set(value)
}
}
migrateString(
AppPreferenceKeys.CAMERA_AR,
AppDefaults.CAMERA_AR,
scrcpyOptions.cameraAr
CAMERA_AR,
ScrcpyOptions.CAMERA_AR.defaultValue,
scrcpyOptions.cameraAr,
)
migrateString(
AppPreferenceKeys.CAMERA_FPS,
AppDefaults.CAMERA_FPS
CAMERA_FPS,
ScrcpyOptions.CAMERA_FPS.defaultValue.toString(),
) { value ->
value.toIntOrNull()?.let { scrcpyOptions.cameraFps.set(it) }
}
migrateBoolean(
AppPreferenceKeys.CAMERA_HIGH_SPEED,
AppDefaults.CAMERA_HIGH_SPEED,
scrcpyOptions.cameraHighSpeed
CAMERA_HIGH_SPEED,
ScrcpyOptions.CAMERA_HIGH_SPEED.defaultValue,
scrcpyOptions.cameraHighSpeed,
)
// Audio Source
val audioSourcePreset = sharedPrefs.getString(
AppPreferenceKeys.AUDIO_SOURCE_PRESET,
AppDefaults.AUDIO_SOURCE_PRESET
).orEmpty().ifBlank { AppDefaults.AUDIO_SOURCE_PRESET }
AUDIO_SOURCE_PRESET,
"auto",
).orEmpty().ifBlank { "auto" }
if (audioSourcePreset == "custom") {
val customSource = sharedPrefs.getString(
AppPreferenceKeys.AUDIO_SOURCE_CUSTOM,
AppDefaults.AUDIO_SOURCE_CUSTOM
AUDIO_SOURCE_CUSTOM,
ScrcpyOptions.AUDIO_SOURCE.defaultValue,
).orEmpty()
scrcpyOptions.audioSource.set(customSource)
} else {
@@ -282,69 +276,69 @@ class PreferenceMigration(private val appContext: Context) {
}
migrateBoolean(
AppPreferenceKeys.AUDIO_DUP,
AppDefaults.AUDIO_DUP,
scrcpyOptions.audioDup
AUDIO_DUP,
ScrcpyOptions.AUDIO_DUP.defaultValue,
scrcpyOptions.audioDup,
)
migrateBoolean(
AppPreferenceKeys.NO_AUDIO_PLAYBACK,
AppDefaults.NO_AUDIO_PLAYBACK
NO_AUDIO_PLAYBACK,
!ScrcpyOptions.AUDIO_PLAYBACK.defaultValue,
) { value ->
scrcpyOptions.audioPlayback.set(!value) // Invert logic
}
migrateBoolean(
AppPreferenceKeys.REQUIRE_AUDIO,
AppDefaults.REQUIRE_AUDIO,
scrcpyOptions.requireAudio
REQUIRE_AUDIO,
ScrcpyOptions.REQUIRE_AUDIO.defaultValue,
scrcpyOptions.requireAudio,
)
// Max Size & FPS
migrateString(
AppPreferenceKeys.MAX_SIZE_INPUT,
AppDefaults.MAX_SIZE_INPUT
MAX_SIZE_INPUT,
ScrcpyOptions.MAX_SIZE.defaultValue.toString(),
) { value ->
value.toIntOrNull()?.let { scrcpyOptions.maxSize.set(it) }
}
migrateString(
AppPreferenceKeys.MAX_FPS_INPUT,
AppDefaults.MAX_FPS_INPUT,
scrcpyOptions.maxFps
MAX_FPS_INPUT,
ScrcpyOptions.MAX_FPS.defaultValue,
scrcpyOptions.maxFps,
)
// Encoders & Codec Options
migrateString(
AppPreferenceKeys.VIDEO_ENCODER,
AppDefaults.VIDEO_ENCODER,
scrcpyOptions.videoEncoder
VIDEO_ENCODER,
ScrcpyOptions.VIDEO_ENCODER.defaultValue,
scrcpyOptions.videoEncoder,
)
migrateString(
AppPreferenceKeys.VIDEO_CODEC_OPTION,
AppDefaults.VIDEO_CODEC_OPTION,
scrcpyOptions.videoCodecOptions
VIDEO_CODEC_OPTION,
ScrcpyOptions.VIDEO_CODEC_OPTIONS.defaultValue,
scrcpyOptions.videoCodecOptions,
)
migrateString(
AppPreferenceKeys.AUDIO_ENCODER,
AppDefaults.AUDIO_ENCODER,
scrcpyOptions.audioEncoder
AUDIO_ENCODER,
ScrcpyOptions.AUDIO_ENCODER.defaultValue,
scrcpyOptions.audioEncoder,
)
migrateString(
AppPreferenceKeys.AUDIO_CODEC_OPTION,
AppDefaults.AUDIO_CODEC_OPTION,
scrcpyOptions.audioCodecOptions
AUDIO_CODEC_OPTION,
ScrcpyOptions.AUDIO_CODEC_OPTIONS.defaultValue,
scrcpyOptions.audioCodecOptions,
)
// New Display
val newDisplayWidth = sharedPrefs.getString(
AppPreferenceKeys.NEW_DISPLAY_WIDTH,
AppDefaults.NEW_DISPLAY_WIDTH
NEW_DISPLAY_WIDTH,
"",
).orEmpty()
val newDisplayHeight = sharedPrefs.getString(
AppPreferenceKeys.NEW_DISPLAY_HEIGHT,
AppDefaults.NEW_DISPLAY_HEIGHT
NEW_DISPLAY_HEIGHT,
"",
).orEmpty()
val newDisplayDpi = sharedPrefs.getString(
AppPreferenceKeys.NEW_DISPLAY_DPI,
AppDefaults.NEW_DISPLAY_DPI
NEW_DISPLAY_DPI,
"",
).orEmpty()
if (newDisplayWidth.isNotBlank() && newDisplayHeight.isNotBlank()) {
@@ -358,34 +352,33 @@ class PreferenceMigration(private val appContext: Context) {
// Crop
val cropWidth = sharedPrefs.getString(
AppPreferenceKeys.CROP_WIDTH,
AppDefaults.CROP_WIDTH
CROP_WIDTH,
"",
).orEmpty()
val cropHeight = sharedPrefs.getString(
AppPreferenceKeys.CROP_HEIGHT,
AppDefaults.CROP_HEIGHT
CROP_HEIGHT,
"",
).orEmpty()
val cropX = sharedPrefs.getString(
AppPreferenceKeys.CROP_X,
AppDefaults.CROP_X
CROP_X,
"",
).orEmpty()
val cropY = sharedPrefs.getString(
AppPreferenceKeys.CROP_Y,
AppDefaults.CROP_Y
CROP_Y,
"",
).orEmpty()
if (cropWidth.isNotBlank() && cropHeight.isNotBlank() &&
cropX.isNotBlank() && cropY.isNotBlank()
if (cropWidth.isNotBlank() && cropHeight.isNotBlank()
&& cropX.isNotBlank() && cropY.isNotBlank()
) {
scrcpyOptions.crop.set("${cropWidth}:${cropHeight}:${cropX}:${cropY}")
}
// Audio enabled flag (convert to audio option)
val audioEnabled = sharedPrefs.getBoolean(
AppPreferenceKeys.AUDIO_ENABLED,
AppDefaults.AUDIO_ENABLED
migrateBoolean(
AUDIO_ENABLED,
ScrcpyOptions.AUDIO.defaultValue,
scrcpyOptions.audio,
)
scrcpyOptions.audio.set(audioEnabled)
Log.d(TAG, "ScrcpyOptions migration completed")
}
@@ -398,7 +391,7 @@ class PreferenceMigration(private val appContext: Context) {
// Migrate quick devices list
val quickDevicesRaw = appSharedPrefs.getString(
AppPreferenceKeys.QUICK_DEVICES,
QUICK_DEVICES,
""
).orEmpty()
if (quickDevicesRaw.isNotBlank()) {
@@ -407,27 +400,27 @@ class PreferenceMigration(private val appContext: Context) {
// Migrate quick connect input
migrateString(
AppPreferenceKeys.QUICK_CONNECT_INPUT,
AppDefaults.QUICK_CONNECT_INPUT,
quickDevices.quickConnectInput
QUICK_CONNECT_INPUT,
QuickDevices.QUICK_CONNECT_INPUT.defaultValue,
quickDevices.quickConnectInput,
)
Log.d(TAG, "QuickDevices migration completed")
}
/**
* 迁移 ADB 客户端数据RSA 密钥)
*/
private suspend fun migrateAdbClientData() {
val adbClientData = Storage.adbClientData
// 迁移 RSA 私钥
val privKey = sharedPrefs.getString("priv", null)
if (privKey != null) {
adbClientData.rsaPrivateKey.set(privKey)
Log.d(TAG, "ADB RSA private key migrated")
}
Log.d(TAG, "AdbClientData migration completed")
}
@@ -464,6 +457,15 @@ class PreferenceMigration(private val appContext: Context) {
settingProperty.set(value)
}
private suspend fun migrateInt(
key: String,
defaultValue: Int,
action: suspend (Int) -> Unit
) {
val value = appSharedPrefs.getInt(key, defaultValue)
action(value)
}
private suspend fun migrateBoolean(
key: String,
defaultValue: Boolean,
@@ -481,4 +483,82 @@ class PreferenceMigration(private val appContext: Context) {
val value = appSharedPrefs.getBoolean(key, defaultValue)
action(value)
}
companion object {
const val PREFS_NAME = "scrcpy_app_prefs"
// Devices
const val QUICK_DEVICES = "quick_devices"
const val QUICK_CONNECT_INPUT = "quick_connect_input"
const val PAIR_HOST = "pair_host"
const val PAIR_PORT = "pair_port"
const val PAIR_CODE = "pair_code"
const val AUDIO_ENABLED = "audio_enabled"
const val AUDIO_CODEC = "audio_codec"
const val AUDIO_BIT_RATE_INPUT = "audio_bit_rate_input"
const val AUDIO_BIT_RATE_KBPS = "audio_bit_rate_kbps"
const val VIDEO_CODEC = "video_codec"
const val VIDEO_BIT_RATE_MBPS = "video_bit_rate_mbps"
const val VIDEO_BIT_RATE_INPUT = "video_bit_rate_input"
const val TURN_SCREEN_OFF = "turn_screen_off"
const val NO_CONTROL = "no_control"
const val NO_VIDEO = "no_video"
const val VIDEO_SOURCE_PRESET = "video_source_preset"
const val DISPLAY_ID = "display_id"
const val CAMERA_ID = "camera_id"
const val CAMERA_FACING_PRESET = "camera_facing_preset"
const val CAMERA_SIZE_PRESET = "camera_size_preset"
const val CAMERA_SIZE_CUSTOM = "camera_size_custom"
const val CAMERA_AR = "camera_ar"
const val CAMERA_FPS = "camera_fps"
const val CAMERA_HIGH_SPEED = "camera_high_speed"
const val AUDIO_SOURCE_PRESET = "audio_source_preset"
const val AUDIO_SOURCE_CUSTOM = "audio_source_custom"
const val AUDIO_DUP = "audio_dup"
const val NO_AUDIO_PLAYBACK = "no_audio_playback"
const val REQUIRE_AUDIO = "require_audio"
const val MAX_SIZE_INPUT = "max_size_input"
const val MAX_FPS_INPUT = "max_fps_input"
const val VIDEO_ENCODER = "video_encoder"
const val VIDEO_CODEC_OPTION = "video_codec_options"
const val AUDIO_ENCODER = "audio_encoder"
const val AUDIO_CODEC_OPTION = "audio_codec_options"
const val NEW_DISPLAY_WIDTH = "new_display_width"
const val NEW_DISPLAY_HEIGHT = "new_display_height"
const val NEW_DISPLAY_DPI = "new_display_dpi"
const val CROP_WIDTH = "crop_width"
const val CROP_HEIGHT = "crop_height"
const val CROP_X = "crop_x"
const val CROP_Y = "crop_y"
// Settings
const val THEME_BASE_INDEX = "theme_base_index"
const val MONET = "monet"
const val FULLSCREEN_DEBUG_INFO = "fullscreen_debug_info"
const val SHOW_FULLSCREEN_VIRTUAL_BUTTONS = "show_fullscreen_virtual_buttons"
const val DEVICE_PREVIEW_CARD_HEIGHT_DP = "device_preview_card_height_dp"
const val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = "preview_virtual_button_show_text"
const val VIRTUAL_BUTTONS_LAYOUT = "virtual_buttons_layout"
const val CUSTOM_SERVER_URI = "custom_server_uri"
const val SERVER_REMOTE_PATH = "server_remote_path"
const val ADB_KEY_NAME = "adb_key_name"
const val ADB_PAIRING_AUTO_DISCOVER_ON_DIALOG_OPEN =
"adb_pairing_auto_discover_on_dialog_open"
const val ADB_AUTO_RECONNECT_PAIRED_DEVICE = "adb_auto_reconnect_paired_device"
const val ADB_MDNS_LAN_DISCOVERY = "adb_mdns_lan_discovery"
}
}