Skip to content

Commit

Permalink
Add omnibus module
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt committed Aug 11, 2024
1 parent bf4f8f5 commit 84eb06b
Show file tree
Hide file tree
Showing 23 changed files with 101 additions and 51 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ jobs:
- uses: actions/checkout@v4
- uses: graalvm/setup-graalvm@v1
with:
java-version: 21
java-version: 22
distribution: 'graalvm-community'
set-java-home: false
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
java-version: 22
- uses: gradle/actions/setup-gradle@v3
- name: Run tests
run: >-
Expand Down Expand Up @@ -62,7 +62,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
java-version: 22
- uses: gradle/actions/setup-gradle@v3
- name: Deploy to sonatype
# disable configuration cache due to https://github.com/gradle/gradle/issues/22779
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 21
java-version: 22
- uses: actions/setup-python@v5
with:
python-version: '3.12'
Expand Down
18 changes: 11 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Changelog

## Unreleased
## Added
- New `TerminalDetection` object that can be used to detect terminal capabilities without creating a terminal instance.
- Added new optional methods to `TerminalInterface` to control raw mode: `getTerminalSize`, `readInputEvent`, `enterRawMode`, and `shouldAutoUpdateSize`.
- Added new terminal implementation that uses the [Foreign Function and Memory (FFM) API](https://openjdk.java.net/jeps/419) added in JDK 22.
- Split the library up into modules, so you can produce smaller JVM artifacts by only using the parts you need.

### Changed
- TODO:
- size moved to terminal
- Only one terminal constructor now
- public TerminalDetection
- TerminalInterface
- info -> info()
- added getTerminalSize etc.
- **Breaking Change** Moved `Terminal.info.width` and `height` to `Terminal.size.width` and `height`.
- **Breaking Change** `TerminalInterface.info` is now a method with parameters instead of a property.

### Removed
- Removed constructor overloads for `Terminal`. There is now one constructor with all default parameters.

## 2.7.2
### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ kotlin {

sourceSets {
jvmMain.dependencies {
implementation(project(":mordant"))
implementation(project(":mordant-omnibus"))
}
}
}

application {
mainClass.set("com.github.ajalt.mordant.samples.MainKt")
applicationDefaultJvmArgs = listOf("-Dfile.encoding=utf-8")
}

