fix: terminal input serialization

This commit is contained in:
Miuzarte
2026-05-08 23:50:46 +08:00
parent 2aa40570b8
commit 5bcd19d1a8
3 changed files with 38 additions and 19 deletions

View File

@@ -1,5 +1,9 @@
# Change Log # Change Log
## 0.3.4
- 修复: 终端输入未正确串行化
## 0.3.3 ## 0.3.3
- 新增: 语言选择 - 新增: 语言选择

View File

@@ -56,8 +56,8 @@ android {
applicationId = "io.github.miuzarte.scrcpyforandroid" applicationId = "io.github.miuzarte.scrcpyforandroid"
minSdk = 26 minSdk = 26
targetSdk = 37 targetSdk = 37
versionCode = 29 versionCode = 30
versionName = "0.3.3" versionName = "0.3.4"
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@@ -17,13 +17,13 @@ import io.github.miuzarte.scrcpyforandroid.services.LocalInputService
import io.github.miuzarte.scrcpyforandroid.storage.BundleSyncDelegate import io.github.miuzarte.scrcpyforandroid.storage.BundleSyncDelegate
import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import top.yukonga.miuix.kmp.basic.SnackbarResult import top.yukonga.miuix.kmp.basic.SnackbarResult
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@@ -67,33 +67,43 @@ internal class TerminalViewModel : ViewModel() {
private val _shellConnecting = MutableStateFlow(false) private val _shellConnecting = MutableStateFlow(false)
val shellConnecting: StateFlow<Boolean> = _shellConnecting.asStateFlow() val shellConnecting: StateFlow<Boolean> = _shellConnecting.asStateFlow()
private val shellWriteMutex = Mutex()
private var shellStream: AdbSocketStream? = null private var shellStream: AdbSocketStream? = null
private var shellWriterJob: Job? = null
private var shellWriteChannel = Channel<ByteArray>(Channel.UNLIMITED)
val sessionHolder = arrayOfNulls<TerminalSession>(1) val sessionHolder = arrayOfNulls<TerminalSession>(1)
fun writeBytesToShell(data: ByteArray, offset: Int, count: Int) { private fun startShellWriter() {
val stream = shellStream shellWriteChannel = Channel(Channel.UNLIMITED)
if (stream == null || !_shellReady.value || stream.closed) return shellWriterJob = viewModelScope.launch(Dispatchers.IO) {
val payload = data.copyOfRange(offset, offset + count) for (payload in shellWriteChannel) {
viewModelScope.launch(Dispatchers.IO) { val stream = shellStream ?: break
val result = runCatching { if (stream.closed) break
shellWriteMutex.withLock { val result = runCatching {
stream.outputStream.write(payload) stream.outputStream.write(payload)
stream.outputStream.flush() stream.outputStream.flush()
} }
} if (result.isFailure) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
result.onFailure { error -> result.exceptionOrNull()?.let { error ->
AppRuntime.snackbar( AppRuntime.snackbar(
R.string.terminal_snack_input_failed, R.string.terminal_snack_input_failed,
error.message ?: error.javaClass.simpleName, error.message ?: error.javaClass.simpleName,
) )
}
}
break
} }
} }
} }
} }
fun writeBytesToShell(data: ByteArray, offset: Int, count: Int) {
if (!_shellReady.value) return
val payload = data.copyOfRange(offset, offset + count)
shellWriteChannel.trySend(payload)
}
fun writeClipboardToShell(context: Context) { fun writeClipboardToShell(context: Context) {
val text = LocalInputService.getClipboardText(context) val text = LocalInputService.getClipboardText(context)
if (!text.isNullOrBlank()) { if (!text.isNullOrBlank()) {
@@ -192,6 +202,7 @@ internal class TerminalViewModel : ViewModel() {
shellStream = stream shellStream = stream
_shellReady.value = true _shellReady.value = true
_shellConnecting.value = false _shellConnecting.value = false
startShellWriter()
if (showKeyboardAfterConnect) requestFocus() if (showKeyboardAfterConnect) requestFocus()
} }
@@ -214,6 +225,8 @@ internal class TerminalViewModel : ViewModel() {
} finally { } finally {
runCatching { stream.close() } runCatching { stream.close() }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
shellWriterJob?.cancel()
shellWriterJob = null
if (shellStream === stream) shellStream = null if (shellStream === stream) shellStream = null
_shellReady.value = false _shellReady.value = false
_shellConnecting.value = false _shellConnecting.value = false
@@ -233,6 +246,8 @@ internal class TerminalViewModel : ViewModel() {
} }
fun closeShell() { fun closeShell() {
shellWriterJob?.cancel()
shellWriterJob = null
runCatching { shellStream?.close() } runCatching { shellStream?.close() }
shellStream = null shellStream = null
_shellReady.value = false _shellReady.value = false