From 71e942b23eb7ff07d8e3d65fde0787f94e1d9384 Mon Sep 17 00:00:00 2001
From: Miuzarte <982809597@qq.com>
Date: Thu, 19 Mar 2026 12:25:53 +0800
Subject: [PATCH] refactor: organize codes; add README.md
---
README.md | 45 +
app/build.gradle.kts | 1 +
.../scrcpyforandroid/NativeCoreFacade.kt | 83 +-
.../scrcpyforandroid/constants/AppDefaults.kt | 112 +--
.../constants/AppPreferenceKeys.kt | 129 +--
.../scrcpyforandroid/constants/UiMotion.kt | 4 +-
.../nativecore/AnnexBDecoder.kt | 19 +-
.../nativecore/DirectAdbClient.kt | 68 +-
.../nativecore/NativeAdbService.kt | 13 +-
.../nativecore/ScrcpyAudioPlayer.kt | 50 +-
.../nativecore/ScrcpySessionManager.kt | 139 ++-
.../pages/AdvancedConfigPage.kt | 26 +-
.../scrcpyforandroid/pages/DevicePage.kt | 237 ++---
.../pages/FullscreenControlPage.kt | 15 +-
.../scrcpyforandroid/pages/MainPage.kt | 828 +++++++++---------
.../scrcpyforandroid/pages/SettingsPage.kt | 34 +-
.../pages/VirtualButtonOrderPage.kt | 13 +-
.../scrcpyforandroid/scaffolds/SuperSlide.kt | 3 +-
.../services/AppSettingsStore.kt | 640 ++++++++++----
.../services/DeviceConnectionLogic.kt | 9 +-
.../services/QuickDeviceStore.kt | 16 +-
.../scrcpyforandroid/widgets/DeviceWidgets.kt | 107 ++-
.../widgets/SortableCardList.kt | 5 +-
.../widgets/StatusCardModels.kt | 20 +-
.../widgets/VirtualButtons.kt | 95 +-
25 files changed, 1696 insertions(+), 1015 deletions(-)
create mode 100644 README.md
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f4cdc54
--- /dev/null
+++ b/README.md
@@ -0,0 +1,45 @@
+# Scrcpy For Android
+
+[Scrcpy](https://github.com/Genymobile/scrcpy) android client
+
+## 截图
+
+
+
+
+
+
+
+
+
+## 已知问题
+
+- ADB 配对流程未实现
+- 多指触控抬起后滞留
+- 快速离开再进入全屏会导致视频流关键帧丢失
+
+## 构建
+
+- JDK 17+
+- Android SDK(含 `compileSdk 36` / `buildTools 36.0.0`)
+- Android NDK `28.2.13676358`
+
+```bash
+./gradlew assembleDebug
+```
+
+Windows:
+
+```powershell
+.\gradlew.bat assembleDebug
+```
+
+## Credits
+
+- [Genymobile/scrcpy](https://github.com/Genymobile/scrcpy)
+- [YuKongA/miuix](https://github.com/compose-miuix-ui/miuix)
+- [tiann/KernelSU/manager](https://github.com/tiann/KernelSU/tree/main/manager)
+
+## License
+
+[Apache License 2.0](LICENSE)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 44c1332..3f6f8a3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -28,6 +28,7 @@ android {
ndk {
abiFilters.clear()
+ //noinspection ChromeOsAbiSupport
abiFilters += configuredAbiList
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/NativeCoreFacade.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/NativeCoreFacade.kt
index 3161ab5..5a0a375 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/NativeCoreFacade.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/NativeCoreFacade.kt
@@ -15,8 +15,8 @@ import java.io.File
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import java.util.ArrayDeque
-import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
@@ -34,7 +34,8 @@ class NativeCoreFacade(private val appContext: Context) {
private val bootstrapLock = Any()
private val bootstrapPackets = ArrayDeque()
private var packetCount: Long = 0
- @Volatile private var audioPlayer: ScrcpyAudioPlayer? = null
+ @Volatile
+ private var audioPlayer: ScrcpyAudioPlayer? = null
@Volatile
private var currentSessionInfo: ScrcpySessionInfo? = null
@@ -72,7 +73,10 @@ class NativeCoreFacade(private val appContext: Context) {
val currentId = surfaceIdentityMap[tag]
val requestId = surface?.let { System.identityHashCode(it) }
if (requestId != null && currentId != null && requestId != currentId) {
- Log.i(TAG, "unregisterVideoSurface(): skip stale request tag=$tag requestSurfaceId=$requestId currentSurfaceId=$currentId")
+ Log.i(
+ TAG,
+ "unregisterVideoSurface(): skip stale request tag=$tag requestSurfaceId=$requestId currentSurfaceId=$currentId"
+ )
return
}
Log.i(TAG, "unregisterVideoSurface(): tag=$tag surfaceId=$requestId")
@@ -178,7 +182,12 @@ class NativeCoreFacade(private val appContext: Context) {
audioPlayer?.release()
audioPlayer = null
if (info.audioCodecId != 0 && !request.noAudioPlayback) {
- Log.i(TAG, "scrcpyStart(): create audio player codecId=0x${info.audioCodecId.toUInt().toString(16)}")
+ Log.i(
+ TAG,
+ "scrcpyStart(): create audio player codecId=0x${
+ info.audioCodecId.toUInt().toString(16)
+ }"
+ )
val player = ScrcpyAudioPlayer(info.audioCodecId)
audioPlayer = player
sessionManager.attachAudioConsumer { packet ->
@@ -208,7 +217,11 @@ class NativeCoreFacade(private val appContext: Context) {
return true
}
- fun scrcpyListEncoders(customServerUri: String?, remotePath: String, serverVersion: String = "3.3.4"): ScrcpyEncoderLists {
+ fun scrcpyListEncoders(
+ customServerUri: String?,
+ remotePath: String,
+ serverVersion: String = "3.3.4"
+ ): ScrcpyEncoderLists {
return ioCall {
val serverJar = if (customServerUri.isNullOrBlank()) {
extractAssetToCache(DEFAULT_SERVER_ASSET)
@@ -232,7 +245,11 @@ class NativeCoreFacade(private val appContext: Context) {
}
}
- fun scrcpyListCameraSizes(customServerUri: String?, remotePath: String, serverVersion: String = "3.3.4"): ScrcpyCameraSizeLists {
+ fun scrcpyListCameraSizes(
+ customServerUri: String?,
+ remotePath: String,
+ serverVersion: String = "3.3.4"
+ ): ScrcpyCameraSizeLists {
return ioCall {
val serverJar = if (customServerUri.isNullOrBlank()) {
extractAssetToCache(DEFAULT_SERVER_ASSET)
@@ -307,7 +324,17 @@ class NativeCoreFacade(private val appContext: Context) {
buttons: Int = 0,
) {
ioExecute {
- runCatching { sessionManager.injectScroll(x, y, screenWidth, screenHeight, hScroll, vScroll, buttons) }
+ runCatching {
+ sessionManager.injectScroll(
+ x,
+ y,
+ screenWidth,
+ screenHeight,
+ hScroll,
+ vScroll,
+ buttons
+ )
+ }
}
}
@@ -475,10 +502,13 @@ class NativeCoreFacade(private val appContext: Context) {
val mime = when (session.codec.lowercase()) {
"h264" -> "video/avc"
"h265" -> "video/hevc"
- "av1" -> "video/av01"
+ "av1" -> "video/av01"
else -> "video/avc"
}
- Log.i(TAG, "createOrReplaceDecoder(): tag=$tag codec=$mime size=${session.width}x${session.height}")
+ Log.i(
+ TAG,
+ "createOrReplaceDecoder(): tag=$tag codec=$mime size=${session.width}x${session.height}"
+ )
val decoder = AnnexBDecoder(
width = session.width,
height = session.height,
@@ -489,7 +519,10 @@ class NativeCoreFacade(private val appContext: Context) {
if (current == null || (current.width == width && current.height == height)) {
return@AnnexBDecoder
}
- Log.i(TAG, "videoSizeChanged(): ${current.width}x${current.height} -> ${width}x${height}")
+ Log.i(
+ TAG,
+ "videoSizeChanged(): ${current.width}x${current.height} -> ${width}x${height}"
+ )
currentSessionInfo = current.copy(width = width, height = height)
mainHandler.post {
videoSizeListeners.forEach { listener ->
@@ -542,14 +575,22 @@ class NativeCoreFacade(private val appContext: Context) {
cacheBootstrapPacket(packet)
packetCount += 1
if (packetCount == 1L || packetCount % 120L == 0L) {
- Log.i(TAG, "videoFeed(): packets=$packetCount key=${packet.isKeyFrame} cfg=${packet.isConfig} decoders=${decoderMap.size}")
+ Log.i(
+ TAG,
+ "videoFeed(): packets=$packetCount key=${packet.isKeyFrame} cfg=${packet.isConfig} decoders=${decoderMap.size}"
+ )
}
decoderMap.forEach { (tag, decoder) ->
if (!surfaceIdentityMap.containsKey(tag)) {
return@forEach
}
runCatching {
- decoder.feedAnnexB(packet.data, packet.ptsUs, packet.isKeyFrame, packet.isConfig)
+ decoder.feedAnnexB(
+ packet.data,
+ packet.ptsUs,
+ packet.isKeyFrame,
+ packet.isConfig
+ )
}
}
}
@@ -634,21 +675,3 @@ data class ScrcpySessionInfo(
val codec: String,
val controlEnabled: Boolean,
)
-
-object AndroidKeycodes {
- const val HOME = 3
- const val BACK = 4
- const val POWER = 26
- const val APP_SWITCH = 187
- const val VOLUME_UP = 24
- const val VOLUME_DOWN = 25
-}
-
-object MotionActions {
- const val DOWN = 0
- const val UP = 1
- const val MOVE = 2
- const val CANCEL = 3
- const val POINTER_DOWN = 5
- const val POINTER_UP = 6
-}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppDefaults.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppDefaults.kt
index c0f811f..0053964 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppDefaults.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppDefaults.kt
@@ -1,75 +1,79 @@
package io.github.miuzarte.scrcpyforandroid.constants
object AppDefaults {
- const val MaxEventLogLines = 512
- const val DefaultAdbPort = 5555
+ const val EVENT_LOG_LINES = 512
+ const val ADB_PORT = 5555
// Devices
- const val DefaultQuickConnectInput = ""
+ const val QUICK_CONNECT_INPUT = ""
- const val DefaultPairHost = ""
- const val DefaultServerRemotePathInput = ""
- const val DefaultAdbKeyNameInput = ""
- const val DefaultPairPort = ""
- const val DefaultPairCode = ""
+ const val PAIR_HOST = ""
+ const val PAIR_PORT = ""
+ const val PAIR_CODE = ""
- const val DefaultAudioEnabled = true
- const val DefaultAudioCodec = "opus"
- const val DefaultAudioBitRateKbps = 128
- const val DefaultVideoCodec = "h264"
- const val DefaultBitRateMbps = 8f
- const val DefaultBitRateInput = "8.0"
+ 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 DefaultTurnScreenOff = false
- const val DefaultNoControl = false
- const val DefaultNoVideo = false
+ const val TURN_SCREEN_OFF = false
+ const val NO_CONTROL = false
+ const val NO_VIDEO = false
- const val DefaultVideoSourcePreset = "display"
- const val DefaultDisplayIdInput = ""
+ const val VIDEO_SOURCE_PRESET = "display"
+ const val DISPLAY_ID = ""
- const val DefaultCameraIdInput = ""
- const val DefaultCameraFacingPreset = ""
- const val DefaultCameraSizePreset = ""
- const val DefaultCameraSizeCustom = ""
- const val DefaultCameraArInput = ""
- const val DefaultCameraFpsInput = ""
- const val DefaultCameraHighSpeed = false
+ 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 DefaultAudioSourcePreset = "output"
- const val DefaultAudioSourceCustom = ""
- const val DefaultAudioDup = false
- const val DefaultNoAudioPlayback = false
- const val DefaultRequireAudio = 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 DefaultMaxSizeInput = ""
- const val DefaultMaxFpsInput = ""
+ const val MAX_SIZE_INPUT = ""
+ const val MAX_FPS_INPUT = ""
- const val DefaultVideoEncoder = ""
- const val DefaultVideoCodecOptions = ""
- const val DefaultAudioEncoder = ""
- const val DefaultAudioCodecOptions = ""
+ const val VIDEO_ENCODER = ""
+ const val VIDEO_CODEC_OPTION = ""
+ const val AUDIO_ENCODER = ""
+ const val AUDIO_CODEC_OPTION = ""
- const val DefaultNewDisplayWidth = ""
- const val DefaultNewDisplayHeight = ""
- const val DefaultNewDisplayDpi = ""
+ const val NEW_DISPLAY_WIDTH = ""
+ const val NEW_DISPLAY_HEIGHT = ""
+ const val NEW_DISPLAY_DPI = ""
- const val DefaultCropWidth = ""
- const val DefaultCropHeight = ""
- const val DefaultCropX = ""
- const val DefaultCropY = ""
+ const val CROP_WIDTH = ""
+ const val CROP_HEIGHT = ""
+ const val CROP_X = ""
+ const val CROP_Y = ""
// Settings
- const val DefaultThemeBaseIndex = 0
- const val DefaultMonetEnabled = false
+ const val THEME_BASE_INDEX = 0
+ const val MONET = false
- const val DefaultFullscreenDebugInfoEnabled = false
- const val DefaultShowFullscreenVirtualButtons = true
- const val DefaultDevicePreviewCardHeightDp = 320
- const val DefaultKeepScreenOnWhenStreamingEnabled = false
- const val DefaultVirtualButtonsOutside = "more,home,back"
- const val DefaultVirtualButtonsInMore = "app_switch,menu,notification,volume_up,volume_down,volume_mute,power,screenshot"
+ 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 VIRTUAL_BUTTONS_OUTSIDE = "more,home,back"
+ const val VIRTUAL_BUTTONS_IN_MORE =
+ "app_switch,menu,notification,volume_up,volume_down,volume_mute,power,screenshot"
- const val DefaultServerRemotePath = "/data/local/tmp/scrcpy-server.jar"
+ const val CUSTOM_SERVER_URI = ""
- const val DefaultAdbKeyName = "scrcpy"
+ 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 = ""
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppPreferenceKeys.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppPreferenceKeys.kt
index 4702b14..69b4832 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppPreferenceKeys.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/AppPreferenceKeys.kt
@@ -1,59 +1,78 @@
package io.github.miuzarte.scrcpyforandroid.constants
object AppPreferenceKeys {
- const val PrefsName = "scrcpy_app_prefs"
- const val NativeAdbKeyPrefsName = "nativecore_adb_rsa"
- const val NativeAdbPrivateKey = "priv"
- const val ThemeBaseIndex = "theme_base_index"
- const val MonetEnabled = "monet_enabled"
- const val FullscreenDebugInfoEnabled = "fullscreen_debug_info_enabled"
- const val ShowFullscreenVirtualButtons = "show_fullscreen_virtual_buttons"
- const val DevicePreviewCardHeightDp = "device_preview_card_height_dp"
- const val KeepScreenOnWhenStreamingEnabled = "keep_screen_on_when_streaming_enabled"
- const val VirtualButtonsOutside = "virtual_buttons_outside"
- const val VirtualButtonsInMore = "virtual_buttons_in_more"
- const val CustomServerUri = "custom_server_uri"
- const val ServerRemotePath = "server_remote_path"
- const val VideoCodec = "video_codec"
- const val AudioEnabled = "audio_enabled"
- const val AudioCodec = "audio_codec"
- const val PairHost = "pair_host"
- const val PairPort = "pair_port"
- const val PairCode = "pair_code"
- const val QuickConnectInput = "quick_connect_input"
- const val BitRateMbps = "bit_rate_mbps"
- const val BitRateInput = "bit_rate_input"
- const val AudioBitRateKbps = "audio_bit_rate_kbps"
- const val MaxSizeInput = "max_size_input"
- const val MaxFpsInput = "max_fps_input"
- const val NoControl = "no_control"
- const val VideoEncoder = "video_encoder"
- const val VideoCodecOptions = "video_codec_options"
- const val AudioEncoder = "audio_encoder"
- const val AudioCodecOptions = "audio_codec_options"
- const val AudioDup = "audio_dup"
- const val AudioSourcePreset = "audio_source_preset"
- const val AudioSourceCustom = "audio_source_custom"
- const val VideoSourcePreset = "video_source_preset"
- const val CameraIdInput = "camera_id_input"
- const val CameraFacingPreset = "camera_facing_preset"
- const val CameraSizePreset = "camera_size_preset"
- const val CameraSizeCustom = "camera_size_custom"
- const val CameraArInput = "camera_ar_input"
- const val CameraFpsInput = "camera_fps_input"
- const val CameraHighSpeed = "camera_high_speed"
- const val NoAudioPlayback = "no_audio_playback"
- const val NoVideo = "no_video"
- const val RequireAudio = "require_audio"
- const val TurnScreenOff = "turn_screen_off"
- const val NewDisplayWidth = "new_display_width"
- const val NewDisplayHeight = "new_display_height"
- const val NewDisplayDpi = "new_display_dpi"
- const val DisplayIdInput = "display_id_input"
- const val CropWidth = "crop_width"
- const val CropHeight = "crop_height"
- const val CropX = "crop_x"
- const val CropY = "crop_y"
- const val AdbKeyName = "adb_key_name"
- const val QuickDevices = "quick_devices"
+ const val PREFS_NAME = "scrcpy_app_prefs"
+ const val NATIVE_ADB_KEY_PREFS_NAME = "nativecore_adb_rsa"
+ const val NATIVE_ADB_PRIVATE_KEY = "priv"
+
+ // 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 VIRTUAL_BUTTONS_OUTSIDE = "virtual_buttons_outside"
+ const val VIRTUAL_BUTTONS_IN_MORE = "virtual_buttons_in_more"
+
+ const val CUSTOM_SERVER_URI = "custom_server_uri"
+
+ const val SERVER_REMOTE_PATH = "server_remote_path"
+
+ const val ADB_KEY_NAME = "adb_key_name"
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/UiMotion.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/UiMotion.kt
index 2991b2d..d9ec651 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/UiMotion.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/constants/UiMotion.kt
@@ -1,6 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.constants
object UiMotion {
- const val PageSwitchDampingRatio = 0.9f
- const val PageSwitchStiffness = 700f
+ const val PAGE_SWITCH_DAMPING_RATIO = 0.9f
+ const val PAGE_SWITCH_STIFFNESS = 700f
}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/AnnexBDecoder.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/AnnexBDecoder.kt
index 0f9f415..6bb143f 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/AnnexBDecoder.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/AnnexBDecoder.kt
@@ -24,6 +24,7 @@ class AnnexBDecoder(
private var outCount = 0L
private var fpsWindowStartNs = System.nanoTime()
private var fpsWindowFrameCount = 0
+
@Volatile
private var released = false
@@ -65,16 +66,26 @@ class AnnexBDecoder(
codec.queueInputBuffer(inputIndex, 0, data.size, ptsUs, flags)
inCount += 1
if (inCount == 1L || inCount % 180L == 0L || isConfig) {
- Log.i(TAG, "feed(): mime=$decoderMime in=$inCount size=${data.size} key=$isKeyFrame cfg=$isConfig pts=$ptsUs")
+ Log.i(
+ TAG,
+ "feed(): mime=$decoderMime in=$inCount size=${data.size} key=$isKeyFrame cfg=$isConfig pts=$ptsUs"
+ )
}
} else {
if (isConfig || isKeyFrame) {
- Log.w(TAG, "drop critical packet: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig")
+ Log.w(
+ TAG,
+ "drop critical packet: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig"
+ )
}
}
drainOutput()
}.onFailure {
- Log.w(TAG, "feed failed: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig", it)
+ Log.w(
+ TAG,
+ "feed failed: mime=$decoderMime size=${data.size} key=$isKeyFrame cfg=$isConfig",
+ it
+ )
}
}
@@ -108,10 +119,12 @@ class AnnexBDecoder(
Log.i(TAG, "drain(): mime=$decoderMime out=$outCount")
}
}
+
outIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
notifyOutputSize(codec.outputFormat)
continue
}
+
else -> return
}
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/DirectAdbClient.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/DirectAdbClient.kt
index 24088bb..1fffb0c 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/DirectAdbClient.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/DirectAdbClient.kt
@@ -41,23 +41,33 @@ internal class DirectAdbTransport(private val context: Context) {
val privateKey: PrivateKey get() = keys.first
val publicKeyX509: ByteArray get() = keys.second
- @Volatile var keyName: String = AppDefaults.DefaultAdbKeyName
+ @Volatile
+ var keyName: String = AppDefaults.ADB_KEY_NAME
fun connect(host: String, port: Int): DirectAdbConnection {
Log.i(TAG, "connect(): opening direct adbd transport to $host:$port")
- val conn = DirectAdbConnection(host, port, privateKey, publicKeyX509, keyName.ifBlank { AppDefaults.DefaultAdbKeyName })
+ val conn = DirectAdbConnection(
+ host,
+ port,
+ privateKey,
+ publicKeyX509,
+ keyName.ifBlank { AppDefaults.ADB_KEY_NAME })
conn.handshake()
Log.i(TAG, "connect(): handshake success for $host:$port")
return conn
}
private fun loadOrCreate(): Pair {
- val prefs = context.getSharedPreferences(AppPreferenceKeys.NativeAdbKeyPrefsName, Context.MODE_PRIVATE)
- val privB64 = prefs.getString(AppPreferenceKeys.NativeAdbPrivateKey, null)
+ val prefs = context.getSharedPreferences(
+ AppPreferenceKeys.NATIVE_ADB_KEY_PREFS_NAME,
+ Context.MODE_PRIVATE
+ )
+ val privB64 = prefs.getString(AppPreferenceKeys.NATIVE_ADB_PRIVATE_KEY, null)
if (privB64 != null) {
try {
val kf = KeyFactory.getInstance("RSA")
- val priv = kf.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privB64, Base64.DEFAULT)))
+ val priv =
+ kf.generatePrivate(PKCS8EncodedKeySpec(Base64.decode(privB64, Base64.DEFAULT)))
val pub = derivePublicX509(priv)
Log.i(TAG, "loadOrCreate(): loaded persisted RSA key pair, fp=${fingerprint(pub)}")
return Pair(priv, pub)
@@ -68,8 +78,16 @@ internal class DirectAdbTransport(private val context: Context) {
val kpg = KeyPairGenerator.getInstance("RSA")
kpg.initialize(2048)
val kp = kpg.generateKeyPair()
- prefs.edit { putString(AppPreferenceKeys.NativeAdbPrivateKey, Base64.encodeToString(kp.private.encoded, Base64.NO_WRAP)) }
- Log.i(TAG, "loadOrCreate(): generated new RSA key pair, fp=${fingerprint(kp.public.encoded)}")
+ prefs.edit {
+ putString(
+ AppPreferenceKeys.NATIVE_ADB_PRIVATE_KEY,
+ Base64.encodeToString(kp.private.encoded, Base64.NO_WRAP)
+ )
+ }
+ Log.i(
+ TAG,
+ "loadOrCreate(): generated new RSA key pair, fp=${fingerprint(kp.public.encoded)}"
+ )
return Pair(kp.private, kp.public.encoded)
}
@@ -96,7 +114,7 @@ internal class DirectAdbConnection(
val port: Int,
private val privateKey: PrivateKey,
private val publicKeyX509: ByteArray,
- private val keyName: String = AppDefaults.DefaultAdbKeyName,
+ private val keyName: String = AppDefaults.ADB_KEY_NAME,
) : AutoCloseable {
private val sha1DigestInfoPrefix = byteArrayOf(
@@ -122,7 +140,9 @@ internal class DirectAdbConnection(
private lateinit var rawOut: OutputStream
private val nextLocalId = AtomicInteger(1)
private val streams = ConcurrentHashMap()
- @Volatile private var closed = false
+
+ @Volatile
+ private var closed = false
private var readerThread: Thread? = null
companion object {
@@ -171,9 +191,17 @@ internal class DirectAdbConnection(
throw IOException("ADB: connection rejected. Please accept the authorisation dialog on the target device.")
}
}
- else -> throw IOException("ADB: unexpected message 0x${afterSign.command.toString(16)} after AUTH_SIGNATURE")
+
+ else -> throw IOException(
+ "ADB: unexpected message 0x${
+ afterSign.command.toString(
+ 16
+ )
+ } after AUTH_SIGNATURE"
+ )
}
}
+
else -> throw IOException("ADB: unexpected initial message 0x${first.command.toString(16)}")
}
@@ -262,6 +290,7 @@ internal class DirectAdbConnection(
sendMsg(A_CLSE, 0, msg.arg0)
}
}
+
A_CLSE -> streams.remove(msg.arg1)?.forceClose()
A_OPEN -> sendMsg(A_CLSE, 0, msg.arg0)
}
@@ -275,7 +304,12 @@ internal class DirectAdbConnection(
}
@Synchronized
- private fun sendMsg(command: Int, arg0: Int = 0, arg1: Int = 0, data: ByteArray = ByteArray(0)) {
+ private fun sendMsg(
+ command: Int,
+ arg0: Int = 0,
+ arg1: Int = 0,
+ data: ByteArray = ByteArray(0)
+ ) {
val crc = data.fold(0L) { acc, b -> acc + (b.toLong() and 0xFF) }.toInt()
val header = ByteBuffer.allocate(24).order(ByteOrder.LITTLE_ENDIAN)
.putInt(command).putInt(arg0).putInt(arg1)
@@ -296,7 +330,8 @@ internal class DirectAdbConnection(
val dataLen = buf.int
buf.int
buf.int
- val data = if (dataLen > 0) ByteArray(dataLen).also { rawIn.readExact(it) } else ByteArray(0)
+ val data =
+ if (dataLen > 0) ByteArray(dataLen).also { rawIn.readExact(it) } else ByteArray(0)
return AdbMsg(command, arg0, arg1, data)
}
@@ -391,12 +426,16 @@ internal class AdbSocketStream(
private const val A_CLSE = 0x45534c43
}
- @Volatile var remoteId: Int = 0
- @Volatile var closed: Boolean = false
+ @Volatile
+ var remoteId: Int = 0
+
+ @Volatile
+ var closed: Boolean = false
private val latch = CountDownLatch(1)
private val latchOk = AtomicBoolean(false)
private val queue = LinkedBlockingQueue()
+
private object EndOfStreamMarker
val inputStream: InputStream = InStream()
@@ -476,6 +515,7 @@ internal class AdbSocketStream(
if (len == 0) return
sender(A_WRTE, localId, remoteId, b.copyOfRange(off, off + len))
}
+
override fun flush() {}
}
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/NativeAdbService.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/NativeAdbService.kt
index 0589b49..c00df64 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/NativeAdbService.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/NativeAdbService.kt
@@ -7,13 +7,18 @@ import java.nio.file.Path
class NativeAdbService(appContext: Context) {
private val transport = DirectAdbTransport(appContext)
- @Volatile private var connection: DirectAdbConnection? = null
- @Volatile private var connectedHost: String? = null
- @Volatile private var connectedPort: Int? = null
+ @Volatile
+ private var connection: DirectAdbConnection? = null
+ @Volatile
+ private var connectedHost: String? = null
+ @Volatile
+ private var connectedPort: Int? = null
var keyName: String
get() = transport.keyName
- set(value) { transport.keyName = value }
+ set(value) {
+ transport.keyName = value
+ }
@Synchronized
fun pair(host: String, port: Int, pairingCode: String): Boolean {
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpyAudioPlayer.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpyAudioPlayer.kt
index d34bf09..5e3ee36 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpyAudioPlayer.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpyAudioPlayer.kt
@@ -4,7 +4,6 @@ import android.media.AudioAttributes
import android.media.AudioFormat
import android.media.AudioManager
import android.media.AudioTrack
-import android.os.Build
import android.media.MediaCodec
import android.media.MediaFormat
import android.util.Log
@@ -23,15 +22,22 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
private var audioTrack: AudioTrack? = null
private val bufferInfo = MediaCodec.BufferInfo()
- @Volatile private var prepared = false
- @Volatile private var released = false
+ @Volatile
+ private var prepared = false
+ @Volatile
+ private var released = false
private var packetCount = 0L
fun feedPacket(data: ByteArray, ptsUs: Long, isConfig: Boolean) {
if (released) return
if (isConfig) {
- Log.i(TAG, "feedPacket(): config packet size=${data.size} codec=0x${codecId.toUInt().toString(16)}")
+ Log.i(
+ TAG,
+ "feedPacket(): config packet size=${data.size} codec=0x${
+ codecId.toUInt().toString(16)
+ }"
+ )
when (codecId) {
AUDIO_CODEC_OPUS -> prepareOpus(data)
AUDIO_CODEC_AAC -> prepareAac(data)
@@ -47,13 +53,7 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
}
if (codecId == AUDIO_CODEC_RAW) {
- ensureRawAudioTrack()?.let { track ->
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- track.write(data, 0, data.size, AudioTrack.WRITE_NON_BLOCKING)
- } else {
- track.write(data, 0, data.size)
- }
- }
+ ensureRawAudioTrack()?.write(data, 0, data.size, AudioTrack.WRITE_NON_BLOCKING)
return
}
@@ -74,11 +74,16 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
private fun prepareOpus(opusHead: ByteArray) {
if (prepared || released) return
runCatching {
- val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_OPUS, SAMPLE_RATE, CHANNELS)
+ val format = MediaFormat.createAudioFormat(
+ MediaFormat.MIMETYPE_AUDIO_OPUS,
+ SAMPLE_RATE,
+ CHANNELS
+ )
format.setByteBuffer("csd-0", ByteBuffer.wrap(opusHead))
// pre-skip field: 2 bytes LE at offset 10 of the OpusHead
if (opusHead.size >= 12) {
- val preSkip = ((opusHead[11].toInt() and 0xFF) shl 8) or (opusHead[10].toInt() and 0xFF)
+ val preSkip =
+ ((opusHead[11].toInt() and 0xFF) shl 8) or (opusHead[10].toInt() and 0xFF)
val codecDelayNs = preSkip.toLong() * 1_000_000_000L / SAMPLE_RATE
format.setByteBuffer("csd-1", longBuffer(codecDelayNs))
format.setByteBuffer("csd-2", longBuffer(OPUS_SEEK_PREROLL_NS))
@@ -90,7 +95,8 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
private fun prepareAac(aacConfig: ByteArray) {
if (prepared || released) return
runCatching {
- val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNELS)
+ val format =
+ MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, SAMPLE_RATE, CHANNELS)
format.setByteBuffer("csd-0", ByteBuffer.wrap(aacConfig))
startCodecAndTrack(format)
}.onFailure { Log.w(TAG, "prepareAac failed", it) }
@@ -99,7 +105,11 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
private fun prepareFlac(flacConfig: ByteArray) {
if (prepared || released) return
runCatching {
- val format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_FLAC, SAMPLE_RATE, CHANNELS)
+ val format = MediaFormat.createAudioFormat(
+ MediaFormat.MIMETYPE_AUDIO_FLAC,
+ SAMPLE_RATE,
+ CHANNELS
+ )
if (flacConfig.isNotEmpty()) {
format.setByteBuffer("csd-0", ByteBuffer.wrap(flacConfig))
}
@@ -161,11 +171,7 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
val pcm = ByteArray(size)
outBuf.position(bufferInfo.offset)
outBuf.get(pcm)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- track.write(pcm, 0, size, AudioTrack.WRITE_NON_BLOCKING)
- } else {
- track.write(pcm, 0, size)
- }
+ track.write(pcm, 0, size, AudioTrack.WRITE_NON_BLOCKING)
}
codec.releaseOutputBuffer(idx, false)
idx = codec.dequeueOutputBuffer(bufferInfo, 0L)
@@ -190,9 +196,9 @@ class ScrcpyAudioPlayer(private val codecId: Int) {
companion object {
private const val TAG = "ScrcpyAudioPlayer"
const val AUDIO_CODEC_OPUS = 0x6f707573
- const val AUDIO_CODEC_AAC = 0x00616163
+ const val AUDIO_CODEC_AAC = 0x00616163
const val AUDIO_CODEC_FLAC = 0x666c6163
- const val AUDIO_CODEC_RAW = 0x00726177
+ const val AUDIO_CODEC_RAW = 0x00726177
private const val SAMPLE_RATE = 48000
private const val CHANNELS = 2
private const val CODEC_TIMEOUT_US = 10_000L
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpySessionManager.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpySessionManager.kt
index 0405161..deb2af6 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpySessionManager.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/nativecore/ScrcpySessionManager.kt
@@ -11,21 +11,25 @@ import java.io.InputStreamReader
import java.nio.file.Path
import java.security.SecureRandom
import java.util.ArrayDeque
-import java.util.LinkedHashSet
import kotlin.concurrent.thread
import kotlin.math.roundToInt
class ScrcpySessionManager(private val adbService: NativeAdbService) {
@Volatile
private var activeSession: ActiveSession? = null
+
@Volatile
private var videoConsumer: ((VideoPacket) -> Unit)? = null
+
@Volatile
private var videoReaderThread: Thread? = null
+
@Volatile
private var audioConsumer: ((AudioPacket) -> Unit)? = null
+
@Volatile
private var audioReaderThread: Thread? = null
+
@Volatile
private var lastServerCommand: String? = null
private val serverLogBuffer = ArrayDeque()
@@ -44,7 +48,10 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
adbService.push(serverJarPath, targetPath)
val cmd = buildServerCommand(targetPath, scid, options)
lastServerCommand = cmd
- Log.i(TAG, "start(): socket=$socketName codec=${options.videoCodec} audio=${options.audio} audioCodec=${options.audioCodec}")
+ Log.i(
+ TAG,
+ "start(): socket=$socketName codec=${options.videoCodec} audio=${options.audio} audioCodec=${options.audioCodec}"
+ )
val serverStream = adbService.openShellStream(cmd)
val serverLogThread = startServerLogThread(serverStream, socketName)
Thread.sleep(SERVER_BOOT_DELAY_MS)
@@ -64,13 +71,16 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
videoStream = firstStream
videoInput = firstInput
}
+
options.audio -> {
audioStream = firstStream
audioInput = firstInput
}
+
options.control -> {
controlStream = firstStream
}
+
else -> {
throw IllegalArgumentException("At least one of video/audio/control must be enabled")
}
@@ -128,11 +138,15 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
audioStream = audioStream,
audioInput = audioInput,
controlStream = controlStream,
- controlWriter = controlStream?.outputStream?.let { ScrcpyControlWriter(DataOutputStream(it)) },
+ controlWriter = controlStream?.outputStream?.let {
+ ScrcpyControlWriter(
+ DataOutputStream(it)
+ )
+ },
)
return sessionInfo
} catch (t: Throwable) {
- val tail = snapshotServerLogs(maxLines = 120)
+ val tail = snapshotServerLogs()
val detail = if (tail.isBlank()) "" else " | server_log_tail=\n$tail"
throw IllegalStateException("scrcpy start failed: ${t.message}$detail", t)
}
@@ -173,7 +187,7 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
)
} catch (_: EOFException) {
break
- } catch (e: InterruptedException) {
+ } catch (_: InterruptedException) {
// Ignore transient interrupts while session remains active.
if (activeSession !== session || vStream.closed) {
break
@@ -212,10 +226,12 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
Log.w(TAG, "audio disabled by server")
return@thread
}
+
AUDIO_ERROR -> {
Log.e(TAG, "audio stream configuration error from server")
return@thread
}
+
else -> {
Log.i(TAG, "audio stream codec=0x${streamCodecId.toUInt().toString(16)}")
}
@@ -223,7 +239,9 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
if (session.info.audioCodecId != 0 && streamCodecId != session.info.audioCodecId) {
Log.w(
TAG,
- "audio codec mismatch: requested=0x${session.info.audioCodecId.toUInt().toString(16)} stream=0x${streamCodecId.toUInt().toString(16)}",
+ "audio codec mismatch: requested=0x${
+ session.info.audioCodecId.toUInt().toString(16)
+ } stream=0x${streamCodecId.toUInt().toString(16)}",
)
}
@@ -238,10 +256,16 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
val isConfig = (ptsAndFlags and PACKET_FLAG_CONFIG) != 0L
val ptsUs = ptsAndFlags and PACKET_PTS_MASK
- audioConsumer?.invoke(AudioPacket(data = payload, ptsUs = ptsUs, isConfig = isConfig))
+ audioConsumer?.invoke(
+ AudioPacket(
+ data = payload,
+ ptsUs = ptsUs,
+ isConfig = isConfig
+ )
+ )
} catch (_: EOFException) {
break
- } catch (e: InterruptedException) {
+ } catch (_: InterruptedException) {
if (activeSession !== session || aStream.closed) break
Thread.interrupted()
} catch (e: Exception) {
@@ -279,12 +303,38 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
actionButton: Int,
buttons: Int,
) {
- requireControlWriter().injectTouch(action, pointerId, x, y, screenWidth, screenHeight, pressure, actionButton, buttons)
+ requireControlWriter().injectTouch(
+ action,
+ pointerId,
+ x,
+ y,
+ screenWidth,
+ screenHeight,
+ pressure,
+ actionButton,
+ buttons
+ )
}
@Synchronized
- fun injectScroll(x: Int, y: Int, screenWidth: Int, screenHeight: Int, hScroll: Float, vScroll: Float, buttons: Int) {
- requireControlWriter().injectScroll(x, y, screenWidth, screenHeight, hScroll, vScroll, buttons)
+ fun injectScroll(
+ x: Int,
+ y: Int,
+ screenWidth: Int,
+ screenHeight: Int,
+ hScroll: Float,
+ vScroll: Float,
+ buttons: Int
+ ) {
+ requireControlWriter().injectScroll(
+ x,
+ y,
+ screenWidth,
+ screenHeight,
+ hScroll,
+ vScroll,
+ buttons
+ )
}
@Synchronized
@@ -384,10 +434,15 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
}
private fun requireControlWriter(): ScrcpyControlWriter {
- return activeSession?.controlWriter ?: throw IllegalStateException("scrcpy control channel not available")
+ return activeSession?.controlWriter
+ ?: throw IllegalStateException("scrcpy control channel not available")
}
- private fun buildServerCommand(targetPath: String, scid: Int, options: ScrcpyStartOptions): String {
+ private fun buildServerCommand(
+ targetPath: String,
+ scid: Int,
+ options: ScrcpyStartOptions
+ ): String {
val serverArgs = buildServerArgs(scid, options)
return "CLASSPATH=$targetPath app_process / com.genymobile.scrcpy.Server ${options.serverVersion} $serverArgs"
}
@@ -474,7 +529,7 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
ServerArg(
type = ServerArgType.NUMBER,
key = "video_bit_rate",
- value = options.videoBitRate, zeroValueBehavior = ZeroValueBehavior.KEEP,
+ value = options.videoBitRate,
),
ServerArg(
type = ServerArgType.NUMBER,
@@ -528,7 +583,6 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
key = "audio_bit_rate",
value = options.audioBitRate,
includeWhen = options.audio,
- zeroValueBehavior = ZeroValueBehavior.KEEP,
),
ServerArg(
type = ServerArgType.STRING,
@@ -539,7 +593,8 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
ServerArg(
type = ServerArgType.BOOLEAN,
key = "audio_dup",
- value = options.audioDup, includeWhen = options.audioDup,
+ value = options.audioDup,
+ includeWhen = options.audioDup,
),
ServerArg(
type = ServerArgType.STRING,
@@ -579,15 +634,22 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
ServerArg(
type = ServerArgType.BOOLEAN,
key = "list_encoders",
- value = options.listEncoders, includeWhen = options.listEncoders,
+ value = options.listEncoders,
+ includeWhen = options.listEncoders,
),
ServerArg(
type = ServerArgType.BOOLEAN,
key = "list_camera_sizes",
- value = options.listCameraSizes, includeWhen = options.listCameraSizes,
+ value = options.listCameraSizes,
+ includeWhen = options.listCameraSizes,
),
// Reserved for future args that require repeated/list values.
- // ServerArg(type = ServerArgType.LIST, key = "_unused_list", value = emptyList(), includeWhen = false),
+ // ServerArg(
+ // type = ServerArgType.LIST,
+ // key = "_unused_list",
+ // value = emptyList(),
+ // includeWhen = false,
+ // ),
)
val rendered = args.mapNotNull { it.render() }
@@ -608,8 +670,8 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
}
private enum class ZeroValueBehavior {
- KEEP,
OMIT,
+ KEEP,
}
private data class ServerArg(
@@ -658,7 +720,7 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
?.filter { it.isNotBlank() }
.orEmpty()
if (items.isEmpty()) return null
- return "$key=${items.joinToString(",")}"
+ return "$key=${items.joinToString(",")}"
}
}
@@ -716,7 +778,12 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
private fun startServerLogThread(serverStream: AdbSocketStream, socketName: String): Thread {
return thread(start = true, name = "scrcpy-server-log") {
try {
- BufferedReader(InputStreamReader(serverStream.inputStream, Charsets.UTF_8)).use { reader ->
+ BufferedReader(
+ InputStreamReader(
+ serverStream.inputStream,
+ Charsets.UTF_8
+ )
+ ).use { reader ->
while (true) {
val line = reader.readLine() ?: break
appendServerLog(line)
@@ -740,7 +807,7 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
}
@Synchronized
- private fun snapshotServerLogs(maxLines: Int): String {
+ private fun snapshotServerLogs(maxLines: Int = 120): String {
if (serverLogBuffer.isEmpty()) {
return ""
}
@@ -748,7 +815,10 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
return serverLogBuffer.toList().takeLast(take).joinToString("\n")
}
- private fun openAbstractSocketWithRetry(socketName: String, expectDummyByte: Boolean): AdbSocketStream {
+ private fun openAbstractSocketWithRetry(
+ socketName: String,
+ expectDummyByte: Boolean
+ ): AdbSocketStream {
var lastEx: Exception? = null
repeat(CONNECT_RETRY_COUNT) { attempt ->
try {
@@ -963,7 +1033,15 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
}
@Synchronized
- fun injectScroll(x: Int, y: Int, screenWidth: Int, screenHeight: Int, hScroll: Float, vScroll: Float, buttons: Int) {
+ fun injectScroll(
+ x: Int,
+ y: Int,
+ screenWidth: Int,
+ screenHeight: Int,
+ hScroll: Float,
+ vScroll: Float,
+ buttons: Int
+ ) {
output.writeByte(TYPE_INJECT_SCROLL_EVENT)
writePosition(x, y, screenWidth, screenHeight)
output.writeShort(encodeSignedFixedPoint16(hScroll / 16f))
@@ -1033,6 +1111,7 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
private const val AUDIO_CODEC_AAC = 0x00616163
private const val AUDIO_CODEC_FLAC = 0x666c6163
private const val AUDIO_CODEC_RAW = 0x00726177
+
// Audio stream disable codes from server (writeDisableStream)
private const val AUDIO_DISABLED = 0
private const val AUDIO_ERROR = 1
@@ -1044,10 +1123,12 @@ class ScrcpySessionManager(private val adbService: NativeAdbService) {
private const val TYPE_SET_DISPLAY_POWER = 10
private val VIDEO_ENCODER_REGEX = Regex("--video-encoder=([\\w.\\-]+)")
private val AUDIO_ENCODER_REGEX = Regex("--audio-encoder=([\\w.\\-]+)")
- private val VIDEO_ENCODER_FALLBACK_REGEX = Regex("""--video-encoder=['\"]?([^'\"\s]+)""")
- private val AUDIO_ENCODER_FALLBACK_REGEX = Regex("""--audio-encoder=['\"]?([^'\"\s]+)""")
- private val VIDEO_ENCODER_TYPE_REGEX = Regex("""--video-codec=[^\s]+\s+--video-encoder=([^\s]+).*?\((hw|sw)\)""")
- private val AUDIO_ENCODER_TYPE_REGEX = Regex("""--audio-codec=[^\s]+\s+--audio-encoder=([^\s]+).*?\((hw|sw)\)""")
+ private val VIDEO_ENCODER_FALLBACK_REGEX = Regex("""--video-encoder=['"]?([^'"\s]+)""")
+ private val AUDIO_ENCODER_FALLBACK_REGEX = Regex("""--audio-encoder=['"]?([^'"\s]+)""")
+ private val VIDEO_ENCODER_TYPE_REGEX =
+ Regex("""--video-codec=\S+\s+--video-encoder=(\S+).*?\((hw|sw)\)""")
+ private val AUDIO_ENCODER_TYPE_REGEX =
+ Regex("""--audio-codec=\S+\s+--audio-encoder=(\S+).*?\((hw|sw)\)""")
private val CAMERA_SIZE_REGEX = Regex("--camera-size=([0-9]+x[0-9]+)")
private val CAMERA_SIZE_FALLBACK_REGEX = Regex("\\b([1-9][0-9]{1,4}x[1-9][0-9]{1,4})\\b")
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/AdvancedConfigPage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/AdvancedConfigPage.kt
index 2484790..e0db43a 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/AdvancedConfigPage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/AdvancedConfigPage.kt
@@ -1,7 +1,6 @@
package io.github.miuzarte.scrcpyforandroid.pages
import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -12,6 +11,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
@@ -22,11 +22,10 @@ import io.github.miuzarte.scrcpyforandroid.constants.ScrcpyPresets
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
import io.github.miuzarte.scrcpyforandroid.scaffolds.SuperSlide
-import kotlin.math.roundToInt
import kotlinx.coroutines.launch
import top.yukonga.miuix.kmp.basic.Card
-import top.yukonga.miuix.kmp.basic.SnackbarHostState
import top.yukonga.miuix.kmp.basic.ScrollBehavior
+import top.yukonga.miuix.kmp.basic.SnackbarHostState
import top.yukonga.miuix.kmp.basic.SpinnerEntry
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextField
@@ -34,6 +33,7 @@ import top.yukonga.miuix.kmp.extra.SuperArrow
import top.yukonga.miuix.kmp.extra.SuperDropdown
import top.yukonga.miuix.kmp.extra.SuperSpinner
import top.yukonga.miuix.kmp.extra.SuperSwitch
+import kotlin.math.roundToInt
private val AUDIO_SOURCE_OPTIONS = listOf(
"output" to "output",
@@ -142,15 +142,20 @@ internal fun AdvancedConfigPage(
) {
val focusManager = LocalFocusManager.current
val scope = rememberCoroutineScope()
- val maxSizePresetIndex = presetIndexFromInputForAdvancedPage(maxSizeInput, ScrcpyPresets.MaxSize)
+ val maxSizePresetIndex =
+ presetIndexFromInputForAdvancedPage(maxSizeInput, ScrcpyPresets.MaxSize)
val maxFpsPresetIndex = presetIndexFromInputForAdvancedPage(maxFpsInput, ScrcpyPresets.MaxFPS)
val audioSourceItems = AUDIO_SOURCE_OPTIONS.map { it.second }
- val audioSourceIndex = AUDIO_SOURCE_OPTIONS.indexOfFirst { it.first == audioSourcePreset }.let { if (it >= 0) it else 0 }
+ val audioSourceIndex = AUDIO_SOURCE_OPTIONS.indexOfFirst { it.first == audioSourcePreset }
+ .let { if (it >= 0) it else 0 }
val videoSourceItems = VIDEO_SOURCE_OPTIONS.map { it.second }
- val videoSourceIndex = VIDEO_SOURCE_OPTIONS.indexOfFirst { it.first == videoSourcePreset }.let { if (it >= 0) it else 0 }
+ val videoSourceIndex = VIDEO_SOURCE_OPTIONS.indexOfFirst { it.first == videoSourcePreset }
+ .let { if (it >= 0) it else 0 }
val cameraFacingItems = CAMERA_FACING_OPTIONS.map { it.second }
- val cameraFacingIndex = CAMERA_FACING_OPTIONS.indexOfFirst { it.first == cameraFacingPreset }.let { if (it >= 0) it else 0 }
- val cameraFpsPresetIndex = presetIndexFromInputForAdvancedPage(cameraFpsInput, CAMERA_FPS_PRESETS)
+ val cameraFacingIndex = CAMERA_FACING_OPTIONS.indexOfFirst { it.first == cameraFacingPreset }
+ .let { if (it >= 0) it else 0 }
+ val cameraFpsPresetIndex =
+ presetIndexFromInputForAdvancedPage(cameraFpsInput, CAMERA_FPS_PRESETS)
val videoEncoderEntries = videoEncoderDropdownItems.map { encoderName ->
if (encoderName == "默认") {
SpinnerEntry(title = encoderName)
@@ -269,7 +274,10 @@ internal fun AdvancedConfigPage(
title = "摄像头分辨率",
summary = "--camera-size",
items = cameraSizeDropdownItems,
- selectedIndex = cameraSizeIndex.coerceIn(0, (cameraSizeDropdownItems.size - 1).coerceAtLeast(0)),
+ selectedIndex = cameraSizeIndex.coerceIn(
+ 0,
+ (cameraSizeDropdownItems.size - 1).coerceAtLeast(0)
+ ),
onSelectedIndexChange = { index ->
onCameraSizePresetChange(
when (index) {
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DevicePage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DevicePage.kt
index eefb46d..81ac392 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DevicePage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/DevicePage.kt
@@ -5,56 +5,48 @@ import android.app.Activity
import android.util.Log
import android.view.WindowManager
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.unit.sp
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.DragIndicator
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
import io.github.miuzarte.scrcpyforandroid.ScrcpySessionInfo
import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
import io.github.miuzarte.scrcpyforandroid.constants.UiSpacing
+import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
-import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
-import io.github.miuzarte.scrcpyforandroid.services.fetchConnectedDeviceInfo
+import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
import io.github.miuzarte.scrcpyforandroid.services.DevicePageSettings
-import io.github.miuzarte.scrcpyforandroid.services.loadQuickDevices
+import io.github.miuzarte.scrcpyforandroid.services.fetchConnectedDeviceInfo
import io.github.miuzarte.scrcpyforandroid.services.loadDevicePageSettings
+import io.github.miuzarte.scrcpyforandroid.services.loadQuickDevices
import io.github.miuzarte.scrcpyforandroid.services.parseQuickTarget
-import io.github.miuzarte.scrcpyforandroid.services.saveQuickDevices
import io.github.miuzarte.scrcpyforandroid.services.saveDevicePageSettings
+import io.github.miuzarte.scrcpyforandroid.services.saveQuickDevices
import io.github.miuzarte.scrcpyforandroid.services.updateQuickDeviceNameIfEmpty
import io.github.miuzarte.scrcpyforandroid.services.upsertQuickDevice
-import io.github.miuzarte.scrcpyforandroid.scaffolds.AppPageLazyColumn
import io.github.miuzarte.scrcpyforandroid.widgets.ConfigPanel
-import io.github.miuzarte.scrcpyforandroid.widgets.PairingCard
import io.github.miuzarte.scrcpyforandroid.widgets.DeviceEditorScreen
import io.github.miuzarte.scrcpyforandroid.widgets.DeviceTile
import io.github.miuzarte.scrcpyforandroid.widgets.LogsPanel
+import io.github.miuzarte.scrcpyforandroid.widgets.PairingCard
import io.github.miuzarte.scrcpyforandroid.widgets.PreviewCard
import io.github.miuzarte.scrcpyforandroid.widgets.QuickConnectCard
import io.github.miuzarte.scrcpyforandroid.widgets.SectionSmallTitle
@@ -66,9 +58,6 @@ import io.github.miuzarte.scrcpyforandroid.widgets.StatusCard
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonAction
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonCard
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
@@ -77,11 +66,10 @@ import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import top.yukonga.miuix.kmp.basic.ScrollBehavior
import top.yukonga.miuix.kmp.basic.SnackbarHostState
-import top.yukonga.miuix.kmp.basic.Card
-import top.yukonga.miuix.kmp.basic.Icon
-import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.extra.SuperBottomSheet
-import top.yukonga.miuix.kmp.theme.MiuixTheme
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
import kotlin.math.roundToInt
private const val ADB_CONNECT_TIMEOUT_MS = 3_000L
@@ -90,38 +78,40 @@ private const val ADB_KEEPALIVE_TIMEOUT_MS = 2_000L
private const val DEVICE_SHORTCUT_SEPARATOR = "\u001F"
private const val LOG_TAG = "DevicePage"
-private val DeviceShortcutStateListSaver = listSaver, String>(
- save = { list ->
- list.map { item ->
- listOf(
- item.id,
- item.name,
- item.host,
- item.port.toString(),
- if (item.online) "1" else "0",
- ).joinToString(DEVICE_SHORTCUT_SEPARATOR)
- }
- },
- restore = { saved ->
- saved.mapNotNull { line ->
- val parts = line.split(DEVICE_SHORTCUT_SEPARATOR)
- if (parts.size != 5) return@mapNotNull null
- val port = parts[3].toIntOrNull() ?: return@mapNotNull null
- DeviceShortcut(
- id = parts[0],
- name = parts[1],
- host = parts[2],
- port = port,
- online = parts[4] == "1",
- )
- }.toMutableStateList()
- },
-)
+private val DeviceShortcutStateListSaver =
+ listSaver, String>(
+ save = { list ->
+ list.map { item ->
+ listOf(
+ item.id,
+ item.name,
+ item.host,
+ item.port.toString(),
+ if (item.online) "1" else "0",
+ ).joinToString(DEVICE_SHORTCUT_SEPARATOR)
+ }
+ },
+ restore = { saved ->
+ saved.mapNotNull { line ->
+ val parts = line.split(DEVICE_SHORTCUT_SEPARATOR)
+ if (parts.size != 5) return@mapNotNull null
+ val port = parts[3].toIntOrNull() ?: return@mapNotNull null
+ DeviceShortcut(
+ id = parts[0],
+ name = parts[1],
+ host = parts[2],
+ port = port,
+ online = parts[4] == "1",
+ )
+ }.toMutableStateList()
+ },
+ )
-private val StringStateListSaver = listSaver, String>(
- save = { it.toList() },
- restore = { it.toMutableStateList() },
-)
+private val StringStateListSaver =
+ listSaver, String>(
+ save = { it.toList() },
+ restore = { it.toMutableStateList() },
+ )
@Composable
fun DeviceTabScreen(
@@ -231,7 +221,7 @@ fun DeviceTabScreen(
var statusLine by rememberSaveable { mutableStateOf("未连接") }
var adbConnected by rememberSaveable { mutableStateOf(false) }
var currentTargetHost by rememberSaveable { mutableStateOf("") }
- var currentTargetPort by rememberSaveable { mutableIntStateOf(AppDefaults.DefaultAdbPort) }
+ var currentTargetPort by rememberSaveable { mutableIntStateOf(AppDefaults.ADB_PORT) }
var connectedDeviceLabel by rememberSaveable { mutableStateOf("未连接") }
var sessionInfoWidth by rememberSaveable { mutableIntStateOf(0) }
var sessionInfoHeight by rememberSaveable { mutableIntStateOf(0) }
@@ -241,7 +231,13 @@ fun DeviceTabScreen(
var sessionInfo by remember {
mutableStateOf(null)
}
- LaunchedEffect(sessionInfoWidth, sessionInfoHeight, sessionInfoDeviceName, sessionInfoCodec, sessionInfoControlEnabled) {
+ LaunchedEffect(
+ sessionInfoWidth,
+ sessionInfoHeight,
+ sessionInfoDeviceName,
+ sessionInfoCodec,
+ sessionInfoControlEnabled
+ ) {
sessionInfo = if (sessionInfoDeviceName.isNotBlank()) {
ScrcpySessionInfo(
width = sessionInfoWidth,
@@ -261,18 +257,22 @@ fun DeviceTabScreen(
var adbConnecting by rememberSaveable { mutableStateOf(false) }
var connectHost by rememberSaveable { mutableStateOf("") }
- var connectPort by rememberSaveable { mutableStateOf(AppDefaults.DefaultAdbPort.toString()) }
+ var connectPort by rememberSaveable { mutableStateOf(AppDefaults.ADB_PORT.toString()) }
var quickConnectInput by rememberSaveable { mutableStateOf(initialSettings.quickConnectInput) }
var audioForwardingSupported by rememberSaveable { mutableStateOf(true) }
var cameraMirroringSupported by rememberSaveable { mutableStateOf(true) }
- var bitRateMbps by rememberSaveable { mutableFloatStateOf(initialSettings.bitRateMbps) }
- var bitRateInput by rememberSaveable { mutableStateOf(initialSettings.bitRateInput) }
+ var bitRateMbps by rememberSaveable { mutableFloatStateOf(initialSettings.videoBitRateMbps) }
+ var bitRateInput by rememberSaveable { mutableStateOf(initialSettings.videoBitRateInput) }
var audioBitRateKbps by rememberSaveable { mutableIntStateOf(initialSettings.audioBitRateKbps) }
- val currentTarget = if (currentTargetHost.isNotBlank()) ConnectionTarget(currentTargetHost, currentTargetPort) else null
+ val currentTarget = if (currentTargetHost.isNotBlank()) ConnectionTarget(
+ currentTargetHost,
+ currentTargetPort
+ ) else null
val eventLog = rememberSaveable(saver = StringStateListSaver) { mutableStateListOf() }
- val quickDevices = rememberSaveable(saver = DeviceShortcutStateListSaver) { mutableStateListOf() }
+ val quickDevices =
+ rememberSaveable(saver = DeviceShortcutStateListSaver) { mutableStateListOf() }
LaunchedEffect(eventLog.size) {
onCanClearLogsChange(eventLog.isNotEmpty())
@@ -282,13 +282,25 @@ fun DeviceTabScreen(
val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
val line = "[$timestamp] $message"
eventLog.add(0, line)
- if (eventLog.size > AppDefaults.MaxEventLogLines) {
- eventLog.removeRange(AppDefaults.MaxEventLogLines, eventLog.size)
+ if (eventLog.size > AppDefaults.EVENT_LOG_LINES) {
+ eventLog.removeRange(AppDefaults.EVENT_LOG_LINES, eventLog.size)
}
when (level) {
- Log.ERROR -> if (error != null) Log.e(LOG_TAG, message, error) else Log.e(LOG_TAG, message)
- Log.WARN -> if (error != null) Log.w(LOG_TAG, message, error) else Log.w(LOG_TAG, message)
- Log.DEBUG -> if (error != null) Log.d(LOG_TAG, message, error) else Log.d(LOG_TAG, message)
+ Log.ERROR -> if (error != null) Log.e(LOG_TAG, message, error) else Log.e(
+ LOG_TAG,
+ message
+ )
+
+ Log.WARN -> if (error != null) Log.w(LOG_TAG, message, error) else Log.w(
+ LOG_TAG,
+ message
+ )
+
+ Log.DEBUG -> if (error != null) Log.d(LOG_TAG, message, error) else Log.d(
+ LOG_TAG,
+ message
+ )
+
else -> if (error != null) Log.i(LOG_TAG, message, error) else Log.i(LOG_TAG, message)
}
}
@@ -298,13 +310,19 @@ fun DeviceTabScreen(
audioForwardingSupported = audioSupported
if (!audioSupported && audioEnabled) {
onAudioEnabledChange(false)
- logEvent("设备 Android ${release.ifBlank { "?" }} (SDK $sdkInt) 不支持音频转发,已自动关闭", Log.WARN)
+ logEvent(
+ "设备 Android ${release.ifBlank { "?" }} (SDK $sdkInt) 不支持音频转发,已自动关闭",
+ Log.WARN
+ )
}
val cameraSupported = sdkInt !in 0..<31
cameraMirroringSupported = cameraSupported
if (!cameraSupported && videoSourcePreset == "camera") {
onVideoSourcePresetChange("display")
- logEvent("设备 Android ${release.ifBlank { "?" }} (SDK $sdkInt) 不支持 camera mirroring,已切换为 display", Log.WARN)
+ logEvent(
+ "设备 Android ${release.ifBlank { "?" }} (SDK $sdkInt) 不支持 camera mirroring,已切换为 display",
+ Log.WARN
+ )
}
}
@@ -377,7 +395,7 @@ fun DeviceTabScreen(
fun refreshEncoderLists() {
if (!adbConnected) return
- val remotePath = serverRemotePath.trim().ifBlank { AppDefaults.DefaultServerRemotePath }
+ val remotePath = serverRemotePath.trim().ifBlank { AppDefaults.SERVER_REMOTE_PATH }
runCatching {
nativeCore.scrcpyListEncoders(
customServerUri = customServerUri,
@@ -413,7 +431,7 @@ fun DeviceTabScreen(
fun refreshCameraSizeLists() {
if (!adbConnected) return
- val remotePath = serverRemotePath.trim().ifBlank { AppDefaults.DefaultServerRemotePath }
+ val remotePath = serverRemotePath.trim().ifBlank { AppDefaults.SERVER_REMOTE_PATH }
runCatching {
nativeCore.scrcpyListCameraSizes(
customServerUri = customServerUri,
@@ -470,20 +488,14 @@ fun DeviceTabScreen(
LaunchedEffect(
quickConnectInput,
+ audioBitRateKbps,
bitRateMbps,
bitRateInput,
- audioBitRateKbps,
- maxSizeInput,
- maxFpsInput,
+ turnScreenOff,
noControl,
- videoEncoder,
- videoCodecOptions,
- audioEncoder,
- audioCodecOptions,
- audioDup,
- audioSourcePreset,
- audioSourceCustom,
+ noVideo,
videoSourcePreset,
+ displayIdInput,
cameraIdInput,
cameraFacingPreset,
cameraSizePreset,
@@ -491,14 +503,20 @@ fun DeviceTabScreen(
cameraArInput,
cameraFpsInput,
cameraHighSpeed,
+ audioSourcePreset,
+ audioSourceCustom,
+ audioDup,
noAudioPlayback,
- noVideo,
requireAudio,
- turnScreenOff,
+ maxSizeInput,
+ maxFpsInput,
+ videoEncoder,
+ videoCodecOptions,
+ audioEncoder,
+ audioCodecOptions,
newDisplayWidth,
newDisplayHeight,
newDisplayDpi,
- displayIdInput,
cropWidth,
cropHeight,
cropX,
@@ -508,35 +526,36 @@ fun DeviceTabScreen(
context,
DevicePageSettings(
quickConnectInput = quickConnectInput,
- bitRateMbps = bitRateMbps,
- bitRateInput = bitRateInput,
audioBitRateKbps = audioBitRateKbps,
- maxSizeInput = maxSizeInput,
- maxFpsInput = maxFpsInput,
+ audioBitRateInput = audioBitRateKbps.toString(),
+ videoBitRateMbps = bitRateMbps,
+ videoBitRateInput = bitRateInput,
+ turnScreenOff = turnScreenOff,
noControl = noControl,
- videoEncoder = videoEncoder,
- videoCodecOptions = videoCodecOptions,
- audioEncoder = audioEncoder,
- audioCodecOptions = audioCodecOptions,
- audioDup = audioDup,
- audioSourcePreset = audioSourcePreset,
- audioSourceCustom = audioSourceCustom,
+ noVideo = noVideo,
videoSourcePreset = videoSourcePreset,
+ displayIdInput = displayIdInput,
cameraIdInput = cameraIdInput,
cameraFacingPreset = cameraFacingPreset,
cameraSizePreset = cameraSizePreset,
cameraSizeCustom = cameraSizeCustom,
- cameraArInput = cameraArInput,
- cameraFpsInput = cameraFpsInput,
+ cameraAr = cameraArInput,
+ cameraFps = cameraFpsInput,
cameraHighSpeed = cameraHighSpeed,
+ audioSourcePreset = audioSourcePreset,
+ audioSourceCustom = audioSourceCustom,
+ audioDup = audioDup,
noAudioPlayback = noAudioPlayback,
- noVideo = noVideo,
requireAudio = requireAudio,
- turnScreenOff = turnScreenOff,
+ maxSizeInput = maxSizeInput,
+ maxFpsInput = maxFpsInput,
+ videoEncoder = videoEncoder,
+ videoCodecOptions = videoCodecOptions,
+ audioEncoder = audioEncoder,
+ audioCodecOptions = audioCodecOptions,
newDisplayWidth = newDisplayWidth,
newDisplayHeight = newDisplayHeight,
newDisplayDpi = newDisplayDpi,
- displayIdInput = displayIdInput,
cropWidth = cropWidth,
cropHeight = cropHeight,
cropX = cropX,
@@ -747,7 +766,8 @@ fun DeviceTabScreen(
itemsIndexed(quickDevices, key = { _, device -> device.id }) { _, device ->
val host = device.host
val port = device.port
- val isConnectedTarget = adbConnected && currentTarget?.host == host && currentTarget.port == port
+ val isConnectedTarget =
+ adbConnected && currentTarget?.host == host && currentTarget.port == port
DeviceTile(
device = device,
@@ -768,7 +788,7 @@ fun DeviceTabScreen(
nativeCore.adbDisconnect()
adbConnected = false
currentTargetHost = ""
- currentTargetPort = AppDefaults.DefaultAdbPort
+ currentTargetPort = AppDefaults.ADB_PORT
audioForwardingSupported = true
cameraMirroringSupported = true
sessionInfo = null
@@ -809,7 +829,13 @@ fun DeviceTabScreen(
enabled = !adbConnecting,
onAddDevice = {
val target = parseQuickTarget(quickConnectInput) ?: return@QuickConnectCard
- upsertQuickDevice(context, quickDevices, target.host, target.port, online = false)
+ upsertQuickDevice(
+ context,
+ quickDevices,
+ target.host,
+ target.port,
+ online = false
+ )
scope.launch {
snack.showSnackbar("已添加设备: ${target.host}:${target.port}")
}
@@ -840,10 +866,13 @@ fun DeviceTabScreen(
runBusy("执行配对") {
val ok = nativeCore.adbPair(
host.trim(),
- port.toIntOrNull() ?: AppDefaults.DefaultAdbPort,
+ port.toIntOrNull() ?: AppDefaults.ADB_PORT,
code.trim(),
)
- logEvent(if (ok) "配对成功" else "配对失败", if (ok) Log.INFO else Log.ERROR)
+ logEvent(
+ if (ok) "配对成功" else "配对失败",
+ if (ok) Log.INFO else Log.ERROR
+ )
scope.launch {
snack.showSnackbar(if (ok) "配对成功" else "配对失败")
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/FullscreenControlPage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/FullscreenControlPage.kt
index b96c365..8257e79 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/FullscreenControlPage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/FullscreenControlPage.kt
@@ -2,8 +2,8 @@ package io.github.miuzarte.scrcpyforandroid.pages
import android.app.Activity
import android.content.pm.ActivityInfo
-import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
@@ -21,8 +21,8 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import io.github.miuzarte.scrcpyforandroid.NativeCoreFacade
import io.github.miuzarte.scrcpyforandroid.ScrcpySessionInfo
-import io.github.miuzarte.scrcpyforandroid.widgets.FullscreenControlScreen
import io.github.miuzarte.scrcpyforandroid.haptics.rememberAppHaptics
+import io.github.miuzarte.scrcpyforandroid.widgets.FullscreenControlScreen
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonActions
import io.github.miuzarte.scrcpyforandroid.widgets.VirtualButtonBar
import top.yukonga.miuix.kmp.basic.Scaffold
@@ -56,7 +56,9 @@ fun FullscreenControlPage(
moreActions = virtualButtonLayout.second,
)
}
- val initialOrientation = remember(activity) { activity?.requestedOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED }
+ val initialOrientation = remember(activity) {
+ activity?.requestedOrientation ?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ }
var session by remember(launch) {
mutableStateOf(
ScrcpySessionInfo(
@@ -88,12 +90,15 @@ fun FullscreenControlPage(
WindowCompat.setDecorFitsSystemWindows(window, false)
val controller = WindowInsetsControllerCompat(window, window.decorView)
controller.hide(WindowInsetsCompat.Type.systemBars())
- controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ controller.systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
onDispose {
val restoreWindow = activity?.window
if (restoreWindow != null) {
- WindowInsetsControllerCompat(restoreWindow, restoreWindow.decorView).show(WindowInsetsCompat.Type.systemBars())
+ WindowInsetsControllerCompat(restoreWindow, restoreWindow.decorView).show(
+ WindowInsetsCompat.Type.systemBars()
+ )
WindowCompat.setDecorFitsSystemWindows(restoreWindow, true)
}
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/MainPage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/MainPage.kt
index c1f50ba..3085b42 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/MainPage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/MainPage.kt
@@ -6,8 +6,8 @@ import androidx.activity.compose.PredictiveBackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.spring
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -34,7 +34,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberDecoratedNavEntries
@@ -92,14 +91,18 @@ fun MainPage() {
val initialDeviceSettings = remember(context) { loadDevicePageSettings(context) }
val snackHostState = remember { SnackbarHostState() }
val tabs = remember { MainTabDestination.entries }
- val pagerState = rememberPagerState(initialPage = MainTabDestination.Device.ordinal, pageCount = { tabs.size })
+ val pagerState = rememberPagerState(
+ initialPage = MainTabDestination.Device.ordinal,
+ pageCount = { tabs.size })
val currentTab = tabs[pagerState.currentPage]
val saveableStateHolder = rememberSaveableStateHolder()
val scope = rememberCoroutineScope()
val rootBackStack = remember { mutableStateListOf(RootScreen.Home) }
val currentRootScreen = rootBackStack.lastOrNull() as? RootScreen ?: RootScreen.Home
- val deviceScrollBehavior = MiuixScrollBehavior(canScroll = { currentTab == MainTabDestination.Device })
- val settingsScrollBehavior = MiuixScrollBehavior(canScroll = { currentTab == MainTabDestination.Settings })
+ val deviceScrollBehavior =
+ MiuixScrollBehavior(canScroll = { currentTab == MainTabDestination.Device })
+ val settingsScrollBehavior =
+ MiuixScrollBehavior(canScroll = { currentTab == MainTabDestination.Settings })
val advancedScrollBehavior = MiuixScrollBehavior(
canScroll = {
currentRootScreen is RootScreen.Advanced || currentRootScreen is RootScreen.VirtualButtonOrder
@@ -110,27 +113,23 @@ fun MainPage() {
restore = { restored -> restored.toList() },
)
+ var audioEnabled by rememberSaveable { mutableStateOf(initialSettings.audioEnabled) }
+ var audioCodec by rememberSaveable { mutableStateOf(initialSettings.audioCodec) }
+ var videoCodec by rememberSaveable { mutableStateOf(initialSettings.videoCodec) }
var themeBaseIndex by rememberSaveable { mutableIntStateOf(initialSettings.themeBaseIndex) }
var monetEnabled by rememberSaveable { mutableStateOf(initialSettings.monetEnabled) }
var fullscreenDebugInfoEnabled by rememberSaveable { mutableStateOf(initialSettings.fullscreenDebugInfoEnabled) }
- var showFullscreenVirtualButtons by rememberSaveable {
- mutableStateOf(initialSettings.showFullscreenVirtualButtons)
- }
- var keepScreenOnWhenStreamingEnabled by rememberSaveable {
- mutableStateOf(initialSettings.keepScreenOnWhenStreamingEnabled)
- }
+ var showFullscreenVirtualButtons by rememberSaveable { mutableStateOf(initialSettings.showFullscreenVirtualButtons) }
+ var keepScreenOnWhenStreamingEnabled by rememberSaveable { mutableStateOf(initialSettings.keepScreenOnWhenStreamingEnabled) }
+ var devicePreviewCardHeightDp by rememberSaveable { mutableIntStateOf(initialSettings.devicePreviewCardHeightDp) }
var virtualButtonsOutside by rememberSaveable(stateSaver = stringListSaver) {
mutableStateOf(VirtualButtonActions.parseStoredIds(initialSettings.virtualButtonsOutside))
}
var virtualButtonsInMore by rememberSaveable(stateSaver = stringListSaver) {
mutableStateOf(VirtualButtonActions.parseStoredIds(initialSettings.virtualButtonsInMore))
}
- var devicePreviewCardHeightDp by rememberSaveable { mutableIntStateOf(initialSettings.devicePreviewCardHeightDp) }
var customServerUri by rememberSaveable { mutableStateOf(initialSettings.customServerUri) }
var serverRemotePath by rememberSaveable { mutableStateOf(initialSettings.serverRemotePath) }
- var videoCodec by rememberSaveable { mutableStateOf(initialSettings.videoCodec) }
- var audioEnabled by rememberSaveable { mutableStateOf(initialSettings.audioEnabled) }
- var audioCodec by rememberSaveable { mutableStateOf(initialSettings.audioCodec) }
var adbKeyName by rememberSaveable { mutableStateOf(initialSettings.adbKeyName) }
var noControl by rememberSaveable { mutableStateOf(initialDeviceSettings.noControl) }
var videoEncoder by rememberSaveable { mutableStateOf(initialDeviceSettings.videoEncoder) }
@@ -145,8 +144,8 @@ fun MainPage() {
var cameraFacingPreset by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraFacingPreset) }
var cameraSizePreset by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraSizePreset) }
var cameraSizeCustom by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraSizeCustom) }
- var cameraArInput by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraArInput) }
- var cameraFpsInput by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraFpsInput) }
+ var cameraArInput by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraAr) }
+ var cameraFpsInput by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraFps) }
var cameraHighSpeed by rememberSaveable { mutableStateOf(initialDeviceSettings.cameraHighSpeed) }
var noAudioPlayback by rememberSaveable { mutableStateOf(initialDeviceSettings.noAudioPlayback) }
var noVideo by rememberSaveable { mutableStateOf(initialDeviceSettings.noVideo) }
@@ -178,47 +177,48 @@ fun MainPage() {
val themeController = remember(themeMode) { ThemeController(colorSchemeMode = themeMode) }
LaunchedEffect(
+ audioEnabled,
+ audioCodec,
+ videoCodec,
themeBaseIndex,
monetEnabled,
fullscreenDebugInfoEnabled,
showFullscreenVirtualButtons,
keepScreenOnWhenStreamingEnabled,
+ devicePreviewCardHeightDp,
virtualButtonsOutside,
virtualButtonsInMore,
- devicePreviewCardHeightDp,
customServerUri,
serverRemotePath,
- videoCodec,
- audioEnabled,
- audioCodec,
adbKeyName,
) {
saveMainSettings(
context,
MainSettings(
+ audioEnabled = audioEnabled,
+ audioCodec = audioCodec,
+ videoCodec = videoCodec,
themeBaseIndex = themeBaseIndex,
monetEnabled = monetEnabled,
fullscreenDebugInfoEnabled = fullscreenDebugInfoEnabled,
showFullscreenVirtualButtons = showFullscreenVirtualButtons,
keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
+ devicePreviewCardHeightDp = devicePreviewCardHeightDp,
virtualButtonsOutside = VirtualButtonActions.encodeStoredIds(virtualButtonsOutside),
virtualButtonsInMore = VirtualButtonActions.encodeStoredIds(virtualButtonsInMore),
- devicePreviewCardHeightDp = devicePreviewCardHeightDp,
customServerUri = customServerUri,
serverRemotePath = serverRemotePath,
- videoCodec = videoCodec,
- audioEnabled = audioEnabled,
- audioCodec = audioCodec,
adbKeyName = adbKeyName,
),
)
}
LaunchedEffect(adbKeyName) {
- nativeCore.setAdbKeyName(adbKeyName.ifBlank { AppDefaults.DefaultAdbKeyName })
+ nativeCore.setAdbKeyName(adbKeyName.ifBlank { AppDefaults.ADB_KEY_NAME })
}
- val canNavigateBack = rootBackStack.size > 1 || pagerState.currentPage != MainTabDestination.Device.ordinal
+ val canNavigateBack =
+ rootBackStack.size > 1 || pagerState.currentPage != MainTabDestination.Device.ordinal
fun popRoot() {
if (rootBackStack.size > 1) {
@@ -234,8 +234,8 @@ fun MainPage() {
pagerState.animateScrollToPage(
page = MainTabDestination.Device.ordinal,
animationSpec = spring(
- dampingRatio = UiMotion.PageSwitchDampingRatio,
- stiffness = UiMotion.PageSwitchStiffness,
+ dampingRatio = UiMotion.PAGE_SWITCH_DAMPING_RATIO,
+ stiffness = UiMotion.PAGE_SWITCH_STIFFNESS,
),
)
}
@@ -251,403 +251,424 @@ fun MainPage() {
}
}
- val picker = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
- if (uri == null) return@rememberLauncherForActivityResult
- runCatching {
- context.contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ val picker =
+ rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
+ if (uri == null) return@rememberLauncherForActivityResult
+ runCatching {
+ context.contentResolver.takePersistableUriPermission(
+ uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ )
+ }
+ customServerUri = uri.toString()
}
- customServerUri = uri.toString()
- }
val rootEntryProvider = entryProvider {
- entry(RootScreen.Home) {
- Scaffold(
- bottomBar = {
- NavigationBar {
- tabs.forEach { tab ->
- NavigationBarItem(
- selected = currentTab == tab,
- onClick = {
- scope.launch {
- pagerState.animateScrollToPage(
- page = tab.ordinal,
- animationSpec = spring(
- dampingRatio = UiMotion.PageSwitchDampingRatio,
- stiffness = UiMotion.PageSwitchStiffness,
- ),
+ entry(RootScreen.Home) {
+ Scaffold(
+ bottomBar = {
+ NavigationBar {
+ tabs.forEach { tab ->
+ NavigationBarItem(
+ selected = currentTab == tab,
+ onClick = {
+ scope.launch {
+ pagerState.animateScrollToPage(
+ page = tab.ordinal,
+ animationSpec = spring(
+ dampingRatio = UiMotion.PAGE_SWITCH_DAMPING_RATIO,
+ stiffness = UiMotion.PAGE_SWITCH_STIFFNESS,
+ ),
+ )
+ }
+ },
+ icon = tab.icon,
+ label = tab.label,
+ )
+ }
+ }
+ },
+ snackbarHost = { SnackbarHost(snackHostState) },
+ ) { contentPadding ->
+ HorizontalPager(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = contentPadding.calculateBottomPadding()),
+ state = pagerState,
+ beyondViewportPageCount = 1,
+ ) { page ->
+ val tab = tabs[page]
+ saveableStateHolder.SaveableStateProvider(tab.name) {
+ when (tab) {
+ MainTabDestination.Device -> Scaffold(
+ topBar = {
+ TopAppBar(
+ title = tab.title,
+ actions = {
+ IconButton(
+ onClick = { showDeviceMenu = true },
+ holdDownState = showDeviceMenu,
+ ) {
+ Icon(
+ Icons.Default.MoreVert,
+ contentDescription = "更多"
+ )
+ }
+ DeviceMenuPopup(
+ show = showDeviceMenu,
+ canClearLogs = canClearLogs,
+ onDismissRequest = { showDeviceMenu = false },
+ onReorderDevices = {
+ openReorderDevicesAction?.invoke()
+ showDeviceMenu = false
+ },
+ onOpenVirtualButtonOrder = {
+ rootBackStack.add(RootScreen.VirtualButtonOrder)
+ showDeviceMenu = false
+ },
+ onClearLogs = {
+ clearLogsAction?.invoke()
+ showDeviceMenu = false
+ },
)
+ },
+ scrollBehavior = deviceScrollBehavior,
+ )
+ },
+ ) { pagePadding ->
+ DeviceTabScreen(
+ contentPadding = pagePadding,
+ nativeCore = nativeCore,
+ snack = snackHostState,
+ scrollBehavior = deviceScrollBehavior,
+ keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
+ virtualButtonsOutside = virtualButtonsOutside,
+ virtualButtonsInMore = virtualButtonsInMore,
+ customServerUri = customServerUri,
+ serverRemotePath = serverRemotePath,
+ onServerRemotePathChange = { serverRemotePath = it },
+ videoCodec = videoCodec,
+ onVideoCodecChange = { videoCodec = it },
+ audioEnabled = audioEnabled,
+ onAudioEnabledChange = { audioEnabled = it },
+ audioCodec = audioCodec,
+ onAudioCodecChange = { audioCodec = it },
+ noControl = noControl,
+ onNoControlChange = {
+ noControl = it
+ if (it) {
+ turnScreenOff = false
}
},
- icon = tab.icon,
- label = tab.label,
+ videoEncoder = videoEncoder,
+ onVideoEncoderChange = { videoEncoder = it },
+ videoCodecOptions = videoCodecOptions,
+ onVideoCodecOptionsChange = { videoCodecOptions = it },
+ audioEncoder = audioEncoder,
+ onAudioEncoderChange = { audioEncoder = it },
+ audioCodecOptions = audioCodecOptions,
+ onAudioCodecOptionsChange = { audioCodecOptions = it },
+ audioDup = audioDup,
+ onAudioDupChange = { audioDup = it },
+ audioSourcePreset = audioSourcePreset,
+ onAudioSourcePresetChange = { audioSourcePreset = it },
+ audioSourceCustom = audioSourceCustom,
+ onAudioSourceCustomChange = { audioSourceCustom = it },
+ videoSourcePreset = videoSourcePreset,
+ onVideoSourcePresetChange = { videoSourcePreset = it },
+ cameraIdInput = cameraIdInput,
+ onCameraIdInputChange = { cameraIdInput = it },
+ cameraFacingPreset = cameraFacingPreset,
+ onCameraFacingPresetChange = { cameraFacingPreset = it },
+ cameraSizePreset = cameraSizePreset,
+ onCameraSizePresetChange = { cameraSizePreset = it },
+ cameraSizeCustom = cameraSizeCustom,
+ onCameraSizeCustomChange = { cameraSizeCustom = it },
+ cameraArInput = cameraArInput,
+ onCameraArInputChange = { cameraArInput = it },
+ cameraFpsInput = cameraFpsInput,
+ onCameraFpsInputChange = { cameraFpsInput = it },
+ cameraHighSpeed = cameraHighSpeed,
+ onCameraHighSpeedChange = { cameraHighSpeed = it },
+ noAudioPlayback = noAudioPlayback,
+ onNoAudioPlaybackChange = { noAudioPlayback = it },
+ noVideo = noVideo,
+ requireAudio = requireAudio,
+ onRequireAudioChange = { requireAudio = it },
+ turnScreenOff = turnScreenOff,
+ onTurnScreenOffChange = { turnScreenOff = it },
+ maxSizeInput = maxSizeInput,
+ onMaxSizeInputChange = { maxSizeInput = it },
+ maxFpsInput = maxFpsInput,
+ onMaxFpsInputChange = { maxFpsInput = it },
+ newDisplayWidth = newDisplayWidth,
+ onNewDisplayWidthChange = { newDisplayWidth = it },
+ newDisplayHeight = newDisplayHeight,
+ onNewDisplayHeightChange = { newDisplayHeight = it },
+ newDisplayDpi = newDisplayDpi,
+ onNewDisplayDpiChange = { newDisplayDpi = it },
+ displayIdInput = displayIdInput,
+ onDisplayIdInputChange = { displayIdInput = it },
+ cropWidth = cropWidth,
+ onCropWidthChange = { cropWidth = it },
+ cropHeight = cropHeight,
+ onCropHeightChange = { cropHeight = it },
+ cropX = cropX,
+ onCropXChange = { cropX = it },
+ cropY = cropY,
+ onCropYChange = { cropY = it },
+ videoEncoderOptions = videoEncoderOptions,
+ onVideoEncoderOptionsChange = {
+ videoEncoderOptions.clear()
+ videoEncoderOptions.addAll(it)
+ },
+ onVideoEncoderTypeMapChange = {
+ videoEncoderTypeMap.clear()
+ videoEncoderTypeMap.putAll(it)
+ },
+ audioEncoderOptions = audioEncoderOptions,
+ onAudioEncoderOptionsChange = {
+ audioEncoderOptions.clear()
+ audioEncoderOptions.addAll(it)
+ },
+ onAudioEncoderTypeMapChange = {
+ audioEncoderTypeMap.clear()
+ audioEncoderTypeMap.putAll(it)
+ },
+ cameraSizeOptions = cameraSizeOptions,
+ onCameraSizeOptionsChange = {
+ cameraSizeOptions.clear()
+ cameraSizeOptions.addAll(it)
+ },
+ onSessionStartedChange = { sessionStarted = it },
+ onRefreshEncodersActionChange = { refreshEncodersAction = it },
+ onRefreshCameraSizesActionChange = {
+ refreshCameraSizesAction = it
+ },
+ onClearLogsActionChange = { clearLogsAction = it },
+ onCanClearLogsChange = { canClearLogs = it },
+ onOpenReorderDevicesActionChange = {
+ openReorderDevicesAction = it
+ },
+ onOpenAdvancedPage = { rootBackStack.add(RootScreen.Advanced) },
+ onOpenFullscreenPage = { session ->
+ rootBackStack.add(
+ RootScreen.Fullscreen(
+ launch = FullscreenControlLaunch(
+ deviceName = session.deviceName,
+ width = session.width,
+ height = session.height,
+ codec = session.codec,
+ ),
+ ),
+ )
+ },
+ previewCardHeightDp = devicePreviewCardHeightDp,
)
}
- }
- },
- snackbarHost = { SnackbarHost(snackHostState) },
- ) { contentPadding ->
- HorizontalPager(
- modifier = Modifier
- .fillMaxSize()
- .padding(bottom = contentPadding.calculateBottomPadding()),
- state = pagerState,
- beyondViewportPageCount = 1,
- ) { page ->
- val tab = tabs[page]
- saveableStateHolder.SaveableStateProvider(tab.name) {
- when (tab) {
- MainTabDestination.Device -> Scaffold(
- topBar = {
- TopAppBar(
- title = tab.title,
- actions = {
- IconButton(
- onClick = { showDeviceMenu = true },
- holdDownState = showDeviceMenu,
- ) {
- Icon(Icons.Default.MoreVert, contentDescription = "更多")
- }
- DeviceMenuPopup(
- show = showDeviceMenu,
- canClearLogs = canClearLogs,
- onDismissRequest = { showDeviceMenu = false },
- onReorderDevices = {
- openReorderDevicesAction?.invoke()
- showDeviceMenu = false
- },
- onOpenVirtualButtonOrder = {
- rootBackStack.add(RootScreen.VirtualButtonOrder)
- showDeviceMenu = false
- },
- onClearLogs = {
- clearLogsAction?.invoke()
- showDeviceMenu = false
- },
- )
- },
- scrollBehavior = deviceScrollBehavior,
- )
- },
- ) { pagePadding ->
- DeviceTabScreen(
- contentPadding = pagePadding,
- nativeCore = nativeCore,
- snack = snackHostState,
- scrollBehavior = deviceScrollBehavior,
- keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
- virtualButtonsOutside = virtualButtonsOutside,
- virtualButtonsInMore = virtualButtonsInMore,
- customServerUri = customServerUri,
- serverRemotePath = serverRemotePath,
- onServerRemotePathChange = { serverRemotePath = it },
- videoCodec = videoCodec,
- onVideoCodecChange = { videoCodec = it },
- audioEnabled = audioEnabled,
- onAudioEnabledChange = { audioEnabled = it },
- audioCodec = audioCodec,
- onAudioCodecChange = { audioCodec = it },
- noControl = noControl,
- onNoControlChange = {
- noControl = it
- if (it) {
- turnScreenOff = false
- }
- },
- videoEncoder = videoEncoder,
- onVideoEncoderChange = { videoEncoder = it },
- videoCodecOptions = videoCodecOptions,
- onVideoCodecOptionsChange = { videoCodecOptions = it },
- audioEncoder = audioEncoder,
- onAudioEncoderChange = { audioEncoder = it },
- audioCodecOptions = audioCodecOptions,
- onAudioCodecOptionsChange = { audioCodecOptions = it },
- audioDup = audioDup,
- onAudioDupChange = { audioDup = it },
- audioSourcePreset = audioSourcePreset,
- onAudioSourcePresetChange = { audioSourcePreset = it },
- audioSourceCustom = audioSourceCustom,
- onAudioSourceCustomChange = { audioSourceCustom = it },
- videoSourcePreset = videoSourcePreset,
- onVideoSourcePresetChange = { videoSourcePreset = it },
- cameraIdInput = cameraIdInput,
- onCameraIdInputChange = { cameraIdInput = it },
- cameraFacingPreset = cameraFacingPreset,
- onCameraFacingPresetChange = { cameraFacingPreset = it },
- cameraSizePreset = cameraSizePreset,
- onCameraSizePresetChange = { cameraSizePreset = it },
- cameraSizeCustom = cameraSizeCustom,
- onCameraSizeCustomChange = { cameraSizeCustom = it },
- cameraArInput = cameraArInput,
- onCameraArInputChange = { cameraArInput = it },
- cameraFpsInput = cameraFpsInput,
- onCameraFpsInputChange = { cameraFpsInput = it },
- cameraHighSpeed = cameraHighSpeed,
- onCameraHighSpeedChange = { cameraHighSpeed = it },
- noAudioPlayback = noAudioPlayback,
- onNoAudioPlaybackChange = { noAudioPlayback = it },
- noVideo = noVideo,
- requireAudio = requireAudio,
- onRequireAudioChange = { requireAudio = it },
- turnScreenOff = turnScreenOff,
- onTurnScreenOffChange = { turnScreenOff = it },
- maxSizeInput = maxSizeInput,
- onMaxSizeInputChange = { maxSizeInput = it },
- maxFpsInput = maxFpsInput,
- onMaxFpsInputChange = { maxFpsInput = it },
- newDisplayWidth = newDisplayWidth,
- onNewDisplayWidthChange = { newDisplayWidth = it },
- newDisplayHeight = newDisplayHeight,
- onNewDisplayHeightChange = { newDisplayHeight = it },
- newDisplayDpi = newDisplayDpi,
- onNewDisplayDpiChange = { newDisplayDpi = it },
- displayIdInput = displayIdInput,
- onDisplayIdInputChange = { displayIdInput = it },
- cropWidth = cropWidth,
- onCropWidthChange = { cropWidth = it },
- cropHeight = cropHeight,
- onCropHeightChange = { cropHeight = it },
- cropX = cropX,
- onCropXChange = { cropX = it },
- cropY = cropY,
- onCropYChange = { cropY = it },
- videoEncoderOptions = videoEncoderOptions,
- onVideoEncoderOptionsChange = {
- videoEncoderOptions.clear()
- videoEncoderOptions.addAll(it)
- },
- onVideoEncoderTypeMapChange = {
- videoEncoderTypeMap.clear()
- videoEncoderTypeMap.putAll(it)
- },
- audioEncoderOptions = audioEncoderOptions,
- onAudioEncoderOptionsChange = {
- audioEncoderOptions.clear()
- audioEncoderOptions.addAll(it)
- },
- onAudioEncoderTypeMapChange = {
- audioEncoderTypeMap.clear()
- audioEncoderTypeMap.putAll(it)
- },
- cameraSizeOptions = cameraSizeOptions,
- onCameraSizeOptionsChange = {
- cameraSizeOptions.clear()
- cameraSizeOptions.addAll(it)
- },
- onSessionStartedChange = { sessionStarted = it },
- onRefreshEncodersActionChange = { refreshEncodersAction = it },
- onRefreshCameraSizesActionChange = { refreshCameraSizesAction = it },
- onClearLogsActionChange = { clearLogsAction = it },
- onCanClearLogsChange = { canClearLogs = it },
- onOpenReorderDevicesActionChange = { openReorderDevicesAction = it },
- onOpenAdvancedPage = { rootBackStack.add(RootScreen.Advanced) },
- onOpenFullscreenPage = { session ->
- rootBackStack.add(
- RootScreen.Fullscreen(
- launch = FullscreenControlLaunch(
- deviceName = session.deviceName,
- width = session.width,
- height = session.height,
- codec = session.codec,
- ),
- ),
- )
- },
- previewCardHeightDp = devicePreviewCardHeightDp,
- )
- }
- MainTabDestination.Settings -> Scaffold(
- topBar = {
- TopAppBar(
- title = tab.title,
- scrollBehavior = settingsScrollBehavior,
- )
- },
- ) { pagePadding ->
- SettingsScreen(
- contentPadding = pagePadding,
- themeBaseIndex = themeBaseIndex,
- onThemeBaseIndexChange = { themeBaseIndex = it },
- monetEnabled = monetEnabled,
- onMonetEnabledChange = { monetEnabled = it },
- fullscreenDebugInfoEnabled = fullscreenDebugInfoEnabled,
- onFullscreenDebugInfoEnabledChange = { fullscreenDebugInfoEnabled = it },
- showFullscreenVirtualButtons = showFullscreenVirtualButtons,
- onShowFullscreenVirtualButtonsChange = { showFullscreenVirtualButtons = it },
- keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
- onKeepScreenOnWhenStreamingEnabledChange = {
- keepScreenOnWhenStreamingEnabled = it
- },
- onOpenReorderDevices = { openReorderDevicesAction?.invoke() },
- onOpenVirtualButtonOrder = { rootBackStack.add(RootScreen.VirtualButtonOrder) },
- devicePreviewCardHeightDp = devicePreviewCardHeightDp,
- onDevicePreviewCardHeightDpChange = {
- devicePreviewCardHeightDp = it.coerceAtLeast(120)
- },
- customServerUri = customServerUri,
- onPickServer = {
- picker.launch(arrayOf("application/java-archive", "application/octet-stream", "*/*"))
- },
- onClearServer = { customServerUri = null },
- serverRemotePath = serverRemotePath,
- onServerRemotePathChange = { serverRemotePath = it },
- adbKeyName = adbKeyName,
- onAdbKeyNameChange = { adbKeyName = it },
+ MainTabDestination.Settings -> Scaffold(
+ topBar = {
+ TopAppBar(
+ title = tab.title,
scrollBehavior = settingsScrollBehavior,
)
- }
+ },
+ ) { pagePadding ->
+ SettingsScreen(
+ contentPadding = pagePadding,
+ themeBaseIndex = themeBaseIndex,
+ onThemeBaseIndexChange = { themeBaseIndex = it },
+ monetEnabled = monetEnabled,
+ onMonetEnabledChange = { monetEnabled = it },
+ fullscreenDebugInfoEnabled = fullscreenDebugInfoEnabled,
+ onFullscreenDebugInfoEnabledChange = {
+ fullscreenDebugInfoEnabled = it
+ },
+ keepScreenOnWhenStreamingEnabled = keepScreenOnWhenStreamingEnabled,
+ onKeepScreenOnWhenStreamingEnabledChange = {
+ keepScreenOnWhenStreamingEnabled = it
+ },
+ devicePreviewCardHeightDp = devicePreviewCardHeightDp,
+ onDevicePreviewCardHeightDpChange = {
+ devicePreviewCardHeightDp = it.coerceAtLeast(120)
+ },
+ customServerUri = customServerUri,
+ onPickServer = {
+ picker.launch(
+ arrayOf(
+ "application/java-archive",
+ "application/octet-stream",
+ "*/*"
+ )
+ )
+ },
+ onClearServer = { customServerUri = null },
+ serverRemotePath = serverRemotePath,
+ onServerRemotePathChange = { serverRemotePath = it },
+ adbKeyName = adbKeyName,
+ onAdbKeyNameChange = { adbKeyName = it },
+ scrollBehavior = settingsScrollBehavior,
+ )
}
}
}
}
}
+ }
- entry(RootScreen.Advanced) {
- val videoEncoderDropdownItems = listOf("默认") + videoEncoderOptions
- val audioEncoderDropdownItems = listOf("默认") + audioEncoderOptions
- val videoEncoderIndex = (videoEncoderOptions.indexOf(videoEncoder) + 1).coerceAtLeast(0)
- val audioEncoderIndex = (audioEncoderOptions.indexOf(audioEncoder) + 1).coerceAtLeast(0)
+ entry(RootScreen.Advanced) {
+ val videoEncoderDropdownItems = listOf("默认") + videoEncoderOptions
+ val audioEncoderDropdownItems = listOf("默认") + audioEncoderOptions
+ val videoEncoderIndex = (videoEncoderOptions.indexOf(videoEncoder) + 1).coerceAtLeast(0)
+ val audioEncoderIndex = (audioEncoderOptions.indexOf(audioEncoder) + 1).coerceAtLeast(0)
- Scaffold(
- topBar = {
- TopAppBar(
- title = "高级参数",
- navigationIcon = {
- IconButton(onClick = { popRoot() }) {
- Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回")
- }
- },
- scrollBehavior = advancedScrollBehavior,
- )
- },
- snackbarHost = { SnackbarHost(snackHostState) },
- ) { pagePadding ->
- AdvancedConfigPage(
- contentPadding = pagePadding,
- scrollBehavior = advancedScrollBehavior,
- sessionStarted = sessionStarted,
- snackbarHostState = snackHostState,
- audioEnabled = audioEnabled,
- noControl = noControl,
- onNoControlChange = {
- noControl = it
- if (it) {
- turnScreenOff = false
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = "高级参数",
+ navigationIcon = {
+ IconButton(onClick = { popRoot() }) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "返回"
+ )
}
},
- audioDup = audioDup,
- onAudioDupChange = { audioDup = it },
- audioSourcePreset = audioSourcePreset,
- onAudioSourcePresetChange = { audioSourcePreset = it },
- audioSourceCustom = audioSourceCustom,
- onAudioSourceCustomChange = { audioSourceCustom = it },
- videoSourcePreset = videoSourcePreset,
- onVideoSourcePresetChange = { videoSourcePreset = it },
- cameraIdInput = cameraIdInput,
- onCameraIdInputChange = { cameraIdInput = it },
- cameraFacingPreset = cameraFacingPreset,
- onCameraFacingPresetChange = { cameraFacingPreset = it },
- cameraSizePreset = cameraSizePreset,
- onCameraSizePresetChange = { cameraSizePreset = it },
- cameraSizeCustom = cameraSizeCustom,
- onCameraSizeCustomChange = { cameraSizeCustom = it },
- cameraSizeDropdownItems = listOf("默认") + cameraSizeOptions + listOf("自定义"),
- cameraSizeIndex = when (cameraSizePreset) {
- "custom" -> cameraSizeOptions.size + 1
- in cameraSizeOptions -> cameraSizeOptions.indexOf(cameraSizePreset) + 1
- else -> 0
- },
- cameraArInput = cameraArInput,
- onCameraArInputChange = { cameraArInput = it },
- cameraFpsInput = cameraFpsInput,
- onCameraFpsInputChange = { cameraFpsInput = it },
- cameraHighSpeed = cameraHighSpeed,
- onCameraHighSpeedChange = { cameraHighSpeed = it },
- noAudioPlayback = noAudioPlayback,
- onNoAudioPlaybackChange = { noAudioPlayback = it },
- noVideo = noVideo,
- onNoVideoChange = { noVideo = it },
- requireAudio = requireAudio,
- onRequireAudioChange = { requireAudio = it },
- turnScreenOff = turnScreenOff,
- onTurnScreenOffChange = { turnScreenOff = it },
- maxSizeInput = maxSizeInput,
- onMaxSizeInputChange = { maxSizeInput = it },
- maxFpsInput = maxFpsInput,
- onMaxFpsInputChange = { maxFpsInput = it },
- videoEncoderDropdownItems = videoEncoderDropdownItems,
- videoEncoderTypeMap = videoEncoderTypeMap,
- videoEncoderIndex = videoEncoderIndex,
- onVideoEncoderChange = { videoEncoder = it },
- videoCodecOptions = videoCodecOptions,
- onVideoCodecOptionsChange = { videoCodecOptions = it },
- audioEncoderDropdownItems = audioEncoderDropdownItems,
- audioEncoderTypeMap = audioEncoderTypeMap,
- audioEncoderIndex = audioEncoderIndex,
- onAudioEncoderChange = { audioEncoder = it },
- audioCodecOptions = audioCodecOptions,
- onAudioCodecOptionsChange = { audioCodecOptions = it },
- onRefreshEncoders = { refreshEncodersAction?.invoke() },
- onRefreshCameraSizes = { refreshCameraSizesAction?.invoke() },
- newDisplayWidth = newDisplayWidth,
- onNewDisplayWidthChange = { newDisplayWidth = it },
- newDisplayHeight = newDisplayHeight,
- onNewDisplayHeightChange = { newDisplayHeight = it },
- newDisplayDpi = newDisplayDpi,
- onNewDisplayDpiChange = { newDisplayDpi = it },
- displayIdInput = displayIdInput,
- onDisplayIdInputChange = { displayIdInput = it },
- cropWidth = cropWidth,
- onCropWidthChange = { cropWidth = it },
- cropHeight = cropHeight,
- onCropHeightChange = { cropHeight = it },
- cropX = cropX,
- onCropXChange = { cropX = it },
- cropY = cropY,
- onCropYChange = { cropY = it },
- )
- }
- }
-
- entry(RootScreen.VirtualButtonOrder) {
- Scaffold(
- modifier = Modifier.nestedScroll(advancedScrollBehavior.nestedScrollConnection),
- topBar = {
- TopAppBar(
- title = "虚拟按钮排序",
- navigationIcon = {
- IconButton(onClick = { popRoot() }) {
- Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "返回")
- }
- },
- scrollBehavior = advancedScrollBehavior,
- )
- },
- ) { pagePadding ->
- VirtualButtonOrderPage(
- contentPadding = pagePadding,
scrollBehavior = advancedScrollBehavior,
- outsideIds = virtualButtonsOutside,
- moreIds = virtualButtonsInMore,
- onLayoutChange = { outside, more ->
- virtualButtonsOutside = outside
- virtualButtonsInMore = more
- },
)
- }
- }
-
- entry { screen ->
- FullscreenControlPage(
- launch = screen.launch,
- nativeCore = nativeCore,
- virtualButtonsOutside = virtualButtonsOutside,
- virtualButtonsInMore = virtualButtonsInMore,
- showDebugInfo = fullscreenDebugInfoEnabled,
- showVirtualButtons = showFullscreenVirtualButtons,
- onDismiss = { popRoot() },
+ },
+ snackbarHost = { SnackbarHost(snackHostState) },
+ ) { pagePadding ->
+ AdvancedConfigPage(
+ contentPadding = pagePadding,
+ scrollBehavior = advancedScrollBehavior,
+ sessionStarted = sessionStarted,
+ snackbarHostState = snackHostState,
+ audioEnabled = audioEnabled,
+ noControl = noControl,
+ onNoControlChange = {
+ noControl = it
+ if (it) {
+ turnScreenOff = false
+ }
+ },
+ audioDup = audioDup,
+ onAudioDupChange = { audioDup = it },
+ audioSourcePreset = audioSourcePreset,
+ onAudioSourcePresetChange = { audioSourcePreset = it },
+ audioSourceCustom = audioSourceCustom,
+ onAudioSourceCustomChange = { audioSourceCustom = it },
+ videoSourcePreset = videoSourcePreset,
+ onVideoSourcePresetChange = { videoSourcePreset = it },
+ cameraIdInput = cameraIdInput,
+ onCameraIdInputChange = { cameraIdInput = it },
+ cameraFacingPreset = cameraFacingPreset,
+ onCameraFacingPresetChange = { cameraFacingPreset = it },
+ cameraSizePreset = cameraSizePreset,
+ onCameraSizePresetChange = { cameraSizePreset = it },
+ cameraSizeCustom = cameraSizeCustom,
+ onCameraSizeCustomChange = { cameraSizeCustom = it },
+ cameraSizeDropdownItems = listOf("默认") + cameraSizeOptions + listOf("自定义"),
+ cameraSizeIndex = when (cameraSizePreset) {
+ "custom" -> cameraSizeOptions.size + 1
+ in cameraSizeOptions -> cameraSizeOptions.indexOf(cameraSizePreset) + 1
+ else -> 0
+ },
+ cameraArInput = cameraArInput,
+ onCameraArInputChange = { cameraArInput = it },
+ cameraFpsInput = cameraFpsInput,
+ onCameraFpsInputChange = { cameraFpsInput = it },
+ cameraHighSpeed = cameraHighSpeed,
+ onCameraHighSpeedChange = { cameraHighSpeed = it },
+ noAudioPlayback = noAudioPlayback,
+ onNoAudioPlaybackChange = { noAudioPlayback = it },
+ noVideo = noVideo,
+ onNoVideoChange = { noVideo = it },
+ requireAudio = requireAudio,
+ onRequireAudioChange = { requireAudio = it },
+ turnScreenOff = turnScreenOff,
+ onTurnScreenOffChange = { turnScreenOff = it },
+ maxSizeInput = maxSizeInput,
+ onMaxSizeInputChange = { maxSizeInput = it },
+ maxFpsInput = maxFpsInput,
+ onMaxFpsInputChange = { maxFpsInput = it },
+ videoEncoderDropdownItems = videoEncoderDropdownItems,
+ videoEncoderTypeMap = videoEncoderTypeMap,
+ videoEncoderIndex = videoEncoderIndex,
+ onVideoEncoderChange = { videoEncoder = it },
+ videoCodecOptions = videoCodecOptions,
+ onVideoCodecOptionsChange = { videoCodecOptions = it },
+ audioEncoderDropdownItems = audioEncoderDropdownItems,
+ audioEncoderTypeMap = audioEncoderTypeMap,
+ audioEncoderIndex = audioEncoderIndex,
+ onAudioEncoderChange = { audioEncoder = it },
+ audioCodecOptions = audioCodecOptions,
+ onAudioCodecOptionsChange = { audioCodecOptions = it },
+ onRefreshEncoders = { refreshEncodersAction?.invoke() },
+ onRefreshCameraSizes = { refreshCameraSizesAction?.invoke() },
+ newDisplayWidth = newDisplayWidth,
+ onNewDisplayWidthChange = { newDisplayWidth = it },
+ newDisplayHeight = newDisplayHeight,
+ onNewDisplayHeightChange = { newDisplayHeight = it },
+ newDisplayDpi = newDisplayDpi,
+ onNewDisplayDpiChange = { newDisplayDpi = it },
+ displayIdInput = displayIdInput,
+ onDisplayIdInputChange = { displayIdInput = it },
+ cropWidth = cropWidth,
+ onCropWidthChange = { cropWidth = it },
+ cropHeight = cropHeight,
+ onCropHeightChange = { cropHeight = it },
+ cropX = cropX,
+ onCropXChange = { cropX = it },
+ cropY = cropY,
+ onCropYChange = { cropY = it },
)
}
+ }
+
+ entry(RootScreen.VirtualButtonOrder) {
+ Scaffold(
+ modifier = Modifier.nestedScroll(advancedScrollBehavior.nestedScrollConnection),
+ topBar = {
+ TopAppBar(
+ title = "虚拟按钮排序",
+ navigationIcon = {
+ IconButton(onClick = { popRoot() }) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "返回"
+ )
+ }
+ },
+ scrollBehavior = advancedScrollBehavior,
+ )
+ },
+ ) { pagePadding ->
+ VirtualButtonOrderPage(
+ contentPadding = pagePadding,
+ scrollBehavior = advancedScrollBehavior,
+ outsideIds = virtualButtonsOutside,
+ moreIds = virtualButtonsInMore,
+ onLayoutChange = { outside, more ->
+ virtualButtonsOutside = outside
+ virtualButtonsInMore = more
+ },
+ )
+ }
+ }
+
+ entry { screen ->
+ FullscreenControlPage(
+ launch = screen.launch,
+ nativeCore = nativeCore,
+ virtualButtonsOutside = virtualButtonsOutside,
+ virtualButtonsInMore = virtualButtonsInMore,
+ showDebugInfo = fullscreenDebugInfoEnabled,
+ showVirtualButtons = showFullscreenVirtualButtons,
+ onDismiss = { popRoot() },
+ )
+ }
}
val rootEntries = rememberDecoratedNavEntries(
@@ -724,7 +745,8 @@ private fun DeviceMenuPopupItem(
}
val additionalTopPadding = if (index == 0) UiSpacing.PopupHorizontal else UiSpacing.PageItem
- val additionalBottomPadding = if (index == optionSize - 1) UiSpacing.PopupHorizontal else UiSpacing.PageItem
+ val additionalBottomPadding =
+ if (index == optionSize - 1) UiSpacing.PopupHorizontal else UiSpacing.PageItem
Text(
text = text,
fontSize = MiuixTheme.textStyles.body1.fontSize,
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/SettingsPage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/SettingsPage.kt
index 9a3a181..95f876a 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/SettingsPage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/SettingsPage.kt
@@ -22,7 +22,6 @@ import top.yukonga.miuix.kmp.basic.IconButton
import top.yukonga.miuix.kmp.basic.ScrollBehavior
import top.yukonga.miuix.kmp.basic.Text
import top.yukonga.miuix.kmp.basic.TextField
-import top.yukonga.miuix.kmp.extra.SuperArrow
import top.yukonga.miuix.kmp.extra.SuperDropdown
import top.yukonga.miuix.kmp.extra.SuperSwitch
import top.yukonga.miuix.kmp.theme.ColorSchemeMode
@@ -61,12 +60,8 @@ fun SettingsScreen(
onMonetEnabledChange: (Boolean) -> Unit,
fullscreenDebugInfoEnabled: Boolean,
onFullscreenDebugInfoEnabledChange: (Boolean) -> Unit,
- showFullscreenVirtualButtons: Boolean,
- onShowFullscreenVirtualButtonsChange: (Boolean) -> Unit,
keepScreenOnWhenStreamingEnabled: Boolean,
onKeepScreenOnWhenStreamingEnabledChange: (Boolean) -> Unit,
- onOpenReorderDevices: () -> Unit,
- onOpenVirtualButtonOrder: () -> Unit,
devicePreviewCardHeightDp: Int,
onDevicePreviewCardHeightDpChange: (Int) -> Unit,
customServerUri: String?,
@@ -111,12 +106,6 @@ fun SettingsScreen(
checked = fullscreenDebugInfoEnabled,
onCheckedChange = onFullscreenDebugInfoEnabledChange,
)
- SuperSwitch(
- title = "全屏显示虚拟按钮",
- summary = "在全屏模式底部显示返回/主页等虚拟按钮",
- checked = showFullscreenVirtualButtons,
- onCheckedChange = onShowFullscreenVirtualButtonsChange,
- )
SuperSwitch(
title = "投屏时不允许息屏",
summary = "Scrcpy 启动后保持本机常亮,避免锁屏导致 ADB 断开",
@@ -127,7 +116,11 @@ fun SettingsScreen(
title = "预览卡高度",
summary = "设备页预览卡高度",
value = devicePreviewCardHeightDp.toFloat(),
- onValueChange = { onDevicePreviewCardHeightDpChange(it.roundToInt().coerceAtLeast(120)) },
+ onValueChange = {
+ onDevicePreviewCardHeightDpChange(
+ it.roundToInt().coerceAtLeast(120)
+ )
+ },
valueRange = 160f..600f,
steps = 439,
unit = "dp",
@@ -136,19 +129,10 @@ fun SettingsScreen(
inputFilter = { it.filter(Char::isDigit) },
inputValueRange = 120f..Float.MAX_VALUE,
onInputConfirm = { raw ->
- raw.toIntOrNull()?.let { onDevicePreviewCardHeightDpChange(it.coerceAtLeast(120)) }
+ raw.toIntOrNull()
+ ?.let { onDevicePreviewCardHeightDpChange(it.coerceAtLeast(120)) }
},
)
- SuperArrow(
- title = "调整设备排序",
- summary = "调整设备页快捷设备列表顺序",
- onClick = onOpenReorderDevices,
- )
- SuperArrow(
- title = "虚拟按钮排序",
- summary = "调整底部虚拟按钮与更多菜单顺序",
- onClick = onOpenVirtualButtonOrder,
- )
}
SectionSmallTitle(text = "scrcpy-server")
@@ -192,7 +176,7 @@ fun SettingsScreen(
TextField(
value = serverRemotePath,
onValueChange = onServerRemotePathChange,
- label = AppDefaults.DefaultServerRemotePath,
+ label = AppDefaults.SERVER_REMOTE_PATH,
useLabelAsPlaceholder = true,
singleLine = true,
modifier = Modifier
@@ -214,7 +198,7 @@ fun SettingsScreen(
TextField(
value = adbKeyName,
onValueChange = onAdbKeyNameChange,
- label = AppDefaults.DefaultAdbKeyName,
+ label = AppDefaults.ADB_KEY_NAME,
useLabelAsPlaceholder = true,
singleLine = true,
modifier = Modifier
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/VirtualButtonOrderPage.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/VirtualButtonOrderPage.kt
index 39da73e..b0bf6c7 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/VirtualButtonOrderPage.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/VirtualButtonOrderPage.kt
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
@@ -54,7 +53,11 @@ internal fun VirtualButtonOrderPage(
onLayoutChange(outsideState.toList(), moreState.toList())
}
- fun reorderInside(list: androidx.compose.runtime.snapshots.SnapshotStateList, itemId: String, deltaY: Float) {
+ fun reorderInside(
+ list: androidx.compose.runtime.snapshots.SnapshotStateList,
+ itemId: String,
+ deltaY: Float
+ ) {
val fromIndex = list.indexOf(itemId)
if (fromIndex < 0) return
@@ -88,7 +91,8 @@ internal fun VirtualButtonOrderPage(
}
fun handleOutsideDrop(payload: SortDropPayload) {
- val transfer = payload.transferDirection == SortTransferDirection.TO_RIGHT && payload.deltaX >= LIST_TRANSFER_STEP_PX
+ val transfer =
+ payload.transferDirection == SortTransferDirection.TO_RIGHT && payload.deltaX >= LIST_TRANSFER_STEP_PX
if (transfer) {
if (payload.itemId == VirtualButtonAction.MORE.id) return
transferToOther(outsideState, moreState, payload.itemId, payload.deltaY)
@@ -98,7 +102,8 @@ internal fun VirtualButtonOrderPage(
}
fun handleMoreDrop(payload: SortDropPayload) {
- val transfer = payload.transferDirection == SortTransferDirection.TO_LEFT && payload.deltaX <= -LIST_TRANSFER_STEP_PX
+ val transfer =
+ payload.transferDirection == SortTransferDirection.TO_LEFT && payload.deltaX <= -LIST_TRANSFER_STEP_PX
if (transfer) {
transferToOther(moreState, outsideState, payload.itemId, payload.deltaY)
} else {
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/scaffolds/SuperSlide.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/scaffolds/SuperSlide.kt
index e0e6c80..13240dd 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/scaffolds/SuperSlide.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/scaffolds/SuperSlide.kt
@@ -58,7 +58,8 @@ fun SuperSlide(
holdDownState = holdArrow,
endActions = {
val isZeroState = value == 0f && zeroStateText != null
- val valueText = if (isZeroState) zeroStateText else (displayText ?: displayFormatter(value))
+ val valueText =
+ if (isZeroState) zeroStateText else (displayText ?: displayFormatter(value))
val shouldShowUnit = unit.isNotBlank() && (!isZeroState || showUnitWhenZeroState)
val text = if (shouldShowUnit) "$valueText $unit" else valueText
Text(
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppSettingsStore.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppSettingsStore.kt
index ff478cd..2b382d1 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppSettingsStore.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/AppSettingsStore.kt
@@ -6,215 +6,495 @@ import io.github.miuzarte.scrcpyforandroid.constants.AppDefaults
import io.github.miuzarte.scrcpyforandroid.constants.AppPreferenceKeys
internal data class MainSettings(
- val themeBaseIndex: Int = AppDefaults.DefaultThemeBaseIndex,
- val monetEnabled: Boolean = AppDefaults.DefaultMonetEnabled,
- val fullscreenDebugInfoEnabled: Boolean = AppDefaults.DefaultFullscreenDebugInfoEnabled,
- val showFullscreenVirtualButtons: Boolean = AppDefaults.DefaultShowFullscreenVirtualButtons,
- val devicePreviewCardHeightDp: Int = AppDefaults.DefaultDevicePreviewCardHeightDp,
- val keepScreenOnWhenStreamingEnabled: Boolean = AppDefaults.DefaultKeepScreenOnWhenStreamingEnabled,
- val virtualButtonsOutside: String = AppDefaults.DefaultVirtualButtonsOutside,
- val virtualButtonsInMore: String = AppDefaults.DefaultVirtualButtonsInMore,
- val customServerUri: String? = null,
- val serverRemotePath: String = AppDefaults.DefaultServerRemotePathInput,
- val videoCodec: String = AppDefaults.DefaultVideoCodec,
- val audioEnabled: Boolean = AppDefaults.DefaultAudioEnabled,
- val audioCodec: String = AppDefaults.DefaultAudioCodec,
- val adbKeyName: String = AppDefaults.DefaultAdbKeyNameInput,
+ 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 keepScreenOnWhenStreamingEnabled: Boolean = AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
+ val devicePreviewCardHeightDp: Int = AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
+ val virtualButtonsOutside: String = AppDefaults.VIRTUAL_BUTTONS_OUTSIDE,
+ val virtualButtonsInMore: String = AppDefaults.VIRTUAL_BUTTONS_IN_MORE,
+ val customServerUri: String? = AppDefaults.CUSTOM_SERVER_URI,
+ val serverRemotePath: String = AppDefaults.SERVER_REMOTE_PATH_INPUT,
+ val adbKeyName: String = AppDefaults.ADB_KEY_NAME_INPUT,
)
internal data class DevicePageSettings(
- val pairHost: String = AppDefaults.DefaultPairHost,
- val pairPort: String = AppDefaults.DefaultPairPort,
- val pairCode: String = AppDefaults.DefaultPairCode,
- val quickConnectInput: String = AppDefaults.DefaultQuickConnectInput,
- val bitRateMbps: Float = AppDefaults.DefaultBitRateMbps,
- val bitRateInput: String = AppDefaults.DefaultBitRateInput,
- val audioBitRateKbps: Int = AppDefaults.DefaultAudioBitRateKbps,
- val maxSizeInput: String = AppDefaults.DefaultMaxSizeInput,
- val maxFpsInput: String = AppDefaults.DefaultMaxFpsInput,
- val noControl: Boolean = AppDefaults.DefaultNoControl,
- val videoEncoder: String = AppDefaults.DefaultVideoEncoder,
- val videoCodecOptions: String = AppDefaults.DefaultVideoCodecOptions,
- val audioEncoder: String = AppDefaults.DefaultAudioEncoder,
- val audioCodecOptions: String = AppDefaults.DefaultAudioCodecOptions,
- val audioDup: Boolean = AppDefaults.DefaultAudioDup,
- val audioSourcePreset: String = AppDefaults.DefaultAudioSourcePreset,
- val audioSourceCustom: String = AppDefaults.DefaultAudioSourceCustom,
- val videoSourcePreset: String = AppDefaults.DefaultVideoSourcePreset,
- val cameraIdInput: String = AppDefaults.DefaultCameraIdInput,
- val cameraFacingPreset: String = AppDefaults.DefaultCameraFacingPreset,
- val cameraSizePreset: String = AppDefaults.DefaultCameraSizePreset,
- val cameraSizeCustom: String = AppDefaults.DefaultCameraSizeCustom,
- val cameraArInput: String = AppDefaults.DefaultCameraArInput,
- val cameraFpsInput: String = AppDefaults.DefaultCameraFpsInput,
- val cameraHighSpeed: Boolean = AppDefaults.DefaultCameraHighSpeed,
- val noAudioPlayback: Boolean = AppDefaults.DefaultNoAudioPlayback,
- val noVideo: Boolean = AppDefaults.DefaultNoVideo,
- val requireAudio: Boolean = AppDefaults.DefaultRequireAudio,
- val turnScreenOff: Boolean = AppDefaults.DefaultTurnScreenOff,
- val newDisplayWidth: String = AppDefaults.DefaultNewDisplayWidth,
- val newDisplayHeight: String = AppDefaults.DefaultNewDisplayHeight,
- val newDisplayDpi: String = AppDefaults.DefaultNewDisplayDpi,
- val displayIdInput: String = AppDefaults.DefaultDisplayIdInput,
- val cropWidth: String = AppDefaults.DefaultCropWidth,
- val cropHeight: String = AppDefaults.DefaultCropHeight,
- val cropX: String = AppDefaults.DefaultCropX,
- val cropY: String = AppDefaults.DefaultCropY,
+ 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.PrefsName, Context.MODE_PRIVATE)
+ val prefs = context.getSharedPreferences(
+ AppPreferenceKeys.PREFS_NAME,
+ Context.MODE_PRIVATE,
+ )
return MainSettings(
- themeBaseIndex = prefs.getInt(AppPreferenceKeys.ThemeBaseIndex, AppDefaults.DefaultThemeBaseIndex),
- monetEnabled = prefs.getBoolean(AppPreferenceKeys.MonetEnabled, AppDefaults.DefaultMonetEnabled),
+ 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.FullscreenDebugInfoEnabled,
- AppDefaults.DefaultFullscreenDebugInfoEnabled,
+ AppPreferenceKeys.FULLSCREEN_DEBUG_INFO,
+ AppDefaults.FULLSCREEN_DEBUG_INFO,
),
showFullscreenVirtualButtons = prefs.getBoolean(
- AppPreferenceKeys.ShowFullscreenVirtualButtons,
- AppDefaults.DefaultShowFullscreenVirtualButtons,
+ AppPreferenceKeys.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
+ AppDefaults.SHOW_FULLSCREEN_VIRTUAL_BUTTONS,
+ ),
+ keepScreenOnWhenStreamingEnabled = prefs.getBoolean(
+ AppPreferenceKeys.KEEP_SCREEN_ON_WHEN_STREAMING,
+ AppDefaults.KEEP_SCREEN_ON_WHEN_STREAMING,
),
devicePreviewCardHeightDp = prefs.getInt(
- AppPreferenceKeys.DevicePreviewCardHeightDp,
- AppDefaults.DefaultDevicePreviewCardHeightDp,
+ AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
+ AppDefaults.DEVICE_PREVIEW_CARD_HEIGHT_DP,
).coerceAtLeast(120),
- keepScreenOnWhenStreamingEnabled = prefs.getBoolean(
- AppPreferenceKeys.KeepScreenOnWhenStreamingEnabled,
- AppDefaults.DefaultKeepScreenOnWhenStreamingEnabled,
- ),
virtualButtonsOutside = prefs.getString(
- AppPreferenceKeys.VirtualButtonsOutside,
- AppDefaults.DefaultVirtualButtonsOutside,
- ).orEmpty().ifBlank { AppDefaults.DefaultVirtualButtonsOutside },
+ AppPreferenceKeys.VIRTUAL_BUTTONS_OUTSIDE,
+ AppDefaults.VIRTUAL_BUTTONS_OUTSIDE,
+ ).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_OUTSIDE },
virtualButtonsInMore = prefs.getString(
- AppPreferenceKeys.VirtualButtonsInMore,
- AppDefaults.DefaultVirtualButtonsInMore,
- ).orEmpty().ifBlank { AppDefaults.DefaultVirtualButtonsInMore },
- customServerUri = prefs.getString(AppPreferenceKeys.CustomServerUri, null),
+ AppPreferenceKeys.VIRTUAL_BUTTONS_IN_MORE,
+ AppDefaults.VIRTUAL_BUTTONS_IN_MORE,
+ ).orEmpty().ifBlank { AppDefaults.VIRTUAL_BUTTONS_IN_MORE },
+ customServerUri = prefs.getString(
+ AppPreferenceKeys.CUSTOM_SERVER_URI,
+ AppDefaults.CUSTOM_SERVER_URI
+ ).orEmpty().ifBlank { null },
serverRemotePath = prefs.getString(
- AppPreferenceKeys.ServerRemotePath,
- AppDefaults.DefaultServerRemotePathInput,
+ AppPreferenceKeys.SERVER_REMOTE_PATH,
+ AppDefaults.SERVER_REMOTE_PATH_INPUT,
+ ).orEmpty(),
+ adbKeyName = prefs.getString(
+ AppPreferenceKeys.ADB_KEY_NAME,
+ AppDefaults.ADB_KEY_NAME_INPUT,
).orEmpty(),
- videoCodec = prefs.getString(AppPreferenceKeys.VideoCodec, AppDefaults.DefaultVideoCodec)
- .orEmpty()
- .ifBlank { AppDefaults.DefaultVideoCodec },
- audioEnabled = prefs.getBoolean(AppPreferenceKeys.AudioEnabled, AppDefaults.DefaultAudioEnabled),
- audioCodec = prefs.getString(AppPreferenceKeys.AudioCodec, AppDefaults.DefaultAudioCodec)
- .orEmpty()
- .ifBlank { AppDefaults.DefaultAudioCodec },
- adbKeyName = prefs.getString(AppPreferenceKeys.AdbKeyName, AppDefaults.DefaultAdbKeyNameInput).orEmpty(),
)
}
internal fun saveMainSettings(context: Context, settings: MainSettings) {
- context.getSharedPreferences(AppPreferenceKeys.PrefsName, Context.MODE_PRIVATE)
- .edit {
- putInt(AppPreferenceKeys.ThemeBaseIndex, settings.themeBaseIndex)
- .putBoolean(AppPreferenceKeys.MonetEnabled, settings.monetEnabled)
- .putBoolean(AppPreferenceKeys.FullscreenDebugInfoEnabled, settings.fullscreenDebugInfoEnabled)
- .putBoolean(AppPreferenceKeys.ShowFullscreenVirtualButtons, settings.showFullscreenVirtualButtons)
- .putInt(AppPreferenceKeys.DevicePreviewCardHeightDp, settings.devicePreviewCardHeightDp.coerceAtLeast(120))
- .putBoolean(AppPreferenceKeys.KeepScreenOnWhenStreamingEnabled, settings.keepScreenOnWhenStreamingEnabled)
- .putString(AppPreferenceKeys.VirtualButtonsOutside, settings.virtualButtonsOutside)
- .putString(AppPreferenceKeys.VirtualButtonsInMore, settings.virtualButtonsInMore)
- .putString(AppPreferenceKeys.CustomServerUri, settings.customServerUri)
- .putString(AppPreferenceKeys.ServerRemotePath, settings.serverRemotePath)
- .putString(AppPreferenceKeys.VideoCodec, settings.videoCodec)
- .putBoolean(AppPreferenceKeys.AudioEnabled, settings.audioEnabled)
- .putString(AppPreferenceKeys.AudioCodec, settings.audioCodec)
- .putString(AppPreferenceKeys.AdbKeyName, settings.adbKeyName)
- }
+ 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.KEEP_SCREEN_ON_WHEN_STREAMING,
+ settings.keepScreenOnWhenStreamingEnabled,
+ )
+ .putInt(
+ AppPreferenceKeys.DEVICE_PREVIEW_CARD_HEIGHT_DP,
+ settings.devicePreviewCardHeightDp.coerceAtLeast(120),
+ )
+ .putString(
+ AppPreferenceKeys.VIRTUAL_BUTTONS_OUTSIDE,
+ settings.virtualButtonsOutside,
+ )
+ .putString(
+ AppPreferenceKeys.VIRTUAL_BUTTONS_IN_MORE,
+ settings.virtualButtonsInMore,
+ )
+ .putString(
+ AppPreferenceKeys.CUSTOM_SERVER_URI,
+ settings.customServerUri,
+ )
+ .putString(
+ AppPreferenceKeys.SERVER_REMOTE_PATH,
+ settings.serverRemotePath,
+ )
+ .putString(
+ AppPreferenceKeys.ADB_KEY_NAME,
+ settings.adbKeyName,
+ )
+ }
}
internal fun loadDevicePageSettings(context: Context): DevicePageSettings {
- val prefs = context.getSharedPreferences(AppPreferenceKeys.PrefsName, Context.MODE_PRIVATE)
+ 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(
- pairHost = AppDefaults.DefaultPairHost,
- pairPort = AppDefaults.DefaultPairPort,
- pairCode = AppDefaults.DefaultPairCode,
- quickConnectInput = prefs.getString(AppPreferenceKeys.QuickConnectInput, AppDefaults.DefaultQuickConnectInput).orEmpty(),
- bitRateMbps = prefs.getFloat(AppPreferenceKeys.BitRateMbps, AppDefaults.DefaultBitRateMbps),
- bitRateInput = prefs.getString(AppPreferenceKeys.BitRateInput, AppDefaults.DefaultBitRateInput)
- .orEmpty()
- .ifBlank { AppDefaults.DefaultBitRateInput },
- maxSizeInput = prefs.getString(AppPreferenceKeys.MaxSizeInput, AppDefaults.DefaultMaxSizeInput).orEmpty(),
- audioBitRateKbps = prefs.getInt(AppPreferenceKeys.AudioBitRateKbps, AppDefaults.DefaultAudioBitRateKbps),
- maxFpsInput = prefs.getString(AppPreferenceKeys.MaxFpsInput, AppDefaults.DefaultMaxFpsInput).orEmpty(),
- noControl = prefs.getBoolean(AppPreferenceKeys.NoControl, AppDefaults.DefaultNoControl),
- videoEncoder = prefs.getString(AppPreferenceKeys.VideoEncoder, AppDefaults.DefaultVideoEncoder).orEmpty(),
- videoCodecOptions = prefs.getString(AppPreferenceKeys.VideoCodecOptions, AppDefaults.DefaultVideoCodecOptions).orEmpty(),
- audioEncoder = prefs.getString(AppPreferenceKeys.AudioEncoder, AppDefaults.DefaultAudioEncoder).orEmpty(),
- audioCodecOptions = prefs.getString(AppPreferenceKeys.AudioCodecOptions, AppDefaults.DefaultAudioCodecOptions).orEmpty(),
- audioDup = prefs.getBoolean(AppPreferenceKeys.AudioDup, AppDefaults.DefaultAudioDup),
- audioSourcePreset = prefs.getString(AppPreferenceKeys.AudioSourcePreset, AppDefaults.DefaultAudioSourcePreset)
- .orEmpty()
- .ifBlank { AppDefaults.DefaultAudioSourcePreset },
- audioSourceCustom = prefs.getString(AppPreferenceKeys.AudioSourceCustom, AppDefaults.DefaultAudioSourceCustom).orEmpty(),
- videoSourcePreset = prefs.getString(AppPreferenceKeys.VideoSourcePreset, AppDefaults.DefaultVideoSourcePreset)
- .orEmpty()
- .ifBlank { AppDefaults.DefaultVideoSourcePreset },
- cameraIdInput = prefs.getString(AppPreferenceKeys.CameraIdInput, AppDefaults.DefaultCameraIdInput).orEmpty(),
- cameraFacingPreset = prefs.getString(AppPreferenceKeys.CameraFacingPreset, AppDefaults.DefaultCameraFacingPreset).orEmpty(),
- cameraSizePreset = prefs.getString(AppPreferenceKeys.CameraSizePreset, AppDefaults.DefaultCameraSizePreset).orEmpty(),
- cameraSizeCustom = prefs.getString(AppPreferenceKeys.CameraSizeCustom, AppDefaults.DefaultCameraSizeCustom).orEmpty(),
- cameraArInput = prefs.getString(AppPreferenceKeys.CameraArInput, AppDefaults.DefaultCameraArInput).orEmpty(),
- cameraFpsInput = prefs.getString(AppPreferenceKeys.CameraFpsInput, AppDefaults.DefaultCameraFpsInput).orEmpty(),
- cameraHighSpeed = prefs.getBoolean(AppPreferenceKeys.CameraHighSpeed, AppDefaults.DefaultCameraHighSpeed),
- noAudioPlayback = prefs.getBoolean(AppPreferenceKeys.NoAudioPlayback, AppDefaults.DefaultNoAudioPlayback),
- noVideo = prefs.getBoolean(AppPreferenceKeys.NoVideo, AppDefaults.DefaultNoVideo),
- requireAudio = prefs.getBoolean(AppPreferenceKeys.RequireAudio, AppDefaults.DefaultRequireAudio),
- turnScreenOff = prefs.getBoolean(AppPreferenceKeys.TurnScreenOff, AppDefaults.DefaultTurnScreenOff),
- newDisplayWidth = prefs.getString(AppPreferenceKeys.NewDisplayWidth, AppDefaults.DefaultNewDisplayWidth).orEmpty(),
- newDisplayHeight = prefs.getString(AppPreferenceKeys.NewDisplayHeight, AppDefaults.DefaultNewDisplayHeight).orEmpty(),
- newDisplayDpi = prefs.getString(AppPreferenceKeys.NewDisplayDpi, AppDefaults.DefaultNewDisplayDpi).orEmpty(),
- displayIdInput = prefs.getString(AppPreferenceKeys.DisplayIdInput, AppDefaults.DefaultDisplayIdInput).orEmpty(),
- cropWidth = prefs.getString(AppPreferenceKeys.CropWidth, AppDefaults.DefaultCropWidth).orEmpty(),
- cropHeight = prefs.getString(AppPreferenceKeys.CropHeight, AppDefaults.DefaultCropHeight).orEmpty(),
- cropX = prefs.getString(AppPreferenceKeys.CropX, AppDefaults.DefaultCropX).orEmpty(),
- cropY = prefs.getString(AppPreferenceKeys.CropY, AppDefaults.DefaultCropY).orEmpty(),
+ 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.PrefsName, Context.MODE_PRIVATE)
+ context.getSharedPreferences(AppPreferenceKeys.PREFS_NAME, Context.MODE_PRIVATE)
.edit {
- remove(AppPreferenceKeys.PairHost)
- .remove(AppPreferenceKeys.PairPort)
- .remove(AppPreferenceKeys.PairCode)
- .putString(AppPreferenceKeys.QuickConnectInput, settings.quickConnectInput)
- .putFloat(AppPreferenceKeys.BitRateMbps, settings.bitRateMbps)
- .putString(AppPreferenceKeys.BitRateInput, settings.bitRateInput)
- .putInt(AppPreferenceKeys.AudioBitRateKbps, settings.audioBitRateKbps)
- .putString(AppPreferenceKeys.MaxSizeInput, settings.maxSizeInput)
- .putString(AppPreferenceKeys.MaxFpsInput, settings.maxFpsInput)
- .putBoolean(AppPreferenceKeys.NoControl, settings.noControl)
- .putString(AppPreferenceKeys.VideoEncoder, settings.videoEncoder)
- .putString(AppPreferenceKeys.VideoCodecOptions, settings.videoCodecOptions)
- .putString(AppPreferenceKeys.AudioEncoder, settings.audioEncoder)
- .putString(AppPreferenceKeys.AudioCodecOptions, settings.audioCodecOptions)
- .putBoolean(AppPreferenceKeys.AudioDup, settings.audioDup)
- .putString(AppPreferenceKeys.AudioSourcePreset, settings.audioSourcePreset)
- .putString(AppPreferenceKeys.AudioSourceCustom, settings.audioSourceCustom)
- .putString(AppPreferenceKeys.VideoSourcePreset, settings.videoSourcePreset)
- .putString(AppPreferenceKeys.CameraIdInput, settings.cameraIdInput)
- .putString(AppPreferenceKeys.CameraFacingPreset, settings.cameraFacingPreset)
- .putString(AppPreferenceKeys.CameraSizePreset, settings.cameraSizePreset)
- .putString(AppPreferenceKeys.CameraSizeCustom, settings.cameraSizeCustom)
- .putString(AppPreferenceKeys.CameraArInput, settings.cameraArInput)
- .putString(AppPreferenceKeys.CameraFpsInput, settings.cameraFpsInput)
- .putBoolean(AppPreferenceKeys.CameraHighSpeed, settings.cameraHighSpeed)
- .putBoolean(AppPreferenceKeys.NoAudioPlayback, settings.noAudioPlayback)
- .putBoolean(AppPreferenceKeys.NoVideo, settings.noVideo)
- .putBoolean(AppPreferenceKeys.RequireAudio, settings.requireAudio)
- .putBoolean(AppPreferenceKeys.TurnScreenOff, settings.turnScreenOff)
- .putString(AppPreferenceKeys.NewDisplayWidth, settings.newDisplayWidth)
- .putString(AppPreferenceKeys.NewDisplayHeight, settings.newDisplayHeight)
- .putString(AppPreferenceKeys.NewDisplayDpi, settings.newDisplayDpi)
- .putString(AppPreferenceKeys.DisplayIdInput, settings.displayIdInput)
- .putString(AppPreferenceKeys.CropWidth, settings.cropWidth)
- .putString(AppPreferenceKeys.CropHeight, settings.cropHeight)
- .putString(AppPreferenceKeys.CropX, settings.cropX)
- .putString(AppPreferenceKeys.CropY, settings.cropY)
+ 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,
+ )
}
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/DeviceConnectionLogic.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/DeviceConnectionLogic.kt
index 6dcaf27..d7c3d3a 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/DeviceConnectionLogic.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/DeviceConnectionLogic.kt
@@ -12,8 +12,13 @@ internal data class ConnectedDeviceInfo(
val sdkInt: Int,
)
-internal fun fetchConnectedDeviceInfo(nativeCore: NativeCoreFacade, host: String, port: Int): ConnectedDeviceInfo {
- fun prop(name: String): String = runCatching { nativeCore.adbShell("getprop $name").trim() }.getOrDefault("")
+internal fun fetchConnectedDeviceInfo(
+ nativeCore: NativeCoreFacade,
+ host: String,
+ port: Int
+): ConnectedDeviceInfo {
+ fun prop(name: String): String =
+ runCatching { nativeCore.adbShell("getprop $name").trim() }.getOrDefault("")
val model = prop("ro.product.model")
val serial = prop("ro.serialno")
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/QuickDeviceStore.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/QuickDeviceStore.kt
index 5a2b1bc..467a44f 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/QuickDeviceStore.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/services/QuickDeviceStore.kt
@@ -8,8 +8,8 @@ import io.github.miuzarte.scrcpyforandroid.models.ConnectionTarget
import io.github.miuzarte.scrcpyforandroid.models.DeviceShortcut
internal fun loadQuickDevices(context: Context): List {
- val raw = context.getSharedPreferences(AppPreferenceKeys.PrefsName, Context.MODE_PRIVATE)
- .getString(AppPreferenceKeys.QuickDevices, "")
+ val raw = context.getSharedPreferences(AppPreferenceKeys.PREFS_NAME, Context.MODE_PRIVATE)
+ .getString(AppPreferenceKeys.QUICK_DEVICES, "")
.orEmpty()
if (raw.isBlank()) return emptyList()
@@ -21,7 +21,7 @@ internal fun loadQuickDevices(context: Context): List {
3 -> {
val name = parts[0].trim()
val host = parts[1].trim()
- val port = parts[2].trim().toIntOrNull() ?: AppDefaults.DefaultAdbPort
+ val port = parts[2].trim().toIntOrNull() ?: AppDefaults.ADB_PORT
if (host.isNotBlank()) {
result.add(
DeviceShortcut(
@@ -39,7 +39,8 @@ internal fun loadQuickDevices(context: Context): List {
// Backward compatibility with old format: name|host:port
val name = parts[0].trim()
val host = parts[1].substringBefore(":").trim()
- val port = parts[1].substringAfter(":", AppDefaults.DefaultAdbPort.toString()).trim().toIntOrNull() ?: AppDefaults.DefaultAdbPort
+ val port = parts[1].substringAfter(":", AppDefaults.ADB_PORT.toString()).trim()
+ .toIntOrNull() ?: AppDefaults.ADB_PORT
if (host.isNotBlank()) {
result.add(
DeviceShortcut(
@@ -59,9 +60,9 @@ internal fun loadQuickDevices(context: Context): List {
internal fun saveQuickDevices(context: Context, quickDevices: List) {
val raw = quickDevices.joinToString("\n") { "${it.name}|${it.host}|${it.port}" }
- context.getSharedPreferences(AppPreferenceKeys.PrefsName, Context.MODE_PRIVATE)
+ context.getSharedPreferences(AppPreferenceKeys.PREFS_NAME, Context.MODE_PRIVATE)
.edit {
- putString(AppPreferenceKeys.QuickDevices, raw)
+ putString(AppPreferenceKeys.QUICK_DEVICES, raw)
}
}
@@ -70,7 +71,8 @@ internal fun parseQuickTarget(raw: String): ConnectionTarget? {
if (value.isEmpty()) return null
val host = value.substringBefore(':').trim()
if (host.isEmpty()) return null
- val port = value.substringAfter(':', AppDefaults.DefaultAdbPort.toString()).trim().toIntOrNull() ?: AppDefaults.DefaultAdbPort
+ val port = value.substringAfter(':', AppDefaults.ADB_PORT.toString()).trim().toIntOrNull()
+ ?: AppDefaults.ADB_PORT
return ConnectionTarget(host, port)
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/DeviceWidgets.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/DeviceWidgets.kt
index e1180df..e5044fb 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/DeviceWidgets.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/DeviceWidgets.kt
@@ -13,7 +13,6 @@ import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -32,11 +31,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.AddLink
import androidx.compose.material.icons.filled.Fullscreen
-import androidx.compose.material.icons.filled.Home
-import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.rounded.CheckCircleOutline
import androidx.compose.material.icons.rounded.LinkOff
import androidx.compose.material.icons.rounded.Wifi
@@ -63,7 +59,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
@@ -96,7 +91,7 @@ import kotlin.math.roundToInt
private val VIDEO_CODEC_OPTIONS = listOf(
"h264" to "H.264",
"h265" to "H.265",
- "av1" to "AV1",
+ "av1" to "AV1",
)
private val AUDIO_CODEC_OPTIONS = listOf(
@@ -266,9 +261,13 @@ internal fun PreviewCard(
}
val containerAspect = maxWidth.value / maxHeight.value
val fittedModifier = if (sessionAspect > containerAspect) {
- Modifier.fillMaxWidth().aspectRatio(sessionAspect)
+ Modifier
+ .fillMaxWidth()
+ .aspectRatio(sessionAspect)
} else {
- Modifier.fillMaxHeight().aspectRatio(sessionAspect)
+ Modifier
+ .fillMaxHeight()
+ .aspectRatio(sessionAspect)
}
Box(
@@ -358,10 +357,13 @@ internal fun ConfigPanel(
sessionStarted: Boolean,
) {
val videoCodecItems = remember { VIDEO_CODEC_OPTIONS.map { it.second } }
- val videoCodecIndex = VIDEO_CODEC_OPTIONS.indexOfFirst { it.first == videoCodec }.coerceAtLeast(0)
+ val videoCodecIndex =
+ VIDEO_CODEC_OPTIONS.indexOfFirst { it.first == videoCodec }.coerceAtLeast(0)
val audioCodecItems = remember { AUDIO_CODEC_OPTIONS.map { it.second } }
- val audioCodecIndex = AUDIO_CODEC_OPTIONS.indexOfFirst { it.first == audioCodec }.coerceAtLeast(0)
- val audioBitRatePresetIndex = presetIndexFromInput(audioBitRateKbps.toString(), ScrcpyPresets.AudioBitRate)
+ val audioCodecIndex =
+ AUDIO_CODEC_OPTIONS.indexOfFirst { it.first == audioCodec }.coerceAtLeast(0)
+ val audioBitRatePresetIndex =
+ presetIndexFromInput(audioBitRateKbps.toString(), ScrcpyPresets.AudioBitRate)
SectionSmallTitle(text = "Scrcpy")
Card {
@@ -433,6 +435,7 @@ internal fun ConfigPanel(
dotUsed = true
true
}
+
else -> false
}
}
@@ -506,7 +509,9 @@ private fun PairingDialog(
onValueChange = { host = it },
label = "IP 地址",
singleLine = true,
- modifier = Modifier.fillMaxWidth().padding(bottom = UiSpacing.CardContent),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = UiSpacing.CardContent),
)
TextField(
value = port,
@@ -514,14 +519,18 @@ private fun PairingDialog(
label = "端口",
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
- modifier = Modifier.fillMaxWidth().padding(bottom = UiSpacing.CardContent),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = UiSpacing.CardContent),
)
TextField(
value = code,
onValueChange = { code = it },
label = "WLAN 配对码",
singleLine = true,
- modifier = Modifier.fillMaxWidth().padding(bottom = UiSpacing.Large),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(bottom = UiSpacing.Large),
)
Row(horizontalArrangement = Arrangement.spacedBy(UiSpacing.PopupHorizontal)) {
TextButton(
@@ -599,8 +608,10 @@ fun FullscreenControlScreen(
}
fun mapToDevice(rawX: Float, rawY: Float): Pair {
- val x = ((rawX / touchAreaSize.width) * session.width).roundToInt().coerceIn(0, (session.width - 1).coerceAtLeast(0))
- val y = ((rawY / touchAreaSize.height) * session.height).roundToInt().coerceIn(0, (session.height - 1).coerceAtLeast(0))
+ val x = ((rawX / touchAreaSize.width) * session.width).roundToInt()
+ .coerceIn(0, (session.width - 1).coerceAtLeast(0))
+ val y = ((rawY / touchAreaSize.height) * session.height).roundToInt()
+ .coerceIn(0, (session.height - 1).coerceAtLeast(0))
return x to y
}
@@ -612,7 +623,14 @@ fun FullscreenControlScreen(
val py = event.getY(i)
activePointerPositions[pointerId] = Offset(px, py)
val (x, y) = mapToDevice(px, py)
- onInjectTouch(UiMotionActions.MOVE, pointerId.toLong(), x, y, event.getPressure(i).coerceIn(0f, 1f), 1)
+ onInjectTouch(
+ UiMotionActions.MOVE,
+ pointerId.toLong(),
+ x,
+ y,
+ event.getPressure(i).coerceIn(0f, 1f),
+ 1
+ )
}
}
@@ -626,7 +644,14 @@ fun FullscreenControlScreen(
activePointerIds += pointerId
activePointerPositions[pointerId] = Offset(event.x, event.y)
activeTouchCount = activePointerIds.size
- onInjectTouch(UiMotionActions.DOWN, pointerId.toLong(), x, y, event.getPressure(0).coerceIn(0f, 1f), 1)
+ onInjectTouch(
+ UiMotionActions.DOWN,
+ pointerId.toLong(),
+ x,
+ y,
+ event.getPressure(0).coerceIn(0f, 1f),
+ 1
+ )
}
MotionEvent.ACTION_POINTER_DOWN -> {
@@ -638,7 +663,14 @@ fun FullscreenControlScreen(
activePointerIds += pointerId
activePointerPositions[pointerId] = Offset(px, py)
activeTouchCount = activePointerIds.size
- onInjectTouch(UiMotionActions.POINTER_DOWN, pointerId.toLong(), x, y, event.getPressure(index).coerceIn(0f, 1f), 1)
+ onInjectTouch(
+ UiMotionActions.POINTER_DOWN,
+ pointerId.toLong(),
+ x,
+ y,
+ event.getPressure(index).coerceIn(0f, 1f),
+ 1
+ )
syncActivePointersFromEvent()
}
@@ -650,7 +682,14 @@ fun FullscreenControlScreen(
val py = event.getY(i)
activePointerPositions[pointerId] = Offset(px, py)
val (x, y) = mapToDevice(px, py)
- onInjectTouch(UiMotionActions.MOVE, pointerId.toLong(), x, y, event.getPressure(i).coerceIn(0f, 1f), 1)
+ onInjectTouch(
+ UiMotionActions.MOVE,
+ pointerId.toLong(),
+ x,
+ y,
+ event.getPressure(i).coerceIn(0f, 1f),
+ 1
+ )
}
}
@@ -698,9 +737,13 @@ fun FullscreenControlScreen(
}
val containerAspect = maxWidth.value / maxHeight.value
val fittedModifier = if (sessionAspect > containerAspect) {
- Modifier.fillMaxWidth().aspectRatio(sessionAspect)
+ Modifier
+ .fillMaxWidth()
+ .aspectRatio(sessionAspect)
} else {
- Modifier.fillMaxHeight().aspectRatio(sessionAspect)
+ Modifier
+ .fillMaxHeight()
+ .aspectRatio(sessionAspect)
}
Box(
@@ -780,13 +823,21 @@ private fun ScrcpyVideoSurface(
factory = { context ->
TextureView(context).apply {
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
- override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
+ override fun onSurfaceTextureAvailable(
+ surfaceTexture: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) {
currentSurface?.release() // Release stale surface if any
@SuppressLint("Recycle")
currentSurface = Surface(surfaceTexture)
}
- override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) = Unit
+ override fun onSurfaceTextureSizeChanged(
+ surfaceTexture: SurfaceTexture,
+ width: Int,
+ height: Int
+ ) = Unit
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
val released = currentSurface
@@ -820,7 +871,7 @@ internal fun DeviceTile(
Card(
colors = CardDefaults.defaultColors(
color = if (device.online) MiuixTheme.colorScheme.surfaceContainer
- else MiuixTheme.colorScheme.surfaceContainer.copy(alpha = 0.6f),
+ else MiuixTheme.colorScheme.surfaceContainer.copy(alpha = 0.6f),
),
pressFeedbackType = PressFeedbackType.Sink,
onClick = haptics.press,
@@ -1016,7 +1067,9 @@ internal fun DeviceEditorScreen(
.padding(top = UiSpacing.CardContent),
)
Row(
- modifier = Modifier.fillMaxWidth().padding(UiSpacing.CardContent),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(UiSpacing.CardContent),
horizontalArrangement = Arrangement.spacedBy(UiSpacing.Medium),
) {
TextButton(
@@ -1032,7 +1085,7 @@ internal fun DeviceEditorScreen(
TextButton(
text = "保存",
onClick = {
- val p = port.toIntOrNull() ?: AppDefaults.DefaultAdbPort
+ val p = port.toIntOrNull() ?: AppDefaults.ADB_PORT
val h = host.trim()
if (h.isNotBlank()) {
onSave(
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/SortableCardList.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/SortableCardList.kt
index 3834c5e..365d5f1 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/SortableCardList.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/SortableCardList.kt
@@ -110,7 +110,10 @@ fun SortableCardList(
Row(
modifier = Modifier
.fillMaxWidth()
- .padding(horizontal = UiSpacing.CardContent, vertical = UiSpacing.FieldLabelBottom),
+ .padding(
+ horizontal = UiSpacing.CardContent,
+ vertical = UiSpacing.FieldLabelBottom
+ ),
verticalAlignment = Alignment.CenterVertically,
) {
Column(modifier = Modifier.weight(1f)) {
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/StatusCardModels.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/StatusCardModels.kt
index 2809ddc..b5ffbbc 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/StatusCardModels.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/StatusCardModels.kt
@@ -69,12 +69,16 @@ internal fun StatusCardLayout(
) {
val haptics = rememberAppHaptics()
Row(
- modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Min),
horizontalArrangement = Arrangement.spacedBy(UiSpacing.PageItem),
verticalAlignment = Alignment.CenterVertically,
) {
Card(
- modifier = Modifier.weight(1f).fillMaxHeight(),
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight(),
colors = defaultColors(color = spec.big.containerColor),
pressFeedbackType = PressFeedbackType.Tilt,
onClick = haptics.press,
@@ -125,15 +129,21 @@ internal fun StatusCardLayout(
}
}
- Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
+ Column(modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight()) {
StatusMetricCard(
spec = spec.firstSmall,
- modifier = Modifier.fillMaxWidth().weight(1f),
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
)
Spacer(Modifier.height(UiSpacing.PageItem))
StatusMetricCard(
spec = spec.secondSmall,
- modifier = Modifier.fillMaxWidth().weight(1f),
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
)
}
}
diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/VirtualButtons.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/VirtualButtons.kt
index ceb55ff..c764646 100644
--- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/VirtualButtons.kt
+++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/widgets/VirtualButtons.kt
@@ -19,9 +19,6 @@ import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.filled.PowerSettingsNew
-import androidx.compose.material.icons.filled.VolumeDown
-import androidx.compose.material.icons.filled.VolumeOff
-import androidx.compose.material.icons.filled.VolumeUp
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -50,36 +47,76 @@ enum class VirtualButtonAction(
val icon: ImageVector,
val keycode: Int?,
) {
- MORE("more", "更多", Icons.Default.MoreVert, null),
- HOME("home", "主页", Icons.Default.Home, UiAndroidKeycodes.HOME),
- BACK("back", "返回", Icons.AutoMirrored.Filled.ArrowBack, UiAndroidKeycodes.BACK),
- APP_SWITCH("app_switch", "多任务", Icons.Default.Apps, UiAndroidKeycodes.APP_SWITCH),
- MENU("menu", "菜单", Icons.Default.Menu, UiAndroidKeycodes.MENU),
- NOTIFICATION("notification", "通知栏", Icons.Default.Notifications, UiAndroidKeycodes.NOTIFICATION),
- VOLUME_UP("volume_up", "音量+", Icons.AutoMirrored.Filled.VolumeUp, UiAndroidKeycodes.VOLUME_UP),
- VOLUME_DOWN("volume_down", "音量-", Icons.AutoMirrored.Filled.VolumeDown, UiAndroidKeycodes.VOLUME_DOWN),
- VOLUME_MUTE("volume_mute", "静音", Icons.AutoMirrored.Filled.VolumeOff, UiAndroidKeycodes.VOLUME_MUTE),
- POWER("power", "锁屏", Icons.Default.PowerSettingsNew, UiAndroidKeycodes.POWER),
- SCREENSHOT("screenshot", "截图", Icons.Default.PhotoCamera, UiAndroidKeycodes.SYSRQ),
+ MORE(
+ "more",
+ "更多",
+ Icons.Default.MoreVert,
+ null
+ ),
+ HOME(
+ "home",
+ "主页",
+ Icons.Default.Home,
+ UiAndroidKeycodes.HOME
+ ),
+ BACK(
+ "back",
+ "返回",
+ Icons.AutoMirrored.Filled.ArrowBack,
+ UiAndroidKeycodes.BACK
+ ),
+ APP_SWITCH(
+ "app_switch",
+ "多任务",
+ Icons.Default.Apps,
+ UiAndroidKeycodes.APP_SWITCH
+ ),
+ MENU(
+ "menu",
+ "菜单",
+ Icons.Default.Menu,
+ UiAndroidKeycodes.MENU
+ ),
+ NOTIFICATION(
+ "notification",
+ "通知栏",
+ Icons.Default.Notifications,
+ UiAndroidKeycodes.NOTIFICATION
+ ),
+ VOLUME_UP(
+ "volume_up",
+ "音量+",
+ Icons.AutoMirrored.Filled.VolumeUp,
+ UiAndroidKeycodes.VOLUME_UP
+ ),
+ VOLUME_DOWN(
+ "volume_down",
+ "音量-",
+ Icons.AutoMirrored.Filled.VolumeDown,
+ UiAndroidKeycodes.VOLUME_DOWN
+ ),
+ VOLUME_MUTE(
+ "volume_mute",
+ "静音",
+ Icons.AutoMirrored.Filled.VolumeOff,
+ UiAndroidKeycodes.VOLUME_MUTE
+ ),
+ POWER(
+ "power",
+ "锁屏",
+ Icons.Default.PowerSettingsNew,
+ UiAndroidKeycodes.POWER
+ ),
+ SCREENSHOT(
+ "screenshot",
+ "截图",
+ Icons.Default.PhotoCamera,
+ UiAndroidKeycodes.SYSRQ
+ ),
}
object VirtualButtonActions {
val all = VirtualButtonAction.entries
- val defaultOutsideIds = listOf(
- VirtualButtonAction.MORE.id,
- VirtualButtonAction.HOME.id,
- VirtualButtonAction.BACK.id,
- )
- val defaultMoreIds = listOf(
- VirtualButtonAction.APP_SWITCH.id,
- VirtualButtonAction.MENU.id,
- VirtualButtonAction.NOTIFICATION.id,
- VirtualButtonAction.VOLUME_UP.id,
- VirtualButtonAction.VOLUME_DOWN.id,
- VirtualButtonAction.VOLUME_MUTE.id,
- VirtualButtonAction.POWER.id,
- VirtualButtonAction.SCREENSHOT.id,
- )
private val byId = all.associateBy { it.id }