tasks.jar {
manifest {
attributes["Enable-Native-Access"] = "ALL-UNNAMED"
}
applicationDefaultJvmArgs = listOf(
"-Dfile.encoding=utf-8",
"--enable-native-access=ALL-UNNAMED"
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ kotlin {
commonMain.dependencies {
implementation(project(":mordant"))
}
jvmMain.dependencies {
implementation(project(":mordant-omnibus"))
}
}
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[versions]
kotlin = "2.0.0"
kotlin = "2.0.10"
coroutines = "1.8.1"

[libraries]
colormath = "com.github.ajalt.colormath:colormath:3.5.0"
markdown = "org.jetbrains:markdown:0.7.3"
jna-core = "net.java.dev.jna:jna:5.14.0"
jna-core = "net.java.dev.jna:jna:5.15.0"

# compileOnly
graalvm-svm = "org.graalvm.nativeimage:svm:23.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.lang.foreign.Arena
import java.lang.foreign.MemorySegment

@Suppress("ClassName", "PropertyName", "SpellCheckingInspection")
private class PosixLibC {
private class LinuxCLibrary {
class winsize(override val segment: MemorySegment) : StructAccessor {
object Layout : StructLayout() {
val ws_row by shortField()
Expand All @@ -30,6 +30,7 @@ private class PosixLibC {
val c_lflag by intField()
val c_line by byteField()
val c_cc by arrayField(32)
@Suppress("unused")
val padding by paddingField(3)
val c_ispeed by intField()
val c_ospeed by intField()
Expand Down Expand Up @@ -79,11 +80,11 @@ internal class TerminalInterfaceFfmLinux : TerminalInterfaceJvmPosix() {
const val TCSADRAIN: Int = 0x1
}

private val libC = PosixLibC()
private val libC = LinuxCLibrary()
override fun isatty(fd: Int): Boolean = libC.isatty(fd)

override fun getTerminalSize(): Size? = Arena.ofConfined().use { arena ->
val size = PosixLibC.winsize(arena)
val size = LinuxCLibrary.winsize(arena)
if (!libC.ioctl(STDIN_FILENO, TIOCGWINSZ, size.segment)) {
null
} else {
Expand All @@ -92,7 +93,7 @@ internal class TerminalInterfaceFfmLinux : TerminalInterfaceJvmPosix() {
}

override fun getStdinTermios(): Termios = Arena.ofConfined().use { arena ->
val termios = PosixLibC.termios(arena)
val termios = LinuxCLibrary.termios(arena)
if (!libC.tcgetattr(STDIN_FILENO, termios)) {
throw RuntimeException("failed to read terminal settings")
}
Expand All @@ -106,7 +107,7 @@ internal class TerminalInterfaceFfmLinux : TerminalInterfaceJvmPosix() {
}

override fun setStdinTermios(termios: Termios): Unit = Arena.ofConfined().use { arena ->
val nativeTermios = PosixLibC.termios(arena)
val nativeTermios = LinuxCLibrary.termios(arena)
if (!libC.tcgetattr(STDIN_FILENO, nativeTermios)) {
throw RuntimeException("failed to update terminal settings")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.lang.foreign.Arena
import java.lang.foreign.MemorySegment

@Suppress("ClassName", "PropertyName", "SpellCheckingInspection")
private class MacosLibC {
private class MacosCLibrary {
class winsize(override val segment: MemorySegment) : StructAccessor {
object Layout : StructLayout() {
val ws_row by shortField()
Expand Down Expand Up @@ -72,11 +72,11 @@ internal class TerminalInterfaceFfmMacos : TerminalInterfaceJvmPosix() {
const val TCSADRAIN: Int = 0x1
}

private val libC = MacosLibC()
private val libC = MacosCLibrary()
override fun isatty(fd: Int): Boolean = libC.isatty(fd)

override fun getTerminalSize(): Size? = Arena.ofConfined().use { arena ->
val size = MacosLibC.winsize(arena)
val size = MacosCLibrary.winsize(arena)
if (!libC.ioctl(STDIN_FILENO, TIOCGWINSZ, size.segment)) {
null
} else {
Expand All @@ -85,7 +85,7 @@ internal class TerminalInterfaceFfmMacos : TerminalInterfaceJvmPosix() {
}

override fun getStdinTermios(): Termios = Arena.ofConfined().use { arena ->
val termios = MacosLibC.termios(arena)
val termios = MacosCLibrary.termios(arena)
if (!libC.tcgetattr(STDIN_FILENO, termios)) {
throw RuntimeException("failed to read terminal settings")
}
Expand All @@ -99,7 +99,7 @@ internal class TerminalInterfaceFfmMacos : TerminalInterfaceJvmPosix() {
}

override fun setStdinTermios(termios: Termios): Unit = Arena.ofConfined().use { arena ->
val nativeTermios = MacosLibC.termios(arena)
val nativeTermios = MacosCLibrary.termios(arena)
if (!libC.tcgetattr(STDIN_FILENO, nativeTermios)) {
throw RuntimeException("failed to update terminal settings")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal object WinLayouts {


@Suppress("FunctionName", "ClassName", "unused", "PropertyName")
private class WinKernel32Lib {
private class WinKernel32Library {

class COORD(override val segment: MemorySegment) : StructAccessor {
object Layout : StructLayout() {
Expand Down Expand Up @@ -242,7 +242,7 @@ internal class TerminalInterfaceFfmWindows : TerminalInterfaceWindows() {
const val STD_ERROR_HANDLE: Int = -12
}

private val kernel = WinKernel32Lib()
private val kernel = WinKernel32Library()
private val stdoutHandle get() = kernel.GetStdHandle(STD_OUTPUT_HANDLE)
private val stdinHandle get() = kernel.GetStdHandle(STD_INPUT_HANDLE)
private val stderrHandle get() = kernel.GetStdHandle(STD_ERROR_HANDLE)
Expand All @@ -256,7 +256,7 @@ internal class TerminalInterfaceFfmWindows : TerminalInterfaceWindows() {
override fun stdinInteractive(): Boolean = handleInteractive(stdinHandle)

override fun getTerminalSize(): Size? = Arena.ofConfined().use { arena ->
val csbi = WinKernel32Lib.CONSOLE_SCREEN_BUFFER_INFO(arena)
val csbi = WinKernel32Library.CONSOLE_SCREEN_BUFFER_INFO(arena)
if (!kernel.GetConsoleScreenBufferInfo(stdoutHandle, csbi)) {
return null
}
Expand All @@ -280,18 +280,18 @@ internal class TerminalInterfaceFfmWindows : TerminalInterfaceWindows() {
if (waitResult != 0) {
throw RuntimeException("Timeout reading from console input")
}
val inputEvents = arena.allocate(WinKernel32Lib.INPUT_RECORD.Layout.layout, 1)
val inputEvents = arena.allocate(WinKernel32Library.INPUT_RECORD.Layout.layout, 1)
val eventsReadSeg = arena.allocateInt()
if (!kernel.ReadConsoleInputW(stdin, inputEvents, 1, eventsReadSeg)
|| eventsReadSeg.get(Layouts.INT, 0) != 1
) {
throw RuntimeException("Error reading from console input")
}
val inputEvent = inputEvents.elements(WinKernel32Lib.INPUT_RECORD.Layout.layout)
.map(WinKernel32Lib::INPUT_RECORD).toList().single()
val inputEvent = inputEvents.elements(WinKernel32Library.INPUT_RECORD.Layout.layout)
.map(WinKernel32Library::INPUT_RECORD).toList().single()

return when (inputEvent.EventType) {
WinKernel32Lib.INPUT_RECORD.KEY_EVENT -> {
WinKernel32Library.INPUT_RECORD.KEY_EVENT -> {
val keyEvent = inputEvent.keyEvent
EventRecord.Key(
bKeyDown = keyEvent.bKeyDown,
Expand All @@ -301,7 +301,7 @@ internal class TerminalInterfaceFfmWindows : TerminalInterfaceWindows() {
)
}

WinKernel32Lib.INPUT_RECORD.MOUSE_EVENT -> {
WinKernel32Library.INPUT_RECORD.MOUSE_EVENT -> {
val mouseEvent = inputEvent.mouseEvent
EventRecord.Mouse(
dwMousePositionX = mouseEvent.dwMousePosition.X,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import com.github.ajalt.mordant.terminal.TerminalInterfaceProvider

class TerminalInterfaceProviderNativeImage : TerminalInterfaceProvider {
override fun load(): TerminalInterface? {
// Inlined version of ImageInfo.inImageCode()
val imageCode = System.getProperty("org.graalvm.nativeimage.imagecode")
val isNativeImage = imageCode == "buildtime" || imageCode == "runtime"
if (!isNativeImage) return null

val os = System.getProperty("os.name")
return when {
os.startsWith("Windows") -> TerminalInterfaceNativeImageWindows()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ internal class TerminalInterfaceJnaMacos : TerminalInterfaceJvmPosix() {
termios.cc.copyInto(nativeTermios.c_cc)
libC.tcsetattr(STDIN_FILENO, TCSANOW, nativeTermios)
}

override fun shouldAutoUpdateSize(): Boolean {
return false // Shelling out to STTY is slow, so don't do it automatically
}
}

@Suppress("SameParameterValue")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import com.github.ajalt.mordant.terminal.TerminalInterfaceProvider

class TerminalInterfaceProviderJna : TerminalInterfaceProvider {
override fun load(): TerminalInterface? {
// Inlined version of ImageInfo.inImageCode()
val imageCode = System.getProperty("org.graalvm.nativeimage.imagecode")
val isNativeImage = imageCode == "buildtime" || imageCode == "runtime"
if (isNativeImage) return null

val os = System.getProperty("os.name")
return try {
when {
Expand Down
18 changes: 18 additions & 0 deletions mordant-omnibus/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id("mordant-kotlin-conventions")
id("mordant-publishing-conventions")
}

kotlin {
jvm()
sourceSets {
commonMain.dependencies {
api(project(":mordant"))
}
jvmMain.dependencies {
implementation(project(":mordant-jvm-jna"))
implementation(project(":mordant-jvm-ffm"))
implementation(project(":mordant-jvm-graal-ffi"))
}
}
}
2 changes: 2 additions & 0 deletions mordant-omnibus/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
POM_ARTIFACT_ID=mordant
POM_NAME=Mordant
4 changes: 2 additions & 2 deletions mordant/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
POM_ARTIFACT_ID=mordant
POM_NAME=Mordant
POM_ARTIFACT_ID=mordant-core
POM_NAME=Mordant Core
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.github.ajalt.mordant.internal

import com.github.ajalt.mordant.terminal.TerminalInterface
import com.github.ajalt.mordant.internal.syscalls.SyscallHandlerNativePosix
import com.github.ajalt.mordant.terminal.terminalinterface.TerminalInterfaceNativeApple

internal actual fun getStandardTerminalInterface(): TerminalInterface = SyscallHandlerNativeApple
internal actual fun getStandardTerminalInterface(): TerminalInterface {
return TerminalInterfaceNativeApple()
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import platform.posix.*
// different bit widths for some of the termios fields, so the compileMetadata task would fail if we
// don't use separate files.

internal object TerminalInterfaceNativeApple : TerminalInterfaceNativePosix() {
internal class TerminalInterfaceNativeApple : TerminalInterfaceNativePosix() {
override val termiosConstants: TermiosConstants = TermiosConstants(
VTIME = VTIME,
VMIN = VMIN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ interface TerminalInterface {
fun readLineOrNull(hideInput: Boolean): String?

// TODO: docs
fun getTerminalSize(): Size?
fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? // null means retry
fun enterRawMode(mouseTracking: MouseTracking): AutoCloseable
fun getTerminalSize(): Size? = null
fun readInputEvent(timeout: Duration, mouseTracking: MouseTracking): InputEvent? = null // null means retry
fun enterRawMode(mouseTracking: MouseTracking): AutoCloseable {
throw NotImplementedError("Raw mode is not supported on this terminal")
}
fun shouldAutoUpdateSize(): Boolean = true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Args = --initialize-at-build-time=com.github.ajalt.mordant.internal.MppInternal_jvmKt,com.github.ajalt.mordant.internal.MppInternalKt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import kotlinx.coroutines.launch
suspend fun main() = coroutineScope {
val terminal = Terminal(ansiLevel = AnsiLevel.TRUECOLOR, interactive = true)
var hue = 0
val canvas = List(terminal.info.height - 1) {
MutableList<Color>(terminal.info.width) { RGB("#000") }
val canvas = List(terminal.size.height - 1) {
MutableList<Color>(terminal.size.width) { RGB("#000") }
}
val animation = terminal.textAnimation<Unit> {
buildString {
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ rootProject.name = "mordant"

include(
"mordant",
"mordant-omnibus",
"mordant-jvm-jna",
"mordant-jvm-ffm",
"mordant-jvm-graal-ffi",
Expand Down
2 changes: 1 addition & 1 deletion test/graalvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ tasks.test {
}

dependencies {
implementation(project(":mordant"))
implementation(project(":mordant-omnibus"))
testImplementation(kotlin("test"))
}

Expand Down
Loading

0 comments on commit 84eb06b

Please sign in to comment.