diff --git a/CHANGELOG.md b/CHANGELOG.md index e377696..e7f6149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Change Log +## 0.3.4 + +- 修复: 终端输入未正确串行化 + ## 0.3.3 - 新增: 语言选择 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 544e6f7..898278f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -56,8 +56,8 @@ android { applicationId = "io.github.miuzarte.scrcpyforandroid" minSdk = 26 targetSdk = 37 - versionCode = 29 - versionName = "0.3.3" + versionCode = 30 + versionName = "0.3.4" externalNativeBuild { cmake { diff --git a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/TerminalViewModel.kt b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/TerminalViewModel.kt index 2d71644..cf31a10 100644 --- a/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/TerminalViewModel.kt +++ b/app/src/main/java/io/github/miuzarte/scrcpyforandroid/pages/TerminalViewModel.kt @@ -17,13 +17,13 @@ import io.github.miuzarte.scrcpyforandroid.services.LocalInputService import io.github.miuzarte.scrcpyforandroid.storage.BundleSyncDelegate import io.github.miuzarte.scrcpyforandroid.storage.Storage.appSettings import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import top.yukonga.miuix.kmp.basic.SnackbarResult import java.nio.charset.StandardCharsets @@ -67,33 +67,43 @@ internal class TerminalViewModel : ViewModel() { private val _shellConnecting = MutableStateFlow(false) val shellConnecting: StateFlow = _shellConnecting.asStateFlow() - private val shellWriteMutex = Mutex() private var shellStream: AdbSocketStream? = null + private var shellWriterJob: Job? = null + private var shellWriteChannel = Channel(Channel.UNLIMITED) val sessionHolder = arrayOfNulls(1) - fun writeBytesToShell(data: ByteArray, offset: Int, count: Int) { - val stream = shellStream - if (stream == null || !_shellReady.value || stream.closed) return - val payload = data.copyOfRange(offset, offset + count) - viewModelScope.launch(Dispatchers.IO) { - val result = runCatching { - shellWriteMutex.withLock { + private fun startShellWriter() { + shellWriteChannel = Channel(Channel.UNLIMITED) + shellWriterJob = viewModelScope.launch(Dispatchers.IO) { + for (payload in shellWriteChannel) { + val stream = shellStream ?: break + if (stream.closed) break + val result = runCatching { stream.outputStream.write(payload) stream.outputStream.flush() } - } - withContext(Dispatchers.Main) { - result.onFailure { error -> - AppRuntime.snackbar( - R.string.terminal_snack_input_failed, - error.message ?: error.javaClass.simpleName, - ) + if (result.isFailure) { + withContext(Dispatchers.Main) { + result.exceptionOrNull()?.let { error -> + AppRuntime.snackbar( + R.string.terminal_snack_input_failed, + 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) { val text = LocalInputService.getClipboardText(context) if (!text.isNullOrBlank()) { @@ -192,6 +202,7 @@ internal class TerminalViewModel : ViewModel() { shellStream = stream _shellReady.value = true _shellConnecting.value = false + startShellWriter() if (showKeyboardAfterConnect) requestFocus() } @@ -214,6 +225,8 @@ internal class TerminalViewModel : ViewModel() { } finally { runCatching { stream.close() } withContext(Dispatchers.Main) { + shellWriterJob?.cancel() + shellWriterJob = null if (shellStream === stream) shellStream = null _shellReady.value = false _shellConnecting.value = false @@ -233,6 +246,8 @@ internal class TerminalViewModel : ViewModel() { } fun closeShell() { + shellWriterJob?.cancel() + shellWriterJob = null runCatching { shellStream?.close() } shellStream = null _shellReady.value = false