diff --git a/mosaic-terminal/src/c/mosaic-stdin-windows.c b/mosaic-terminal/src/c/mosaic-stdin-windows.c index 0ba173031..3c0112c46 100644 --- a/mosaic-terminal/src/c/mosaic-stdin-windows.c +++ b/mosaic-terminal/src/c/mosaic-stdin-windows.c @@ -51,8 +51,8 @@ stdinReaderResult stdinReader_initWithHandle(HANDLE stdinRead, HANDLE stdinWait) } stdinReaderResult stdinReader_init() { - HANDLE stdin = GetStdHandle(STD_INPUT_HANDLE); - return stdinReader_initWithHandle(stdin, stdin); + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + return stdinReader_initWithHandle(h, h); } stdinRead stdinReader_read( @@ -100,7 +100,7 @@ platformError stdinReader_interrupt(stdinReader *reader) { platformError stdinReader_free(stdinReader *reader) { DWORD result = 0; - if (unlikely(CloseHandle(reader->waitHandles[1]) != 0)) { + if (unlikely(CloseHandle(reader->waitHandles[1]) == 0)) { result = GetLastError(); } free(reader); @@ -162,13 +162,13 @@ platformError stdinWriter_write(stdinWriter *writer, void *buffer, int count) { platformError stdinWriter_free(stdinWriter *writer) { DWORD result = 0; - if (unlikely(CloseHandle(writer->eventHandle) != 0)) { + if (unlikely(CloseHandle(writer->eventHandle) == 0)) { result = GetLastError(); } - if (unlikely(CloseHandle(writer->writeHandle) != 0 && result == 0)) { + if (unlikely(CloseHandle(writer->writeHandle) == 0 && result == 0)) { result = GetLastError(); } - if (unlikely(CloseHandle(writer->readHandle) != 0 && result == 0)) { + if (unlikely(CloseHandle(writer->readHandle) == 0 && result == 0)) { result = GetLastError(); } free(writer); diff --git a/mosaic-terminal/src/commonTest/kotlin/com/jakewharton/mosaic/terminal/StdinReaderTest.kt b/mosaic-terminal/src/commonTest/kotlin/com/jakewharton/mosaic/terminal/StdinReaderTest.kt index 31e86bc14..c06283306 100644 --- a/mosaic-terminal/src/commonTest/kotlin/com/jakewharton/mosaic/terminal/StdinReaderTest.kt +++ b/mosaic-terminal/src/commonTest/kotlin/com/jakewharton/mosaic/terminal/StdinReaderTest.kt @@ -61,16 +61,19 @@ class StdinReaderTest { } @Test fun readWithTimeoutReturnsZeroOnTimeout() { + // The timeouts passed are slightly higher than those validated thanks to Windows which + // can return _slightly_ early. Usually it's around .1ms, but we go 10ms to be sure. + val readA: Int val tookA = measureTime { - readA = reader.readWithTimeout(ByteArray(10), 0, 10, 100) + readA = reader.readWithTimeout(ByteArray(10), 0, 10, 110) } assertThat(readA).isZero() assertThat(tookA).isGreaterThan(100.milliseconds) val readB: Int val tookB = measureTime { - readB = reader.readWithTimeout(ByteArray(10), 0, 10, 100) + readB = reader.readWithTimeout(ByteArray(10), 0, 10, 110) } assertThat(readB).isZero() assertThat(tookB).isGreaterThan(100.milliseconds) diff --git a/mosaic-terminal/src/jvmMain/jni/mosaic-jni.c b/mosaic-terminal/src/jvmMain/jni/mosaic-jni.c index d879afb0a..029676a73 100644 --- a/mosaic-terminal/src/jvmMain/jni/mosaic-jni.c +++ b/mosaic-terminal/src/jvmMain/jni/mosaic-jni.c @@ -37,9 +37,12 @@ Java_com_jakewharton_mosaic_terminal_Tty_enterRawMode(JNIEnv *env, jclass type) return 0; } -JNIEXPORT jint JNICALL +JNIEXPORT void JNICALL Java_com_jakewharton_mosaic_terminal_Tty_exitRawMode(JNIEnv *env, jclass type, jlong ptr) { - return exitRawMode((rawModeConfig *) ptr); + platformError error = exitRawMode((rawModeConfig *) ptr); + if (unlikely(error)) { + throwIse(env, error, "Unable to exit raw mode"); + } } JNIEXPORT jlong JNICALL @@ -113,14 +116,20 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderReadWithTimeout( return -1; } -JNIEXPORT jint JNICALL +JNIEXPORT void JNICALL Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderInterrupt(JNIEnv *env, jclass type, jlong ptr) { - return stdinReader_interrupt((stdinReader *) ptr); + platformError error = stdinReader_interrupt((stdinReader *) ptr); + if (unlikely(error)) { + throwIse(env, error, "Unable to interrupt stdin reader"); + } } -JNIEXPORT jint JNICALL +JNIEXPORT void JNICALL Java_com_jakewharton_mosaic_terminal_Tty_stdinReaderFree(JNIEnv *env, jclass type, jlong ptr) { - return stdinReader_free((stdinReader *) ptr); + platformError error = stdinReader_free((stdinReader *) ptr); + if (unlikely(error)) { + throwIse(env, error, "Unable to free stdin reader"); + } } JNIEXPORT jlong JNICALL @@ -161,7 +170,10 @@ Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterGetReader(JNIEnv *env, jclas return (jlong) stdinWriter_getReader((stdinWriter *) ptr); } -JNIEXPORT jint JNICALL +JNIEXPORT void JNICALL Java_com_jakewharton_mosaic_terminal_Tty_stdinWriterFree(JNIEnv *env, jclass type, jlong ptr) { - return stdinWriter_free((stdinWriter *) ptr); + platformError error = stdinWriter_free((stdinWriter *) ptr); + if (unlikely(error)) { + throwIse(env, error, "Unable to free stdin writer"); + } } diff --git a/mosaic-terminal/src/jvmMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt b/mosaic-terminal/src/jvmMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt index d459d3e4c..06cfe5a9d 100644 --- a/mosaic-terminal/src/jvmMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt +++ b/mosaic-terminal/src/jvmMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt @@ -21,8 +21,7 @@ public actual object Tty { private val savedPtr: Long, ) : AutoCloseable { override fun close() { - val error = exitRawMode(savedPtr) - check(error == 0) { "Unable to exit raw mode: $error" } + exitRawMode(savedPtr) } } @@ -44,7 +43,7 @@ public actual object Tty { private external fun enterRawMode(): Long @JvmStatic - private external fun exitRawMode(savedConfig: Long): Int + private external fun exitRawMode(savedConfig: Long) @JvmStatic private external fun stdinReaderInit(): Long @@ -73,12 +72,12 @@ public actual object Tty { @JvmStatic @JvmSynthetic // Hide from Java callers. @JvmName("stdinReaderInterrupt") // Avoid internal name mangling. - internal external fun stdinReaderInterrupt(reader: Long): Int + internal external fun stdinReaderInterrupt(reader: Long) @JvmStatic @JvmSynthetic // Hide from Java callers. @JvmName("stdinReaderFree") // Avoid internal name mangling. - internal external fun stdinReaderFree(reader: Long): Int + internal external fun stdinReaderFree(reader: Long) @JvmStatic private external fun stdinWriterInit(): Long diff --git a/mosaic-terminal/src/nativeMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt b/mosaic-terminal/src/nativeMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt index 9f90aa2c7..22ce0f181 100644 --- a/mosaic-terminal/src/nativeMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt +++ b/mosaic-terminal/src/nativeMain/kotlin/com/jakewharton/mosaic/terminal/Tty.kt @@ -21,7 +21,8 @@ public actual object Tty { ) : AutoCloseable { override fun close() { val error = exitRawMode(savedConfig) - check(error == 0U) { "Unable to exit raw mode: $error" } + if (error == 0U) return + throwError(error) } } @@ -41,17 +42,21 @@ public actual object Tty { val reader = stdinWriter_getReader(writer)!! return StdinWriter(writer, reader) } + + internal fun throwError(error: UInt): Nothing { + throw RuntimeException(error.toString()) + } } @OptIn(ExperimentalForeignApi::class) public actual class StdinReader internal constructor( - private val ref: CPointer, + private var ref: CPointer?, ) : AutoCloseable { public actual fun read(buffer: ByteArray, offset: Int, length: Int): Int { buffer.usePinned { stdinReader_read(ref, it.addressOf(offset), length).useContents { if (error == 0U) return count - throw RuntimeException(error.toString()) + Tty.throwError(error) } } } @@ -60,23 +65,31 @@ public actual class StdinReader internal constructor( buffer.usePinned { stdinReader_readWithTimeout(ref, it.addressOf(offset), length, timeoutMillis).useContents { if (error == 0U) return count - throw RuntimeException(error.toString()) + Tty.throwError(error) } } } public actual fun interrupt() { - stdinReader_interrupt(ref) + val error = stdinReader_interrupt(ref) + if (error == 0U) return + Tty.throwError(error) } public actual override fun close() { - stdinReader_free(ref) + ref?.let { ref -> + this.ref = null + + val error = stdinReader_free(ref) + if (error == 0U) return + Tty.throwError(error) + } } } @OptIn(ExperimentalForeignApi::class) internal actual class StdinWriter internal constructor( - private val ref: CPointer, + private var ref: CPointer?, readerRef: CPointer, ) : AutoCloseable { actual val reader: StdinReader = StdinReader(readerRef) @@ -86,10 +99,19 @@ internal actual class StdinWriter internal constructor( stdinWriter_write(ref, it.addressOf(0), buffer.size) } if (error == 0U) return - throw RuntimeException(error.toString()) + Tty.throwError(error) } actual override fun close() { - stdinWriter_free(ref) + ref?.let { ref -> + this.ref = null + + reader.close() + + val error = stdinWriter_free(ref) + + if (error == 0U) return + Tty.throwError(error) + } } }