diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f8991e55..886fec156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +### Fixed +- Fixed raw mode on macos for JVM and native not using the correct termios constants. [(#180)](https://github.com/ajalt/mordant/issues/180) ## 2.7.1 ## Added diff --git a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.posix.kt b/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.apple.kt similarity index 88% rename from mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.posix.kt rename to mordant/src/appleMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.apple.kt index 0694c605c..914e457e6 100644 --- a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.posix.kt +++ b/mordant/src/appleMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.apple.kt @@ -1,6 +1,6 @@ package com.github.ajalt.mordant.internal import com.github.ajalt.mordant.internal.syscalls.SyscallHandler -import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerNativePosix +import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerNativeApple -internal actual fun getSyscallHandler(): SyscallHandler = SyscallHandlerNativePosix +internal actual fun getSyscallHandler(): SyscallHandler = SyscallHandlerNativeApple diff --git a/mordant/src/appleMain/kotlin/internal/syscalls/SyscallHanlder.native.apple.kt b/mordant/src/appleMain/kotlin/internal/syscalls/SyscallHanlder.native.apple.kt index b4eb1b3df..20897b1ea 100644 --- a/mordant/src/appleMain/kotlin/internal/syscalls/SyscallHanlder.native.apple.kt +++ b/mordant/src/appleMain/kotlin/internal/syscalls/SyscallHanlder.native.apple.kt @@ -4,45 +4,65 @@ import com.github.ajalt.mordant.internal.Size import kotlinx.cinterop.* import platform.posix.* -// The source code for this file is identical between linux and apple targets, but they have different -// bit widths for some the termios fields, so the compileMetadata task fails. +// The source code for this file is identical between linux and apple targets, but they have +// different bit widths for some of the termios fields, so the compileMetadata task would fail if we +// don't use separate files. -internal actual fun getTerminalSizeNative(): Size? = memScoped { - val size = alloc() - if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) { - null - } else { - Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) - } -} +internal object SyscallHandlerNativeApple : SyscallHandlerNativePosix() { + override val termiosConstants: TermiosConstants = TermiosConstants( + VTIME = VTIME, + VMIN = VMIN, + INPCK = INPCK.convert(), + ISTRIP = ISTRIP.convert(), + INLCR = INLCR.convert(), + IGNCR = IGNCR.convert(), + ICRNL = ICRNL.convert(), + IXON = IXON.convert(), + OPOST = OPOST.convert(), + CS8 = CS8.convert(), + ISIG = ISIG.convert(), + ICANON = ICANON.convert(), + ECHO = ECHO.convert(), + IEXTEN = IEXTEN.convert(), + ) -internal actual fun getStdinTermiosNative(): SyscallHandlerPosix.Termios = memScoped { - val termios = alloc() - if (tcgetattr(STDIN_FILENO, termios.ptr) != 0) { - throw RuntimeException("Error reading terminal attributes") + override fun getTerminalSize(): Size? = memScoped { + val size = alloc() + if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) { + null + } else { + Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) + } } - return SyscallHandlerPosix.Termios( - iflag = termios.c_iflag.convert(), - oflag = termios.c_oflag.convert(), - cflag = termios.c_cflag.convert(), - lflag = termios.c_lflag.convert(), - cc = ByteArray(NCCS) { termios.c_cc[it].convert() }, - ) -} -internal actual fun setStdinTermiosNative(termios: SyscallHandlerPosix.Termios): Unit = memScoped { - val nativeTermios = alloc() - // different platforms have different fields in termios, so we need to read the current - // struct before we set the fields we care about. - if (tcgetattr(STDIN_FILENO, nativeTermios.ptr) != 0) { - throw RuntimeException("Error reading terminal attributes") + override fun getStdinTermios(): Termios = memScoped { + val termios = alloc() + if (tcgetattr(STDIN_FILENO, termios.ptr) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + return Termios( + iflag = termios.c_iflag.convert(), + oflag = termios.c_oflag.convert(), + cflag = termios.c_cflag.convert(), + lflag = termios.c_lflag.convert(), + cc = ByteArray(NCCS) { termios.c_cc[it].convert() }, + ) } - nativeTermios.c_iflag = termios.iflag.convert() - nativeTermios.c_oflag = termios.oflag.convert() - nativeTermios.c_cflag = termios.cflag.convert() - nativeTermios.c_lflag = termios.lflag.convert() - repeat(NCCS) { nativeTermios.c_cc[it] = termios.cc[it].convert() } - if (tcsetattr(STDIN_FILENO, TCSADRAIN, nativeTermios.ptr) != 0) { - throw RuntimeException("Error setting terminal attributes") + + override fun setStdinTermios(termios: Termios) = memScoped { + val nativeTermios = alloc() + // different platforms have different fields in termios, so we need to read the current + // struct before we set the fields we care about. + if (tcgetattr(STDIN_FILENO, nativeTermios.ptr) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + nativeTermios.c_iflag = termios.iflag.convert() + nativeTermios.c_oflag = termios.oflag.convert() + nativeTermios.c_cflag = termios.cflag.convert() + nativeTermios.c_lflag = termios.lflag.convert() + repeat(NCCS) { nativeTermios.c_cc[it] = termios.cc[it].convert() } + if (tcsetattr(STDIN_FILENO, TCSADRAIN, nativeTermios.ptr) != 0) { + throw RuntimeException("Error setting terminal attributes") + } } } diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.linux.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.linux.kt index 2d3997dd5..87402288d 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.linux.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.linux.kt @@ -2,6 +2,7 @@ package com.github.ajalt.mordant.internal.syscalls.jna import com.github.ajalt.mordant.internal.Size import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerJvmPosix +import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerPosix import com.oracle.svm.core.annotate.Delete import com.sun.jna.* @@ -69,6 +70,7 @@ private interface PosixLibC : Library { internal object SyscallHandlerJnaLinux : SyscallHandlerJvmPosix() { private const val TIOCGWINSZ = 0x00005413 private const val TCSADRAIN: Int = 0x1 + override val termiosConstants: TermiosConstants get() = LinuxTermiosConstants private val libC: PosixLibC = Native.load(Platform.C_LIBRARY_NAME, PosixLibC::class.java) override fun isatty(fd: Int): Boolean = libC.isatty(fd) != 0 diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.macos.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.macos.kt index a872ad489..afe5fe046 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.macos.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/jna/SyscallHandler.jna.macos.kt @@ -69,6 +69,7 @@ private interface MacosLibC : Library { @Delete @Suppress("SpellCheckingInspection") internal object SyscallHandlerJnaMacos : SyscallHandlerJvmPosix() { + override val termiosConstants: TermiosConstants get() = MacosTermiosConstants private const val TCSANOW: Int = 0x0 private val TIOCGWINSZ = when { Platform.isMIPS() || Platform.isPPC() || Platform.isSPARC() -> 0x40087468L @@ -104,12 +105,11 @@ internal object SyscallHandlerJnaMacos : SyscallHandlerJvmPosix() { override fun setStdinTermios(termios: Termios) { val nativeTermios = MacosLibC.termios() libC.tcgetattr(STDIN_FILENO, nativeTermios) - nativeTermios.c_iflag = NativeLong(termios.iflag.toLong()) - nativeTermios.c_oflag = NativeLong(termios.oflag.toLong()) - nativeTermios.c_cflag = NativeLong(termios.cflag.toLong()) - nativeTermios.c_lflag = NativeLong(termios.lflag.toLong()) + nativeTermios.c_iflag.setValue(termios.iflag.toLong()) + nativeTermios.c_oflag.setValue(termios.oflag.toLong()) + nativeTermios.c_cflag.setValue(termios.cflag.toLong()) + nativeTermios.c_lflag.setValue(termios.lflag.toLong()) termios.cc.copyInto(nativeTermios.c_cc) - libC.tcsetattr(STDIN_FILENO, TCSANOW, nativeTermios) } } diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt index f23524164..927deeed7 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt @@ -91,6 +91,7 @@ private object LinuxLibC { @Platforms(Platform.LINUX::class) internal class SyscallHandlerNativeImageLinux : SyscallHandlerJvmPosix() { + override val termiosConstants: TermiosConstants get() = LinuxTermiosConstants override fun isatty(fd: Int): Boolean = LinuxLibC.isatty(fd) override fun getTerminalSize(): Size? { diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt index fbb8b8364..35005b7fb 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt @@ -87,6 +87,7 @@ private object MacosLibC { @Platforms(Platform.MACOS::class) internal class SyscallHandlerNativeImageMacos : SyscallHandlerJvmPosix() { + override val termiosConstants: TermiosConstants get() = MacosTermiosConstants override fun isatty(fd: Int): Boolean = MacosLibC.isatty(fd) override fun getTerminalSize(): Size? { diff --git a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.linux.kt b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.linux.kt index 50ea57066..db32c0c15 100644 --- a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.linux.kt +++ b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.linux.kt @@ -1,3 +1,7 @@ package com.github.ajalt.mordant.internal +import com.github.ajalt.mordant.internal.syscalls.SyscallHandler +import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerNativeLinux + internal actual fun hasFileSystem(): Boolean = true +internal actual fun getSyscallHandler(): SyscallHandler = SyscallHandlerNativeLinux diff --git a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.linux.kt b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.linux.kt index b4eb1b3df..d7d9b1915 100644 --- a/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.linux.kt +++ b/mordant/src/linuxMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.linux.kt @@ -4,45 +4,65 @@ import com.github.ajalt.mordant.internal.Size import kotlinx.cinterop.* import platform.posix.* -// The source code for this file is identical between linux and apple targets, but they have different -// bit widths for some the termios fields, so the compileMetadata task fails. +// The source code for this file is identical between linux and apple targets, but they have +// different bit widths for some of the termios fields, so the compileMetadata task would fail if we +// don't use separate files. -internal actual fun getTerminalSizeNative(): Size? = memScoped { - val size = alloc() - if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) { - null - } else { - Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) - } -} +internal object SyscallHandlerNativeLinux : SyscallHandlerNativePosix() { + override val termiosConstants: TermiosConstants = TermiosConstants( + VTIME = VTIME, + VMIN = VMIN, + INPCK = INPCK.convert(), + ISTRIP = ISTRIP.convert(), + INLCR = INLCR.convert(), + IGNCR = IGNCR.convert(), + ICRNL = ICRNL.convert(), + IXON = IXON.convert(), + OPOST = OPOST.convert(), + CS8 = CS8.convert(), + ISIG = ISIG.convert(), + ICANON = ICANON.convert(), + ECHO = ECHO.convert(), + IEXTEN = IEXTEN.convert(), + ) -internal actual fun getStdinTermiosNative(): SyscallHandlerPosix.Termios = memScoped { - val termios = alloc() - if (tcgetattr(STDIN_FILENO, termios.ptr) != 0) { - throw RuntimeException("Error reading terminal attributes") + override fun getTerminalSize(): Size? = memScoped { + val size = alloc() + if (ioctl(STDIN_FILENO, TIOCGWINSZ.toULong(), size) < 0) { + null + } else { + Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) + } } - return SyscallHandlerPosix.Termios( - iflag = termios.c_iflag.convert(), - oflag = termios.c_oflag.convert(), - cflag = termios.c_cflag.convert(), - lflag = termios.c_lflag.convert(), - cc = ByteArray(NCCS) { termios.c_cc[it].convert() }, - ) -} -internal actual fun setStdinTermiosNative(termios: SyscallHandlerPosix.Termios): Unit = memScoped { - val nativeTermios = alloc() - // different platforms have different fields in termios, so we need to read the current - // struct before we set the fields we care about. - if (tcgetattr(STDIN_FILENO, nativeTermios.ptr) != 0) { - throw RuntimeException("Error reading terminal attributes") + override fun getStdinTermios(): Termios = memScoped { + val termios = alloc() + if (tcgetattr(STDIN_FILENO, termios.ptr) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + return Termios( + iflag = termios.c_iflag.convert(), + oflag = termios.c_oflag.convert(), + cflag = termios.c_cflag.convert(), + lflag = termios.c_lflag.convert(), + cc = ByteArray(NCCS) { termios.c_cc[it].convert() }, + ) } - nativeTermios.c_iflag = termios.iflag.convert() - nativeTermios.c_oflag = termios.oflag.convert() - nativeTermios.c_cflag = termios.cflag.convert() - nativeTermios.c_lflag = termios.lflag.convert() - repeat(NCCS) { nativeTermios.c_cc[it] = termios.cc[it].convert() } - if (tcsetattr(STDIN_FILENO, TCSADRAIN, nativeTermios.ptr) != 0) { - throw RuntimeException("Error setting terminal attributes") + + override fun setStdinTermios(termios: Termios) = memScoped { + val nativeTermios = alloc() + // different platforms have different fields in termios, so we need to read the current + // struct before we set the fields we care about. + if (tcgetattr(STDIN_FILENO, nativeTermios.ptr) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + nativeTermios.c_iflag = termios.iflag.convert() + nativeTermios.c_oflag = termios.oflag.convert() + nativeTermios.c_cflag = termios.cflag.convert() + nativeTermios.c_lflag = termios.lflag.convert() + repeat(NCCS) { nativeTermios.c_cc[it] = termios.cc[it].convert() } + if (tcsetattr(STDIN_FILENO, TCSADRAIN, nativeTermios.ptr) != 0) { + throw RuntimeException("Error setting terminal attributes") + } } } diff --git a/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.native.kt b/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.native.kt index 98567f997..129c88fc8 100644 --- a/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.native.kt +++ b/mordant/src/nativeMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.native.kt @@ -65,11 +65,15 @@ internal actual fun printStderr(message: String, newline: Boolean) { internal expect fun ttySetEcho(echo: Boolean) internal actual fun readLineOrNullMpp(hideInput: Boolean): String? { - if (hideInput) ttySetEcho(false) - try { - return readlnOrNull() - } finally { - if (hideInput) ttySetEcho(true) + return try { + if (hideInput) ttySetEcho(false) + try { + readlnOrNull() + } finally { + if (hideInput) ttySetEcho(true) + } + } catch (e: Throwable) { + null } } diff --git a/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt b/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt index 6120fad71..d9c1597fb 100644 --- a/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt +++ b/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/input/InteractiveSelectList.kt @@ -15,7 +15,7 @@ inline fun Terminal.interactiveSelectList( return InteractiveSelectListBuilder(this) .apply(block) .createSingleSelectInputAnimation() - .receiveEvents() + .receiveEvents(MouseTracking.Off) } /** @@ -65,7 +65,7 @@ inline fun Terminal.interactiveMultiSelectList( return InteractiveSelectListBuilder(this) .apply(block) .createMultiSelectInputAnimation() - .receiveEvents() + .receiveEvents(MouseTracking.Off) } /** diff --git a/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHandler.posix.kt b/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHandler.posix.kt index 19e518c77..d2b5f6d3f 100644 --- a/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHandler.posix.kt +++ b/mordant/src/nonJsMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHandler.posix.kt @@ -18,123 +18,39 @@ internal abstract class SyscallHandlerPosix : SyscallHandler { private const val ESC = '\u001b' - private const val VINTR: Int = 0 - private const val VQUIT: Int = 1 - private const val VERASE: Int = 2 - private const val VKILL: Int = 3 - private const val VEOF: Int = 4 - private const val VTIME: Int = 5 - private const val VMIN: Int = 6 - private const val VSWTC: Int = 7 - private const val VSTART: Int = 8 - private const val VSTOP: Int = 9 - private const val VSUSP: Int = 10 - private const val VEOL: Int = 11 - private const val VREPRINT: Int = 12 - private const val VDISCARD: Int = 13 - private const val VWERASE: Int = 14 - private const val VLNEXT: Int = 15 - private const val VEOL2: Int = 16 - - private const val IGNBRK: UInt = 0x0000001u - private const val BRKINT: UInt = 0x0000002u - private const val IGNPAR: UInt = 0x0000004u - private const val PARMRK: UInt = 0x0000008u - private const val INPCK: UInt = 0x0000010u - private const val ISTRIP: UInt = 0x0000020u - private const val INLCR: UInt = 0x0000040u - private const val IGNCR: UInt = 0x0000080u - private const val ICRNL: UInt = 0x0000100u - private const val IUCLC: UInt = 0x0000200u - private const val IXON: UInt = 0x0000400u - private const val IXANY: UInt = 0x0000800u - private const val IXOFF: UInt = 0x0001000u - private const val IMAXBEL: UInt = 0x0002000u - private const val IUTF8: UInt = 0x0004000u - - private const val OPOST: UInt = 0x0000001u - private const val OLCUC: UInt = 0x0000002u - private const val ONLCR: UInt = 0x0000004u - private const val OCRNL: UInt = 0x0000008u - private const val ONOCR: UInt = 0x0000010u - private const val ONLRET: UInt = 0x0000020u - private const val OFILL: UInt = 0x0000040u - private const val OFDEL: UInt = 0x0000080u - private const val NLDLY: UInt = 0x0000100u - private const val NL0: UInt = 0x0000000u - private const val NL1: UInt = 0x0000100u - private const val CRDLY: UInt = 0x0000600u - private const val CR0: UInt = 0x0000000u - private const val CR1: UInt = 0x0000200u - private const val CR2: UInt = 0x0000400u - private const val CR3: UInt = 0x0000600u - private const val TABDLY: UInt = 0x0001800u - private const val TAB0: UInt = 0x0000000u - private const val TAB1: UInt = 0x0000800u - private const val TAB2: UInt = 0x0001000u - private const val TAB3: UInt = 0x0001800u - private const val XTABS: UInt = 0x0001800u - private const val BSDLY: UInt = 0x0002000u - private const val BS0: UInt = 0x0000000u - private const val BS1: UInt = 0x0002000u - private const val VTDLY: UInt = 0x0004000u - private const val VT0: UInt = 0x0000000u - private const val VT1: UInt = 0x0004000u - private const val FFDLY: UInt = 0x0008000u - private const val FF0: UInt = 0x0000000u - private const val FF1: UInt = 0x0008000u - - private const val CBAUD: UInt = 0x000100fu - private const val B0: UInt = 0x0000000u - private const val B50: UInt = 0x0000001u - private const val B75: UInt = 0x0000002u - private const val B110: UInt = 0x0000003u - private const val B134: UInt = 0x0000004u - private const val B150: UInt = 0x0000005u - private const val B200: UInt = 0x0000006u - private const val B300: UInt = 0x0000007u - private const val B600: UInt = 0x0000008u - private const val B1200: UInt = 0x0000009u - private const val B1800: UInt = 0x000000au - private const val B2400: UInt = 0x000000bu - private const val B4800: UInt = 0x000000cu - private const val B9600: UInt = 0x000000du - private const val B19200: UInt = 0x000000eu - private const val B38400: UInt = 0x000000fu - private const val EXTA: UInt = B19200 - private const val EXTB: UInt = B38400 - private const val CSIZE: UInt = 0x0000030u - private const val CS5: UInt = 0x0000000u - private const val CS6: UInt = 0x0000010u - private const val CS7: UInt = 0x0000020u - private const val CS8: UInt = 0x0000030u - private const val CSTOPB: UInt = 0x0000040u - private const val CREAD: UInt = 0x0000080u - private const val PARENB: UInt = 0x0000100u - private const val PARODD: UInt = 0x0000200u - private const val HUPCL: UInt = 0x0000400u - private const val CLOCAL: UInt = 0x0000800u - - private const val ISIG: UInt = 0x0000001u - private const val ICANON: UInt = 0x0000002u - private const val XCASE: UInt = 0x0000004u - private const val ECHO: UInt = 0x0000008u - private const val ECHOE: UInt = 0x0000010u - private const val ECHOK: UInt = 0x0000020u - private const val ECHONL: UInt = 0x0000040u - private const val NOFLSH: UInt = 0x0000080u - private const val TOSTOP: UInt = 0x0000100u - private const val ECHOCTL: UInt = 0x0000200u - private const val ECHOPRT: UInt = 0x0000400u - private const val ECHOKE: UInt = 0x0000800u - private const val FLUSHO: UInt = 0x0001000u - private const val PENDIN: UInt = 0x0002000u - private const val IEXTEN: UInt = 0x0008000u - private const val EXTPROC: UInt = 0x0010000u - - private const val TCSANOW: UInt = 0x0u - private const val TCSADRAIN: UInt = 0x1u - private const val TCSAFLUSH: UInt = 0x2u + val MacosTermiosConstants = TermiosConstants( + VTIME = 17, + VMIN = 16, + INPCK = 0x00000010u, + ISTRIP = 0x00000020u, + INLCR = 0x00000040u, + IGNCR = 0x00000080u, + ICRNL = 0x00000100u, + IXON = 0x00000200u, + OPOST = 0x00000001u, + CS8 = 0x00000300u, + ISIG = 0x00000080u, + ICANON = 0x00000100u, + ECHO = 0x00000008u, + IEXTEN = 0x00000400u, + ) + + val LinuxTermiosConstants = TermiosConstants( + VTIME = 5, + VMIN = 6, + INPCK = 0x0000010u, + ISTRIP = 0x0000020u, + INLCR = 0x0000040u, + IGNCR = 0x0000080u, + ICRNL = 0x0000100u, + IXON = 0x0000400u, + OPOST = 0x0000001u, + CS8 = 0x0000030u, + ISIG = 0x0000001u, + ICANON = 0x0000002u, + ECHO = 0x0000008u, + IEXTEN = 0x0008000u, + ) } @Suppress("ArrayInDataClass") @@ -146,8 +62,33 @@ internal abstract class SyscallHandlerPosix : SyscallHandler { val cc: ByteArray, ) + /** + * Constants for termios flags and control characters. + * + * The values for these are platform-specific. + * https://www.man7.org/linux/man-pages/man3/termios.3.html + */ + data class TermiosConstants( + val VTIME: Int, + val VMIN: Int, + val INPCK: UInt, + val ISTRIP: UInt, + val INLCR: UInt, + val IGNCR: UInt, + val ICRNL: UInt, + val IXON: UInt, + val OPOST: UInt, + val CS8: UInt, + val ISIG: UInt, + val ICANON: UInt, + val ECHO: UInt, + val IEXTEN: UInt, + ) + + abstract fun getStdinTermios(): Termios abstract fun setStdinTermios(termios: Termios) + abstract val termiosConstants: TermiosConstants protected abstract fun isatty(fd: Int): Boolean protected abstract fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char @@ -155,20 +96,20 @@ internal abstract class SyscallHandlerPosix : SyscallHandler { override fun stdinInteractive(): Boolean = isatty(STDIN_FILENO) override fun stderrInteractive(): Boolean = isatty(STDERR_FILENO) - // https://www.man7.org/linux/man-pages/man3/termios.3.html // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking override fun enterRawMode(mouseTracking: MouseTracking): AutoCloseable { val orig = getStdinTermios() + val c = termiosConstants val new = Termios( - iflag = orig.iflag and (ICRNL or IGNCR or INPCK or ISTRIP or IXON).inv(), + iflag = orig.iflag and (c.ICRNL or c.IGNCR or c.INPCK or c.ISTRIP or c.IXON).inv(), // we leave OPOST on so we don't change \r\n handling oflag = orig.oflag, - cflag = orig.cflag or CS8, - lflag = orig.lflag and (ECHO or ICANON or IEXTEN or ISIG).inv(), + cflag = orig.cflag or c.CS8, + lflag = orig.lflag and (c.ECHO or c.ICANON or c.IEXTEN or c.ISIG).inv(), cc = orig.cc.copyOf().also { - it[VMIN] = 0 // min wait time on read - it[VTIME] = 1 // max wait time on read, in 10ths of a second + it[c.VMIN] = 0 // min wait time on read + it[c.VTIME] = 1 // max wait time on read, in 10ths of a second }, ) setStdinTermios(new) diff --git a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.posix.kt b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.posix.kt index 59ab9380a..a9efc4b42 100644 --- a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.posix.kt +++ b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/syscalls/SyscallHanlder.native.posix.kt @@ -1,27 +1,15 @@ package com.github.ajalt.mordant.internal.syscalls -import com.github.ajalt.mordant.internal.Size -import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerPosix.Termios import kotlinx.cinterop.* import platform.posix.read import kotlin.time.ComparableTimeMark import kotlin.time.Duration -internal expect fun getTerminalSizeNative(): Size? -internal expect fun getStdinTermiosNative(): Termios -internal expect fun setStdinTermiosNative(termios: Termios) - -internal object SyscallHandlerNativePosix : SyscallHandlerPosix() { +internal abstract class SyscallHandlerNativePosix : SyscallHandlerPosix() { override fun isatty(fd: Int): Boolean { return platform.posix.isatty(fd) != 0 } - override fun getTerminalSize(): Size? = getTerminalSizeNative() - - override fun getStdinTermios(): Termios = getStdinTermiosNative() - - override fun setStdinTermios(termios: Termios): Unit = setStdinTermiosNative(termios) - override fun readRawByte(t0: ComparableTimeMark, timeout: Duration): Char = memScoped { do { val c = alloc() diff --git a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/tty.kt b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/tty.kt index d395ab56b..d6805ed75 100644 --- a/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/tty.kt +++ b/mordant/src/posixMain/kotlin/com/github/ajalt/mordant/internal/tty.kt @@ -1,13 +1,13 @@ package com.github.ajalt.mordant.internal -import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerNativePosix -import kotlinx.cinterop.UnsafeNumber +import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerPosix import platform.posix.ECHO // https://www.gnu.org/software/libc/manual/html_node/getpass.html internal actual fun ttySetEcho(echo: Boolean) { - val termios = SyscallHandlerNativePosix.getStdinTermios() ?: return - SyscallHandlerNativePosix.setStdinTermios( + val handlerPosix = SYSCALL_HANDLER as SyscallHandlerPosix + val termios = handlerPosix.getStdinTermios() + handlerPosix.setStdinTermios( termios.copy( lflag = when { echo -> termios.lflag or ECHO.toUInt()