From 00b3abe2438cdbf4bbb10842689e26619ae33f5c Mon Sep 17 00:00:00 2001 From: AJ Alt Date: Mon, 8 Jul 2024 18:49:39 -0700 Subject: [PATCH] Split out SyscallHandler.nativeimage.macos (#187) This is a follow on to #186 that fixes the `termios` struct definition on macos. --- CHANGELOG.md | 4 + .../ajalt/mordant/internal/MppInternal.jvm.kt | 6 +- ...kt => SyscallHandler.nativeimage.linux.kt} | 28 ++-- .../SyscallHandler.nativeimage.macos.kt | 129 ++++++++++++++++++ 4 files changed, 151 insertions(+), 16 deletions(-) rename mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/{SyscallHandler.nativeimage.posix.kt => SyscallHandler.nativeimage.linux.kt} (81%) create mode 100644 mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 02690bfde..18098c393 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Changelog ## Unreleased +## Added +- Added support for raw mode on GraalVM native Image. Contributed by @hubvd [(#186)](https://github.com/ajalt/mordant/issues/186) + ### Fixed - Fix markdown rendering not supporting math blocks [(#182)](https://github.com/ajalt/mordant/issues/182) - Fix exception thrown when using `readEvent` in raw mode when some windows terminals lose focus + ## 2.7.0 ### Added - Added raw mode support for reading keyboard and mouse events. See the docs at [https://ajalt.github.io/mordant/](https://ajalt.github.io/mordant/input/) for details. This feature is currently supported on all targets except JS, wasmJS, and Graal Native Image. diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt index 47fba7615..4a4b27a8c 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/MppInternal.jvm.kt @@ -5,7 +5,8 @@ import com.github.ajalt.mordant.internal.syscalls.SyscallHandler import com.github.ajalt.mordant.internal.syscalls.jna.SyscallHandlerJnaLinux import com.github.ajalt.mordant.internal.syscalls.jna.SyscallHandlerJnaMacos import com.github.ajalt.mordant.internal.syscalls.jna.SyscallHandlerJnaWindows -import com.github.ajalt.mordant.internal.syscalls.nativeimage.SyscallHandlerNativeImagePosix +import com.github.ajalt.mordant.internal.syscalls.nativeimage.SyscallHandlerNativeImageLinux +import com.github.ajalt.mordant.internal.syscalls.nativeimage.SyscallHandlerNativeImageMacos import com.github.ajalt.mordant.internal.syscalls.nativeimage.SyscallHandlerNativeImageWindows import com.github.ajalt.mordant.terminal.* import java.io.File @@ -130,7 +131,8 @@ internal actual fun getSyscallHandler(): SyscallHandler { val os = System.getProperty("os.name") when { isNativeImage && os.startsWith("Windows") -> SyscallHandlerNativeImageWindows() - isNativeImage && (os == "Linux" || os == "Mac OS X") -> SyscallHandlerNativeImagePosix() + isNativeImage && (os == "Linux") -> SyscallHandlerNativeImageLinux() + isNativeImage && (os == "Mac OS X") -> SyscallHandlerNativeImageMacos() os.startsWith("Windows") -> SyscallHandlerJnaWindows os == "Linux" -> SyscallHandlerJnaLinux os == "Mac OS X" -> SyscallHandlerJnaMacos diff --git a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.posix.kt b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt similarity index 81% rename from mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.posix.kt rename to mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt index e9a6f8d1e..f23524164 100644 --- a/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.posix.kt +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.linux.kt @@ -14,10 +14,10 @@ import org.graalvm.nativeimage.c.struct.CStruct import org.graalvm.nativeimage.c.type.CCharPointer import org.graalvm.word.PointerBase -@CContext(PosixLibC.Directives::class) -@Platforms(Platform.LINUX::class, Platform.MACOS::class) +@CContext(LinuxLibC.Directives::class) +@Platforms(Platform.LINUX::class) @Suppress("ClassName", "PropertyName", "SpellCheckingInspection", "FunctionName") -private object PosixLibC { +private object LinuxLibC { class Directives : CContext.Directives { override fun getHeaderFiles() = listOf("", "", "") @@ -89,13 +89,13 @@ private object PosixLibC { external fun tcsetattr(fd: Int, cmd: Int, termios: termios?): Int } -@Platforms(Platform.LINUX::class, Platform.MACOS::class) -internal class SyscallHandlerNativeImagePosix : SyscallHandlerJvmPosix() { - override fun isatty(fd: Int): Boolean = PosixLibC.isatty(fd) +@Platforms(Platform.LINUX::class) +internal class SyscallHandlerNativeImageLinux : SyscallHandlerJvmPosix() { + override fun isatty(fd: Int): Boolean = LinuxLibC.isatty(fd) override fun getTerminalSize(): Size? { - val size = StackValue.get(PosixLibC.winsize::class.java) - return if (PosixLibC.ioctl(0, PosixLibC.TIOCGWINSZ(), size) < 0) { + val size = StackValue.get(LinuxLibC.winsize::class.java) + return if (LinuxLibC.ioctl(0, LinuxLibC.TIOCGWINSZ(), size) < 0) { null } else { Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) @@ -103,8 +103,8 @@ internal class SyscallHandlerNativeImagePosix : SyscallHandlerJvmPosix() { } override fun getStdinTermios(): Termios { - val termios = StackValue.get(PosixLibC.termios::class.java) - if (PosixLibC.tcgetattr(STDIN_FILENO, termios) != 0) { + val termios = StackValue.get(LinuxLibC.termios::class.java) + if (LinuxLibC.tcgetattr(STDIN_FILENO, termios) != 0) { throw RuntimeException("Error reading terminal attributes") } return Termios( @@ -112,13 +112,13 @@ internal class SyscallHandlerNativeImagePosix : SyscallHandlerJvmPosix() { oflag = termios.c_oflag.toUInt(), cflag = termios.c_cflag.toUInt(), lflag = termios.c_lflag.toUInt(), - cc = ByteArray(PosixLibC.NCCS()) { termios.c_cc.read(it) }, + cc = ByteArray(LinuxLibC.NCCS()) { termios.c_cc.read(it) }, ) } override fun setStdinTermios(termios: Termios) { - val nativeTermios = StackValue.get(PosixLibC.termios::class.java) - if (PosixLibC.tcgetattr(STDIN_FILENO, nativeTermios) != 0) { + val nativeTermios = StackValue.get(LinuxLibC.termios::class.java) + if (LinuxLibC.tcgetattr(STDIN_FILENO, nativeTermios) != 0) { throw RuntimeException("Error reading terminal attributes") } nativeTermios.c_iflag = termios.iflag.toInt() @@ -126,7 +126,7 @@ internal class SyscallHandlerNativeImagePosix : SyscallHandlerJvmPosix() { nativeTermios.c_cflag = termios.cflag.toInt() nativeTermios.c_lflag = termios.lflag.toInt() termios.cc.forEachIndexed { i, b -> nativeTermios.c_cc.write(i, b) } - if (PosixLibC.tcsetattr(STDIN_FILENO, PosixLibC.TCSADRAIN(), nativeTermios) != 0) { + if (LinuxLibC.tcsetattr(STDIN_FILENO, LinuxLibC.TCSADRAIN(), nativeTermios) != 0) { throw RuntimeException("Error setting terminal attributes") } } 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 new file mode 100644 index 000000000..fbb8b8364 --- /dev/null +++ b/mordant/src/jvmMain/kotlin/com/github/ajalt/mordant/internal/syscalls/nativeimage/SyscallHandler.nativeimage.macos.kt @@ -0,0 +1,129 @@ +package com.github.ajalt.mordant.internal.syscalls.nativeimage + +import com.github.ajalt.mordant.internal.Size +import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerJvmPosix +import org.graalvm.nativeimage.Platform +import org.graalvm.nativeimage.Platforms +import org.graalvm.nativeimage.StackValue +import org.graalvm.nativeimage.c.CContext +import org.graalvm.nativeimage.c.constant.CConstant +import org.graalvm.nativeimage.c.function.CFunction +import org.graalvm.nativeimage.c.struct.CField +import org.graalvm.nativeimage.c.struct.CFieldAddress +import org.graalvm.nativeimage.c.struct.CStruct +import org.graalvm.nativeimage.c.type.CCharPointer +import org.graalvm.word.PointerBase + +@CContext(MacosLibC.Directives::class) +@Platforms(Platform.MACOS::class) +@Suppress("ClassName", "PropertyName", "SpellCheckingInspection", "FunctionName") +private object MacosLibC { + + class Directives : CContext.Directives { + override fun getHeaderFiles() = listOf("", "", "") + } + + @CConstant("TIOCGWINSZ") + external fun TIOCGWINSZ(): Int + + @CConstant("TCSADRAIN") + external fun TCSADRAIN(): Int + + @CConstant("NCCS") + external fun NCCS(): Int + + @CStruct("winsize", addStructKeyword = true) + interface winsize : PointerBase { + + @get:CField("ws_row") + val ws_row: Short + + @get:CField("ws_col") + val ws_col: Short + } + + @CStruct("termios", addStructKeyword = true) + interface termios : PointerBase { + @get:CField("c_iflag") + @set:CField("c_iflag") + var c_iflag: Long + + @get:CField("c_oflag") + @set:CField("c_oflag") + var c_oflag: Long + + @get:CField("c_cflag") + @set:CField("c_cflag") + var c_cflag: Long + + @get:CField("c_lflag") + @set:CField("c_lflag") + var c_lflag: Long + + @get:CFieldAddress("c_cc") + val c_cc: CCharPointer + + @get:CField("c_ispeed") + @set:CField("c_ispeed") + var c_ispeed: Long + + @get:CField("c_ospeed") + @set:CField("c_ospeed") + var c_ospeed: Long + } + + @CFunction("isatty") + external fun isatty(fd: Int): Boolean + + @CFunction("ioctl") + external fun ioctl(fd: Int, cmd: Int, winSize: winsize?): Int + + @CFunction("tcgetattr") + external fun tcgetattr(fd: Int, termios: termios?): Int + + @CFunction("tcsetattr") + external fun tcsetattr(fd: Int, cmd: Int, termios: termios?): Int +} + +@Platforms(Platform.MACOS::class) +internal class SyscallHandlerNativeImageMacos : SyscallHandlerJvmPosix() { + override fun isatty(fd: Int): Boolean = MacosLibC.isatty(fd) + + override fun getTerminalSize(): Size? { + val size = StackValue.get(MacosLibC.winsize::class.java) + return if (MacosLibC.ioctl(0, MacosLibC.TIOCGWINSZ(), size) < 0) { + null + } else { + Size(width = size.ws_col.toInt(), height = size.ws_row.toInt()) + } + } + + override fun getStdinTermios(): Termios { + val termios = StackValue.get(MacosLibC.termios::class.java) + if (MacosLibC.tcgetattr(STDIN_FILENO, termios) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + return Termios( + iflag = termios.c_iflag.toUInt(), + oflag = termios.c_oflag.toUInt(), + cflag = termios.c_cflag.toUInt(), + lflag = termios.c_lflag.toUInt(), + cc = ByteArray(MacosLibC.NCCS()) { termios.c_cc.read(it) }, + ) + } + + override fun setStdinTermios(termios: Termios) { + val nativeTermios = StackValue.get(MacosLibC.termios::class.java) + if (MacosLibC.tcgetattr(STDIN_FILENO, nativeTermios) != 0) { + throw RuntimeException("Error reading terminal attributes") + } + nativeTermios.c_iflag = termios.iflag.toLong() + nativeTermios.c_oflag = termios.oflag.toLong() + nativeTermios.c_cflag = termios.cflag.toLong() + nativeTermios.c_lflag = termios.lflag.toLong() + termios.cc.forEachIndexed { i, b -> nativeTermios.c_cc.write(i, b) } + if (MacosLibC.tcsetattr(STDIN_FILENO, MacosLibC.TCSADRAIN(), nativeTermios) != 0) { + throw RuntimeException("Error setting terminal attributes") + } + } +}